diff --git a/README.md b/README.md index 625eb068440..67ce034542e 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Nacos (official site: [nacos.io](https://nacos.io)) is an easy-to-use platform designed for dynamic service discovery and configuration and service management. It helps you to build cloud native applications and microservices platform easily. -Service is a first-class citizen in Nacos. Nacos supports almost all type of services,for example,[Dubbo/gRPC service](https://nacos.io/en-us/docs/use-nacos-with-dubbo.html), [Spring Cloud RESTFul service](https://nacos.io/en-us/docs/use-nacos-with-springcloud.html) or [Kubernetes service](https://nacos.io/en-us/docs/use-nacos-with-kubernetes.html). +Service is a first-class citizen in Nacos. Nacos supports almost all type of services,for example,[Dubbo/gRPC service](https://nacos.io/docs/latest/ecology/use-nacos-with-dubbo/), [Spring Cloud RESTFul service](https://nacos.io/docs/latest/ecology/use-nacos-with-spring-cloud/) or [Kubernetes service](https://nacos.io/docs/latest/quickstart/quick-start-kubernetes/). Nacos provides four major functions. @@ -68,23 +68,23 @@ On the **Windows** platform, run the following command to start server with stan startup.cmd -m standalone ``` -For more details, see [quick-start.](https://nacos.io/en-us/docs/quick-start.html) +For more details, see [quick-start.](https://nacos.io/docs/latest/quickstart/quick-start/) ## Quick start for other open-source projects: -* [Quick start with Nacos command and console](https://nacos.io/en-us/docs/quick-start.html) +* [Quick start with Nacos command and console](https://nacos.io/docs/latest/quickstart/quick-start/) -* [Quick start with dubbo](https://nacos.io/en-us/docs/use-nacos-with-dubbo.html) +* [Quick start with dubbo](https://nacos.io/docs/latest/ecology/use-nacos-with-dubbo/) -* [Quick start with spring cloud](https://nacos.io/en-us/docs/quick-start-spring-cloud.html) +* [Quick start with spring cloud](https://nacos.io/docs/latest/ecology/use-nacos-with-spring-cloud/) -* [Quick start with kubernetes](https://nacos.io/en-us/docs/use-nacos-with-kubernetes.html) +* [Quick start with kubernetes](https://nacos.io/docs/latest/quickstart/quick-start-kubernetes/) ## Documentation -You can view the full documentation from the [Nacos website](https://nacos.io/en-us/docs/v2/what-is-nacos.html). +You can view the full documentation from the [Nacos website](https://nacos.io/docs/latest/overview/). -You can also read this online eBook from the [NACOS ARCHITECTURE & PRINCIPLES](https://www.yuque.com/nacos/ebook/kbyo6n). +You can also read this online eBook from the [NACOS ARCHITECTURE & PRINCIPLES](https://nacos.io/docs/ebook/kbyo6n/). All the latest and long-term notice can also be found here from [GitHub notice issue](https://github.com/alibaba/nacos/labels/notice). diff --git a/api/pom.xml b/api/pom.xml index 7ddee1459d3..6d31744932e 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -100,6 +100,5 @@ javax.annotation javax.annotation-api - diff --git a/api/src/main/java/com/alibaba/nacos/api/NacosFactory.java b/api/src/main/java/com/alibaba/nacos/api/NacosFactory.java index 33c9e25e906..88b224e5a71 100644 --- a/api/src/main/java/com/alibaba/nacos/api/NacosFactory.java +++ b/api/src/main/java/com/alibaba/nacos/api/NacosFactory.java @@ -19,6 +19,8 @@ import com.alibaba.nacos.api.config.ConfigFactory; import com.alibaba.nacos.api.config.ConfigService; import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.LockService; +import com.alibaba.nacos.api.lock.NacosLockFactory; import com.alibaba.nacos.api.naming.NamingFactory; import com.alibaba.nacos.api.naming.NamingMaintainFactory; import com.alibaba.nacos.api.naming.NamingMaintainService; @@ -98,4 +100,15 @@ public static NamingMaintainService createMaintainService(String serverAddr) thr public static NamingMaintainService createMaintainService(Properties properties) throws NacosException { return NamingMaintainFactory.createMaintainService(properties); } + + /** + * Create lock service. + * + * @param properties init param + * @return lock service + * @throws NacosException Exception + */ + public static LockService createLockService(Properties properties) throws NacosException { + return NacosLockFactory.createLockService(properties); + } } diff --git a/api/src/main/java/com/alibaba/nacos/api/common/Constants.java b/api/src/main/java/com/alibaba/nacos/api/common/Constants.java index b3313dd45e8..c8acd6336a3 100644 --- a/api/src/main/java/com/alibaba/nacos/api/common/Constants.java +++ b/api/src/main/java/com/alibaba/nacos/api/common/Constants.java @@ -35,6 +35,8 @@ public class Constants { public static final String CLIENT_VERSION_KEY = "ClientVersion"; + public static final String CLIENT_IP = "ClientIp"; + public static final String UNKNOWN_APP = "UnknownApp"; public static final String DEFAULT_DOMAINNAME = "commonconfig.config-host.taobao.com"; @@ -274,6 +276,15 @@ public static class Naming { public static final String CMDB_CONTEXT_TYPE = "CMDB"; } + /** + * The constants in lock directory. + */ + public static class Lock { + + public static final String LOCK_MODULE = "lock"; + + } + /** * The constants in remote directory. */ diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequest.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequest.java index c9dab7288f6..557dc29c286 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequest.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequest.java @@ -25,14 +25,16 @@ * @version $Id: ConfigChangeClusterSyncRequest.java, v 0.1 2020年08月11日 4:30 PM liuzunfei Exp $ */ public class ConfigChangeClusterSyncRequest extends AbstractConfigRequest { - - String tag; - + long lastModified; + String grayName; + + @Deprecated boolean isBeta; - boolean isBatch; + @Deprecated + String tag; public boolean isBeta() { return isBeta; @@ -42,14 +44,6 @@ public void setBeta(boolean beta) { isBeta = beta; } - public boolean isBatch() { - return isBatch; - } - - public void setBatch(boolean batch) { - isBatch = batch; - } - /** * Getter method for property tag. * @@ -68,6 +62,14 @@ public void setTag(String tag) { this.tag = tag; } + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + /** * Getter method for property lastModified. * diff --git a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigQueryResponse.java b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigQueryResponse.java index 6b417fd6711..d92f273ed62 100644 --- a/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigQueryResponse.java +++ b/api/src/main/java/com/alibaba/nacos/api/config/remote/response/ConfigQueryResponse.java @@ -30,6 +30,8 @@ public class ConfigQueryResponse extends Response { public static final int CONFIG_QUERY_CONFLICT = 400; + public static final int NO_RIGHT = 403; + String content; String encryptedDataKey; diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/LockService.java b/api/src/main/java/com/alibaba/nacos/api/lock/LockService.java new file mode 100644 index 00000000000..589c5c9d8f9 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/LockService.java @@ -0,0 +1,80 @@ +/* + * 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.api.lock; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.model.LockInstance; + +import java.util.Properties; + +/** + * Nacos Lock Process. + * + *

lock => {@link LockService#lock(LockInstance)} -> {@link LockInstance#lock(LockService)} -> + * {@link LockService#remoteTryLock(LockInstance)}
unLock => {@link LockService#unLock(LockInstance)} -> + * {@link LockInstance#unLock(LockService)} -> {@link LockService#remoteReleaseLock(LockInstance)} + * + * @author 985492783@qq.com + * @date 2023/8/24 19:49 + */ +public interface LockService { + + /** + * Real lock method expose to user to acquire the lock.
It will call {@link LockInstance#lock(LockService)} + *
+ * + * @param instance instance + * @return Boolean + * @throws NacosException NacosException + */ + Boolean lock(LockInstance instance) throws NacosException; + + /** + * Real lock method expose to user to release the lock.
It will call {@link LockInstance#unLock(LockService)} + *
+ * + * @param instance instance + * @return Boolean + * @throws NacosException NacosException + */ + Boolean unLock(LockInstance instance) throws NacosException; + + /** + * use grpc request to try lock. + * + * @param instance instance + * @return Boolean + * @throws NacosException NacosException + */ + Boolean remoteTryLock(LockInstance instance) throws NacosException; + + /** + * use grpc request to release lock. + * + * @param instance instance + * @return Boolean + * @throws NacosException NacosException + */ + Boolean remoteReleaseLock(LockInstance instance) throws NacosException; + + /** + * get properties. + * + * @return Properties + */ + Properties getProperties(); +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/NacosLockFactory.java b/api/src/main/java/com/alibaba/nacos/api/lock/NacosLockFactory.java new file mode 100644 index 00000000000..82a97a1a3cd --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/NacosLockFactory.java @@ -0,0 +1,48 @@ +/* + * 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.api.lock; + +import com.alibaba.nacos.api.exception.NacosException; + +import java.lang.reflect.Constructor; +import java.util.Properties; + +/** + * lock Factory. + * + * @author 985492783@qq.com + * @date 2023/8/25 0:40 + */ +public class NacosLockFactory { + + /** + * Create a new lock service. + * + * @param properties lock service properties + * @return new lock service + * @throws NacosException nacos exception + */ + public static LockService createLockService(Properties properties) throws NacosException { + try { + Class driverImplClass = Class.forName("com.alibaba.nacos.client.lock.NacosLockService"); + Constructor constructor = driverImplClass.getConstructor(Properties.class); + return (LockService) constructor.newInstance(properties); + } catch (Throwable e) { + throw new NacosException(NacosException.CLIENT_INVALID_PARAM, e); + } + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/common/LockConstants.java b/api/src/main/java/com/alibaba/nacos/api/lock/common/LockConstants.java new file mode 100644 index 00000000000..af4c8de0bc6 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/common/LockConstants.java @@ -0,0 +1,28 @@ +/* + * 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.api.lock.common; + +/** + * lock constant. + * + * @author 985492783@qq.com + * @date 2023/8/23 15:53 + */ +public class LockConstants { + + public static final String NACOS_LOCK_TYPE = "NACOS_LOCK"; +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/constant/PropertyConstants.java b/api/src/main/java/com/alibaba/nacos/api/lock/constant/PropertyConstants.java new file mode 100644 index 00000000000..dccfd1fd8e7 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/constant/PropertyConstants.java @@ -0,0 +1,31 @@ +/* + * 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.api.lock.constant; + +/** + * lock properties constants. + * @author 985492783@qq.com + * @description PropertyConstants + * @date 2023/6/28 17:38 + */ +public class PropertyConstants { + public static final String LOCK_REQUEST_TIMEOUT = "lockRequestTimeout"; + + public static final String LOCK_DEFAULT_WAIT_TIME = "nacos.lock.default_wait_time"; + + public static final Long LOCK_DEFAULT_WAIT_SECOND = 10_000L; +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/model/LockInstance.java b/api/src/main/java/com/alibaba/nacos/api/lock/model/LockInstance.java new file mode 100644 index 00000000000..95f0866d082 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/model/LockInstance.java @@ -0,0 +1,112 @@ +/* + * 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.api.lock.model; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.LockService; + +import java.io.Serializable; +import java.util.Map; + +/** + * lock info entity. + * + * @author 985492783@qq.com + * @date 2023/6/28 2:46 + */ +public class LockInstance implements Serializable { + + private static final long serialVersionUID = -3460985546826875524L; + + private String key; + + private Long expiredTime; + + private Map params; + + private String lockType; + + public LockInstance(String key, Long expiredTime, String lockType) { + this.key = key; + this.expiredTime = expiredTime; + this.lockType = lockType; + } + + public LockInstance() { + } + + public Long getExpiredTime() { + return expiredTime; + } + + public void setExpiredTime(Long expiredTime) { + this.expiredTime = expiredTime; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } + + /** + * Will call {@link LockService#remoteTryLock(LockInstance)} request grpc to get lock and do something.
can be + * {@link Override} to do some client special logic. + * + * @param lockService {@link LockService} + * @return Boolean {@link Boolean} + * @throws NacosException NacosException + */ + public Boolean lock(LockService lockService) throws NacosException { + return lockService.remoteTryLock(this); + } + + /** + * Will call {@link LockService#remoteReleaseLock(LockInstance)} request grpc to release lock and do something.
+ * can be {@link Override} to do some client special logic. + * + * @param lockService {@link LockService} + * @return Boolean {@link Boolean} + * @throws NacosException NacosException + */ + public Boolean unLock(LockService lockService) throws NacosException { + return lockService.remoteReleaseLock(this); + } + + /** + * spi get lock type. + * + * @return type + */ + public String getLockType() { + return lockType; + } + + public void setLockType(String lockType) { + this.lockType = lockType; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/remote/AbstractLockRequest.java b/api/src/main/java/com/alibaba/nacos/api/lock/remote/AbstractLockRequest.java new file mode 100644 index 00000000000..22e15396f42 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/remote/AbstractLockRequest.java @@ -0,0 +1,36 @@ +/* + * 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.api.lock.remote; + +import com.alibaba.nacos.api.remote.request.Request; + +import static com.alibaba.nacos.api.common.Constants.Lock.LOCK_MODULE; + +/** + * lock grpc request. + * + * @author 985492783@qq.com + * @description LockRequest + * @date 2023/6/29 12:00 + */ +public abstract class AbstractLockRequest extends Request { + + @Override + public String getModule() { + return LOCK_MODULE; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/remote/LockOperationEnum.java b/api/src/main/java/com/alibaba/nacos/api/lock/remote/LockOperationEnum.java new file mode 100644 index 00000000000..b2f64008424 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/remote/LockOperationEnum.java @@ -0,0 +1,41 @@ +/* + * 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.api.lock.remote; + +import java.io.Serializable; + +/** + * lock operation. + * @author 985492783@qq.com + */ +public enum LockOperationEnum implements Serializable { + + /** + * Acquire. + */ + ACQUIRE, + /** + * Release. + */ + RELEASE, + /** + * Expire. + */ + EXPIRE; + + private static final long serialVersionUID = -241044344531890549L; +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/remote/request/LockOperationRequest.java b/api/src/main/java/com/alibaba/nacos/api/lock/remote/request/LockOperationRequest.java new file mode 100644 index 00000000000..777aa835537 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/remote/request/LockOperationRequest.java @@ -0,0 +1,51 @@ +/* + * 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.api.lock.remote.request; + +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.api.lock.remote.AbstractLockRequest; +import com.alibaba.nacos.api.lock.remote.LockOperationEnum; + +/** + * grpc acquire lock request. + * + * @author 985492783@qq.com + * @description AcquireLockRequest + * @date 2023/6/29 12:01 + */ +public class LockOperationRequest extends AbstractLockRequest { + + private LockInstance lockInstance; + + private LockOperationEnum lockOperationEnum; + + public LockInstance getLockInstance() { + return lockInstance; + } + + public void setLockInstance(LockInstance lockInstance) { + this.lockInstance = lockInstance; + } + + public LockOperationEnum getLockOperationEnum() { + return lockOperationEnum; + } + + public void setLockOperationEnum(LockOperationEnum lockOperationEnum) { + this.lockOperationEnum = lockOperationEnum; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/lock/remote/response/LockOperationResponse.java b/api/src/main/java/com/alibaba/nacos/api/lock/remote/response/LockOperationResponse.java new file mode 100644 index 00000000000..bafdc83cb90 --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/lock/remote/response/LockOperationResponse.java @@ -0,0 +1,70 @@ +/* + * 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.api.lock.remote.response; + +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ResponseCode; + +/** + * grpc acquire lock response. + * + * @author 985492783@qq.com + * @description AcquireLockResponse + * @date 2023/6/29 13:51 + */ +public class LockOperationResponse extends Response { + + private Object result; + + public LockOperationResponse() { + + } + + public LockOperationResponse(Boolean result) { + this.result = result; + } + + /** + * create success response. + * @param result result + * @return LockOperationResponse + */ + public static LockOperationResponse success(Boolean result) { + LockOperationResponse response = new LockOperationResponse(result); + return response; + } + + /** + * create fail response. + * @param message message + * @return LockOperationResponse + */ + public static LockOperationResponse fail(String message) { + LockOperationResponse response = new LockOperationResponse(false); + response.setResultCode(ResponseCode.FAIL.getCode()); + response.setMessage(message); + return response; + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java index 53b4c35735f..4ee3ee2e188 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/ErrorCode.java @@ -90,6 +90,26 @@ public enum ErrorCode { */ PARAMETER_MISMATCH(20009, "parameter mismatch"), + /** + * config gray request error. + */ + CONFIG_GRAY_OVER_MAX_VERSION_COUNT(20010, "config gray version version over max count"), + + /** + * config gray tag v2 rule format invalid. + */ + CONFIG_GRAY_RULE_FORMAT_INVALID(20011, "config gray rule format invalid"), + + /** + * config gray tag v2 rule version invalid. + */ + CONFIG_GRAY_VERSION_INVALID(20012, "config gray rule version invalid"), + + /** + * config gray request error. + */ + CONFIG_GRAY_NAME_UNRECOGNIZED_ERROR(20013, "config gray name not recognized"), + /** * service name error. */ @@ -183,7 +203,25 @@ public enum ErrorCode { /** * server error. */ - SERVER_ERROR(30000, "server error"); + SERVER_ERROR(30000, "server error"), + + /** + * API will be deprecated. + */ + API_DEPRECATED(40000, "API deprecated."), + + /** + * Config use 100001 ~ 100999. + **/ + METADATA_ILLEGAL(100002, "导入的元数据非法"), + + DATA_VALIDATION_FAILED(100003, "未读取到合法数据"), + + PARSING_DATA_FAILED(100004, "解析数据失败"), + + DATA_EMPTY(100005, "导入的文件数据为空"), + + NO_SELECTED_CONFIG(100006, "没有选择任何配置"); private final Integer code; diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java index 98ae78c5f73..183fa8a4555 100644 --- a/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/Result.java @@ -96,6 +96,17 @@ public static Result failure(ErrorCode errorCode, T data) { return new Result<>(errorCode.getCode(), errorCode.getMsg(), data); } + /** + * Failed return with code, message and data. + * @param data type + * @param code error code + * @param msg error message + * @return Result + */ + public static Result failure(Integer code, String msg, T data) { + return new Result<>(code, msg, data); + } + @Override public String toString() { return "Result{" + "errorCode=" + code + ", message='" + message + '\'' + ", data=" + data + '}'; diff --git a/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java b/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java new file mode 100644 index 00000000000..8ca862cef5f --- /dev/null +++ b/api/src/main/java/com/alibaba/nacos/api/model/v2/SupportedLanguage.java @@ -0,0 +1,60 @@ +/* + * Copyright 1999-2024 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.api.model.v2; + +/** + * Supported languages for announcements. + * + * @author zhangyukun on:2024/9/24 + */ +public enum SupportedLanguage { + /** + * Chinese language. + */ + ZH_CN("zh-CN"), + + /** + * English language. + */ + EN_US("en-US"); + + private final String language; + + SupportedLanguage(String language) { + this.language = language; + } + + public String getLanguage() { + return language; + } + + /** + * Check if the given language is supported. + * + * @param language the language to check + * @return true if the language is supported, false otherwise + */ + public static boolean isSupported(String language) { + for (SupportedLanguage lang : SupportedLanguage.values()) { + if (lang.getLanguage().equals(language)) { + return true; + } + } + return false; + } +} diff --git a/api/src/main/java/com/alibaba/nacos/api/remote/RemoteConstants.java b/api/src/main/java/com/alibaba/nacos/api/remote/RemoteConstants.java index a5b22ffc2b4..904af67ec38 100644 --- a/api/src/main/java/com/alibaba/nacos/api/remote/RemoteConstants.java +++ b/api/src/main/java/com/alibaba/nacos/api/remote/RemoteConstants.java @@ -40,4 +40,6 @@ public class RemoteConstants { public static final String LABEL_MODULE_NAMING = "naming"; public static final String MONITOR_LABEL_NONE = "none"; + + public static final String LABEL_MODULE_LOCK = "lock"; } diff --git a/api/src/main/java/com/alibaba/nacos/api/remote/request/RequestMeta.java b/api/src/main/java/com/alibaba/nacos/api/remote/request/RequestMeta.java index c633f893484..221e90cb7df 100644 --- a/api/src/main/java/com/alibaba/nacos/api/remote/request/RequestMeta.java +++ b/api/src/main/java/com/alibaba/nacos/api/remote/request/RequestMeta.java @@ -18,9 +18,11 @@ import com.alibaba.nacos.api.ability.constant.AbilityKey; import com.alibaba.nacos.api.ability.constant.AbilityStatus; +import com.alibaba.nacos.api.common.Constants; import java.util.HashMap; import java.util.Map; +import java.util.Objects; /** * RequestMeta info. @@ -38,6 +40,8 @@ public class RequestMeta { private Map labels = new HashMap<>(); + private Map appLabels = new HashMap<>(); + private Map abilityTable; public AbilityStatus getConnectionAbility(AbilityKey abilityKey) { @@ -90,6 +94,35 @@ public Map getLabels() { */ public void setLabels(Map labels) { this.labels = labels; + extractAppLabels(); + } + + private void extractAppLabels() { + HashMap applabelsMap = new HashMap(8) { + { + put(Constants.APPNAME, labels.get(Constants.APPNAME)); + put(Constants.CLIENT_VERSION_KEY, clientVersion); + put(Constants.CLIENT_IP, clientIp); + } + }; + labels.entrySet().stream().filter(Objects::nonNull).filter(e -> e.getKey().startsWith(Constants.APP_CONN_PREFIX) + && e.getKey().length() > Constants.APP_CONN_PREFIX.length() && !e.getValue().trim().isEmpty()) + .forEach(entry -> { + applabelsMap.putIfAbsent(entry.getKey().substring(Constants.APP_CONN_PREFIX.length()), + entry.getValue()); + }); + this.appLabels = applabelsMap; + } + + /** + * get labels map with filter of starting with prefix #{@link Constants#APP_CONN_PREFIX} and return a new map trim + * the prefix #{@link Constants#APP_CONN_PREFIX}. + * + * @return map of labels. + * @date 2024/2/29 + */ + public Map getAppLabels() { + return appLabels; } /** diff --git a/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload b/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload index cbd1e87502a..618f0a47132 100644 --- a/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload +++ b/api/src/main/resources/META-INF/services/com.alibaba.nacos.api.remote.Payload @@ -57,4 +57,6 @@ com.alibaba.nacos.api.naming.remote.response.InstanceResponse com.alibaba.nacos.api.naming.remote.response.NotifySubscriberResponse com.alibaba.nacos.api.naming.remote.response.QueryServiceResponse com.alibaba.nacos.api.naming.remote.response.ServiceListResponse -com.alibaba.nacos.api.naming.remote.response.SubscribeServiceResponse \ No newline at end of file +com.alibaba.nacos.api.naming.remote.response.SubscribeServiceResponse +com.alibaba.nacos.api.lock.remote.request.LockOperationRequest +com.alibaba.nacos.api.lock.remote.response.LockOperationResponse diff --git a/api/src/test/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequestTest.java b/api/src/test/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequestTest.java index d1055c18c73..6562f54dee7 100644 --- a/api/src/test/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequestTest.java +++ b/api/src/test/java/com/alibaba/nacos/api/config/remote/request/cluster/ConfigChangeClusterSyncRequestTest.java @@ -40,7 +40,6 @@ void before() { configChangeClusterSyncRequest.setTag(TAG); configChangeClusterSyncRequest.setBeta(Boolean.TRUE); configChangeClusterSyncRequest.setLastModified(0L); - configChangeClusterSyncRequest.setBatch(false); configChangeClusterSyncRequest.putAllHeader(HEADERS); requestId = injectRequestUuId(configChangeClusterSyncRequest); } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java index 150738db186..50bc4e765c0 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/AbstractProtocolAuthService.java @@ -17,8 +17,13 @@ package com.alibaba.nacos.auth; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; +import com.alibaba.nacos.auth.serveridentity.ServerIdentity; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityChecker; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityCheckerHolder; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.auth.util.Loggers; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; @@ -40,28 +45,36 @@ */ public abstract class AbstractProtocolAuthService implements ProtocolAuthService { - protected final AuthConfigs authConfigs; + protected final NacosAuthConfig authConfig; - protected AbstractProtocolAuthService(AuthConfigs authConfigs) { - this.authConfigs = authConfigs; + protected final ServerIdentityChecker checker; + + protected AbstractProtocolAuthService(NacosAuthConfig authConfig) { + this.authConfig = authConfig; + this.checker = ServerIdentityCheckerHolder.getInstance().getChecker(); + } + + @Override + public void initialize() { + this.checker.init(authConfig); } @Override public boolean enableAuth(Secured secured) { Optional authPluginService = AuthPluginManager.getInstance() - .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType()); + .findAuthServiceSpiImpl(authConfig.getNacosAuthSystemType()); if (authPluginService.isPresent()) { return authPluginService.get().enableAuth(secured.action(), secured.signType()); } Loggers.AUTH.warn("Can't find auth plugin for type {}, please add plugin to classpath or set {} as false", - authConfigs.getNacosAuthSystemType(), Constants.Auth.NACOS_CORE_AUTH_ENABLED); + authConfig.getNacosAuthSystemType(), Constants.Auth.NACOS_CORE_AUTH_ENABLED); return false; } @Override public boolean validateIdentity(IdentityContext identityContext, Resource resource) throws AccessException { Optional authPluginService = AuthPluginManager.getInstance() - .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType()); + .findAuthServiceSpiImpl(authConfig.getNacosAuthSystemType()); if (authPluginService.isPresent()) { return authPluginService.get().validateIdentity(identityContext, resource); } @@ -71,13 +84,37 @@ public boolean validateIdentity(IdentityContext identityContext, Resource resour @Override public boolean validateAuthority(IdentityContext identityContext, Permission permission) throws AccessException { Optional authPluginService = AuthPluginManager.getInstance() - .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType()); + .findAuthServiceSpiImpl(authConfig.getNacosAuthSystemType()); if (authPluginService.isPresent()) { return authPluginService.get().validateAuthority(identityContext, permission); } return true; } + @Override + public ServerIdentityResult checkServerIdentity(R request, Secured secured) { + if (isInvalidServerIdentity()) { + return ServerIdentityResult.fail( + "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`"); + } + ServerIdentity serverIdentity = parseServerIdentity(request); + return checker.check(serverIdentity, secured); + } + + private boolean isInvalidServerIdentity() { + return StringUtils.isBlank(authConfig.getServerIdentityKey()) || StringUtils.isBlank( + authConfig.getServerIdentityValue()); + } + + /** + * Parse server identity from protocol request. + * + * @param request protocol request + * @return nacos server identity. + */ + protected abstract ServerIdentity parseServerIdentity(R request); + /** * Get resource from secured annotation specified resource. * diff --git a/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java index 7b4c3197931..7f0ae736880 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/GrpcProtocolAuthService.java @@ -18,16 +18,19 @@ import com.alibaba.nacos.api.remote.request.Request; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.plugin.auth.api.IdentityContext; -import com.alibaba.nacos.plugin.auth.api.Resource; -import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.plugin.auth.constant.SignType; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.auth.context.GrpcIdentityContextBuilder; import com.alibaba.nacos.auth.parser.grpc.AbstractGrpcResourceParser; import com.alibaba.nacos.auth.parser.grpc.ConfigGrpcResourceParser; import com.alibaba.nacos.auth.parser.grpc.NamingGrpcResourceParser; +import com.alibaba.nacos.auth.serveridentity.ServerIdentity; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.auth.util.Loggers; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.plugin.auth.constant.SignType; import java.util.HashMap; import java.util.Map; @@ -43,14 +46,15 @@ public class GrpcProtocolAuthService extends AbstractProtocolAuthService(2); - identityContextBuilder = new GrpcIdentityContextBuilder(authConfigs); + identityContextBuilder = new GrpcIdentityContextBuilder(authConfig); } @Override public void initialize() { + super.initialize(); resourceParserMap.put(SignType.NAMING, new NamingGrpcResourceParser()); resourceParserMap.put(SignType.CONFIG, new ConfigGrpcResourceParser()); } @@ -73,4 +77,19 @@ public Resource parseResource(Request request, Secured secured) { public IdentityContext parseIdentity(Request request) { return identityContextBuilder.build(request); } + + @Override + public ServerIdentityResult checkServerIdentity(Request request, Secured secured) { + if (ApiType.INNER_API != secured.apiType()) { + return ServerIdentityResult.noMatched(); + } + return super.checkServerIdentity(request, secured); + } + + @Override + protected ServerIdentity parseServerIdentity(Request request) { + String serverIdentityKey = authConfig.getServerIdentityKey(); + String serverIdentity = request.getHeader(serverIdentityKey); + return new ServerIdentity(serverIdentityKey, serverIdentity); + } } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java index 3d6507246db..47fb9a41131 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/HttpProtocolAuthService.java @@ -17,16 +17,17 @@ package com.alibaba.nacos.auth; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.plugin.auth.api.IdentityContext; -import com.alibaba.nacos.plugin.auth.api.Resource; -import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.plugin.auth.constant.SignType; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.auth.context.HttpIdentityContextBuilder; import com.alibaba.nacos.auth.parser.http.AbstractHttpResourceParser; import com.alibaba.nacos.auth.parser.http.ConfigHttpResourceParser; import com.alibaba.nacos.auth.parser.http.NamingHttpResourceParser; +import com.alibaba.nacos.auth.serveridentity.ServerIdentity; import com.alibaba.nacos.auth.util.Loggers; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.constant.SignType; import jakarta.servlet.http.HttpServletRequest; import java.util.HashMap; @@ -43,14 +44,15 @@ public class HttpProtocolAuthService extends AbstractProtocolAuthService(2); - identityContextBuilder = new HttpIdentityContextBuilder(authConfigs); + identityContextBuilder = new HttpIdentityContextBuilder(authConfig); } @Override public void initialize() { + super.initialize(); resourceParserMap.put(SignType.NAMING, new NamingHttpResourceParser()); resourceParserMap.put(SignType.CONFIG, new ConfigHttpResourceParser()); } @@ -72,4 +74,11 @@ public Resource parseResource(HttpServletRequest request, Secured secured) { public IdentityContext parseIdentity(HttpServletRequest request) { return identityContextBuilder.build(request); } + + @Override + protected ServerIdentity parseServerIdentity(HttpServletRequest request) { + String serverIdentityKey = authConfig.getServerIdentityKey(); + String serverIdentity = request.getHeader(serverIdentityKey); + return new ServerIdentity(serverIdentityKey, serverIdentity); + } } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java b/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java index 0815db4d224..f2f99366931 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/ProtocolAuthService.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.auth; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; @@ -85,4 +86,13 @@ public interface ProtocolAuthService { * @throws AccessException exception during validating */ boolean validateAuthority(IdentityContext identityContext, Permission permission) throws AccessException; + + /** + * check server identity. + * + * @param request protocol request + * @param secured secured api secured annotation + * @return server identity result + */ + ServerIdentityResult checkServerIdentity(R request, Secured secured); } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/annotation/Secured.java b/auth/src/main/java/com/alibaba/nacos/auth/annotation/Secured.java index c1c0fe24f4b..5c3548c16b1 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/annotation/Secured.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/annotation/Secured.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.auth.annotation; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.auth.parser.DefaultResourceParser; import com.alibaba.nacos.auth.parser.ResourceParser; import com.alibaba.nacos.common.utils.StringUtils; @@ -70,4 +71,12 @@ * @return tags */ String[] tags() default {}; + + /** + * The type of API. Distinguishing between ADMIN_API and OPEN_API. + * + * @return the type of the API + */ + ApiType apiType() default ApiType.OPEN_API; + } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/config/AuthConfigs.java b/auth/src/main/java/com/alibaba/nacos/auth/config/AuthConfigs.java index 35375143b7e..7c3c7de838b 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/config/AuthConfigs.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/config/AuthConfigs.java @@ -58,11 +58,17 @@ public class AuthConfigs extends Subscriber { private static Boolean cachingEnabled = null; /** - * Whether auth enabled. + * Whether server auth enabled. */ @Value("${" + Constants.Auth.NACOS_CORE_AUTH_ENABLED + ":false}") private boolean authEnabled; + /** + * Whether console auth enabled. + */ + @Value("${" + Constants.Auth.NACOS_CORE_AUTH_CONSOLE_ENABLED + ":true}") + private boolean consoleAuthEnabled; + /** * Which auth system is in use. */ @@ -75,9 +81,6 @@ public class AuthConfigs extends Subscriber { @Value("${" + Constants.Auth.NACOS_CORE_AUTH_SERVER_IDENTITY_VALUE + ":}") private String serverIdentityValue; - @Value("${" + Constants.Auth.NACOS_CORE_AUTH_ENABLE_USER_AGENT_AUTH_WHITE + ":false}") - private boolean enableUserAgentAuthWhite; - private boolean hasGlobalAdminRole; private Map authPluginProperties = new HashMap<>(); @@ -94,7 +97,7 @@ public AuthConfigs() { */ @PostConstruct public void validate() throws NacosException { - if (!authEnabled) { + if (!authEnabled && !consoleAuthEnabled) { return; } if (StringUtils.isEmpty(nacosAuthSystemType)) { @@ -147,19 +150,24 @@ public String getServerIdentityValue() { return serverIdentityValue; } - public boolean isEnableUserAgentAuthWhite() { - return enableUserAgentAuthWhite; + /** + * console auth function is open. + * + * @return console auth function is open + */ + public boolean isConsoleAuthEnabled() { + return consoleAuthEnabled; } /** - * auth function is open. + * server auth function is open. * - * @return auth function is open + * @return server auth function is open */ public boolean isAuthEnabled() { return authEnabled; } - + /** * Whether permission information can be cached. * @@ -189,11 +197,10 @@ public static void setCachingEnabled(boolean cachingEnabled) { public void onEvent(ServerConfigChangeEvent event) { try { authEnabled = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_ENABLED, Boolean.class, false); + consoleAuthEnabled = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_CONSOLE_ENABLED, Boolean.class, true); cachingEnabled = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_CACHING_ENABLED, Boolean.class, true); serverIdentityKey = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_SERVER_IDENTITY_KEY, ""); serverIdentityValue = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_SERVER_IDENTITY_VALUE, ""); - enableUserAgentAuthWhite = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_ENABLE_USER_AGENT_AUTH_WHITE, - Boolean.class, false); nacosAuthSystemType = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_SYSTEM_TYPE, ""); refreshPluginProperties(); ModuleStateHolder.getInstance().getModuleState(AuthModuleStateBuilder.AUTH_MODULE) diff --git a/auth/src/main/java/com/alibaba/nacos/auth/config/AuthErrorCode.java b/auth/src/main/java/com/alibaba/nacos/auth/config/AuthErrorCode.java index 85e846f8172..4e2f27e3619 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/config/AuthErrorCode.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/config/AuthErrorCode.java @@ -27,10 +27,10 @@ public enum AuthErrorCode { * invalid auth type. */ INVALID_TYPE(50001, - "Invalid auth type, Please set `nacos.core.auth.system.type`, detail: https://nacos.io/zh-cn/docs/v2/plugin/auth-plugin.html"), + "Invalid auth type, Please set `nacos.core.auth.system.type`, detail: https://nacos.io/docs/latest/manual/admin/auth/"), EMPTY_IDENTITY(50002, - "Empty identity, Please set `nacos.core.auth.server.identity.key` and `nacos.core.auth.server.identity.value`, detail: https://nacos.io/zh-cn/docs/v2/guide/user/auth.html"); + "Empty identity, Please set `nacos.core.auth.server.identity.key` and `nacos.core.auth.server.identity.value`, detail: https://nacos.io/docs/latest/manual/admin/auth/"); private final Integer code; diff --git a/auth/src/main/java/com/alibaba/nacos/auth/config/AuthModuleStateBuilder.java b/auth/src/main/java/com/alibaba/nacos/auth/config/AuthModuleStateBuilder.java index b6eb66657ce..079fb732ba5 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/config/AuthModuleStateBuilder.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/config/AuthModuleStateBuilder.java @@ -47,7 +47,7 @@ public class AuthModuleStateBuilder implements ModuleStateBuilder { public ModuleState build() { ModuleState result = new ModuleState(AUTH_MODULE); AuthConfigs authConfigs = ApplicationUtils.getBean(AuthConfigs.class); - result.newState(AUTH_ENABLED, authConfigs.isAuthEnabled()); + result.newState(AUTH_ENABLED, authConfigs.isConsoleAuthEnabled()); result.newState(LOGIN_PAGE_ENABLED, isLoginPageEnabled(authConfigs)); result.newState(AUTH_SYSTEM_TYPE, authConfigs.getNacosAuthSystemType()); result.newState(AUTH_ADMIN_REQUEST, isAdminRequest(authConfigs)); diff --git a/auth/src/main/java/com/alibaba/nacos/auth/config/NacosAuthConfig.java b/auth/src/main/java/com/alibaba/nacos/auth/config/NacosAuthConfig.java new file mode 100644 index 00000000000..c133786aa18 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/config/NacosAuthConfig.java @@ -0,0 +1,60 @@ +/* + * 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.auth.config; + +/** + * Nacos Auth configurations. + * + * @author xiweng.yy + */ +public interface NacosAuthConfig { + + /** + * Whether nacos server or console auth enabled. + * + * @return {@code true} means enabled, otherwise {@code false} + */ + boolean isAuthEnabled(); + + /** + * Get current auth plugin type. + * + * @return auth plugin type. + */ + String getNacosAuthSystemType(); + + /** + * Whether support server identity to identify request from other nacos servers. + * + * @return {@code true} means supported, otherwise {@code false} + */ + boolean isSupportServerIdentity(); + + /** + * Get server identity key. + * + * @return server identity key If {@link #isSupportServerIdentity()} return {@code true}, otherwise empty string. + */ + String getServerIdentityKey(); + + /** + * Get server identity value. + * + * @return server identity value If {@link #isSupportServerIdentity()} return {@code true}, otherwise empty string. + */ + String getServerIdentityValue(); +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilder.java b/auth/src/main/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilder.java index bfa3af6303c..0247a1f3137 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilder.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilder.java @@ -17,7 +17,7 @@ package com.alibaba.nacos.auth.context; import com.alibaba.nacos.api.remote.request.Request; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.constant.Constants; import com.alibaba.nacos.plugin.auth.spi.server.AuthPluginManager; @@ -35,10 +35,10 @@ */ public class GrpcIdentityContextBuilder implements IdentityContextBuilder { - private final AuthConfigs authConfigs; + private final NacosAuthConfig authConfig; - public GrpcIdentityContextBuilder(AuthConfigs authConfigs) { - this.authConfigs = authConfigs; + public GrpcIdentityContextBuilder(NacosAuthConfig authConfig) { + this.authConfig = authConfig; } /** @@ -51,7 +51,7 @@ public GrpcIdentityContextBuilder(AuthConfigs authConfigs) { @Override public IdentityContext build(Request request) { Optional authPluginService = AuthPluginManager.getInstance() - .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType()); + .findAuthServiceSpiImpl(authConfig.getNacosAuthSystemType()); IdentityContext result = new IdentityContext(); getRemoteIp(request, result); if (!authPluginService.isPresent()) { diff --git a/auth/src/main/java/com/alibaba/nacos/auth/context/HttpIdentityContextBuilder.java b/auth/src/main/java/com/alibaba/nacos/auth/context/HttpIdentityContextBuilder.java index 0571c7f9e1c..92760391afd 100644 --- a/auth/src/main/java/com/alibaba/nacos/auth/context/HttpIdentityContextBuilder.java +++ b/auth/src/main/java/com/alibaba/nacos/auth/context/HttpIdentityContextBuilder.java @@ -16,7 +16,7 @@ package com.alibaba.nacos.auth.context; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.constant.Constants; @@ -40,10 +40,10 @@ public class HttpIdentityContextBuilder implements IdentityContextBuilder authPluginService = AuthPluginManager.getInstance() - .findAuthServiceSpiImpl(authConfigs.getNacosAuthSystemType()); + .findAuthServiceSpiImpl(authConfig.getNacosAuthSystemType()); if (!authPluginService.isPresent()) { return result; } diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/DefaultChecker.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/DefaultChecker.java new file mode 100644 index 00000000000..6403f858e86 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/DefaultChecker.java @@ -0,0 +1,43 @@ +/* + * 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.auth.serveridentity; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.NacosAuthConfig; + +/** + * Nacos default server identity checker. + * + * @author xiweng.yy + */ +public class DefaultChecker implements ServerIdentityChecker { + + private NacosAuthConfig authConfig; + + @Override + public void init(NacosAuthConfig authConfigs) { + this.authConfig = authConfigs; + } + + @Override + public ServerIdentityResult check(ServerIdentity serverIdentity, Secured secured) { + if (authConfig.getServerIdentityValue().equals(serverIdentity.getIdentityValue())) { + return ServerIdentityResult.success(); + } + return ServerIdentityResult.noMatched(); + } +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentity.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentity.java new file mode 100644 index 00000000000..2c846e3f87d --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentity.java @@ -0,0 +1,42 @@ +/* + * 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.auth.serveridentity; + +/** + * Nacos server identity. + * + * @author xiweng.yy + */ +public class ServerIdentity { + + private final String identityKey; + + private final String identityValue; + + public ServerIdentity(String identityKey, String identityValue) { + this.identityKey = identityKey; + this.identityValue = identityValue; + } + + public String getIdentityKey() { + return identityKey; + } + + public String getIdentityValue() { + return identityValue; + } +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityChecker.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityChecker.java new file mode 100644 index 00000000000..3cb13268f73 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityChecker.java @@ -0,0 +1,44 @@ +/* + * 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.auth.serveridentity; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.NacosAuthConfig; + +/** + * Nacos server identity checker for nacos inner/admin API identity check. + * + * @author xiweng.yy + */ +public interface ServerIdentityChecker { + + /** + * Do init checker. + * + * @param authConfig config for nacos auth. + */ + void init(NacosAuthConfig authConfig); + + /** + * Do check nacos server identity. + * + * @param serverIdentity server identity + * @param secured secured api secured annotation + * @return result of checking server identity + */ + ServerIdentityResult check(ServerIdentity serverIdentity, Secured secured); +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolder.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolder.java new file mode 100644 index 00000000000..77469df5e32 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolder.java @@ -0,0 +1,74 @@ +/* + * 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.auth.serveridentity; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; + +/** + * Server Identity Checker SPI holder. + * + * @author xiweng.yy + */ +public class ServerIdentityCheckerHolder { + + private static final Logger LOGGER = LoggerFactory.getLogger(ServerIdentityCheckerHolder.class); + + private static final ServerIdentityCheckerHolder INSTANCE = new ServerIdentityCheckerHolder(); + + private ServerIdentityChecker checker; + + private ServerIdentityCheckerHolder() { + tryGetCheckerBySpi(); + } + + public static ServerIdentityCheckerHolder getInstance() { + return INSTANCE; + } + + public ServerIdentityChecker getChecker() { + return checker; + } + + private synchronized void tryGetCheckerBySpi() { + Collection checkers = NacosServiceLoader.load(ServerIdentityChecker.class); + if (checkers.isEmpty()) { + checker = new DefaultChecker(); + LOGGER.info("Not found ServerIdentityChecker implementation from SPI, use default."); + return; + } + if (checkers.size() > 1) { + checker = showAllImplementations(checkers); + return; + } + checker = checkers.iterator().next(); + LOGGER.info("Found ServerIdentityChecker implementation {}", checker.getClass().getCanonicalName()); + } + + private ServerIdentityChecker showAllImplementations(Collection checkers) { + ServerIdentityChecker result = checkers.iterator().next(); + for (ServerIdentityChecker each : checkers) { + LOGGER.warn("Found ServerIdentityChecker implementation {}", each.getClass().getCanonicalName()); + } + LOGGER.warn("Found more than one ServerIdentityChecker implementation from SPI, use the first one {}.", + result.getClass().getCanonicalName()); + return result; + } +} diff --git a/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityResult.java b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityResult.java new file mode 100644 index 00000000000..68093d24f07 --- /dev/null +++ b/auth/src/main/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityResult.java @@ -0,0 +1,72 @@ +/* + * 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.auth.serveridentity; + +/** + * Nacos server identity check result. + * + * @author xiweng.yy + */ +public class ServerIdentityResult { + + private final ResultStatus status; + + private final String message; + + private ServerIdentityResult(ResultStatus status, String message) { + this.status = status; + this.message = message; + } + + public ResultStatus getStatus() { + return status; + } + + public String getMessage() { + return message; + } + + public static ServerIdentityResult success() { + return new ServerIdentityResult(ResultStatus.MATCHED, "Server identity matched."); + } + + public static ServerIdentityResult noMatched() { + return new ServerIdentityResult(ResultStatus.NOT_MATCHED, "Server identity not matched."); + } + + public static ServerIdentityResult fail(String message) { + return new ServerIdentityResult(ResultStatus.FAIL, message); + } + + public enum ResultStatus { + + /** + * Nacos server identity matched. + */ + MATCHED, + + /** + * Nacos server identity not matched, need authentication. + */ + NOT_MATCHED, + + /** + * Nacos server identity check failed. + */ + FAIL; + } +} diff --git a/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java b/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java index b9a937d1833..a14a300ffe4 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/GrpcProtocolAuthServiceTest.java @@ -19,12 +19,14 @@ import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest; import com.alibaba.nacos.api.naming.remote.request.AbstractNamingRequest; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.auth.mock.MockAuthPluginService; import com.alibaba.nacos.auth.mock.MockResourceParser; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.plugin.auth.constant.SignType; import com.alibaba.nacos.plugin.auth.exception.AccessException; import org.junit.jupiter.api.BeforeEach; @@ -41,12 +43,13 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) class GrpcProtocolAuthServiceTest { @Mock - private AuthConfigs authConfigs; + private NacosAuthConfig authConfig; private ConfigPublishRequest configRequest; @@ -56,7 +59,7 @@ class GrpcProtocolAuthServiceTest { @BeforeEach void setUp() throws Exception { - protocolAuthService = new GrpcProtocolAuthService(authConfigs); + protocolAuthService = new GrpcProtocolAuthService(authConfig); protocolAuthService.initialize(); mockConfigRequest(); mockNamingRequest(); @@ -144,7 +147,7 @@ void testValidateIdentityWithoutPlugin() throws AccessException { @Test void testValidateIdentityWithPlugin() throws AccessException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + Mockito.when(authConfig.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); IdentityContext identityContext = new IdentityContext(); assertFalse(protocolAuthService.validateIdentity(identityContext, Resource.EMPTY_RESOURCE)); } @@ -157,7 +160,7 @@ void testValidateAuthorityWithoutPlugin() throws AccessException { @Test void testValidateAuthorityWithPlugin() throws AccessException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + Mockito.when(authConfig.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); assertFalse(protocolAuthService.validateAuthority(new IdentityContext(), new Permission(Resource.EMPTY_RESOURCE, ""))); } @@ -165,7 +168,7 @@ void testValidateAuthorityWithPlugin() throws AccessException { @Test @Secured(signType = SignType.CONFIG) void testEnabledAuthWithPlugin() throws NoSuchMethodException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + Mockito.when(authConfig.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); Secured secured = getMethodSecure("testEnabledAuthWithPlugin"); assertTrue(protocolAuthService.enableAuth(secured)); } @@ -173,11 +176,61 @@ void testEnabledAuthWithPlugin() throws NoSuchMethodException { @Test @Secured(signType = SignType.CONFIG) void testEnabledAuthWithoutPlugin() throws NoSuchMethodException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist-plugin"); + Mockito.when(authConfig.getNacosAuthSystemType()).thenReturn("non-exist-plugin"); Secured secured = getMethodSecure("testEnabledAuthWithoutPlugin"); assertFalse(protocolAuthService.enableAuth(secured)); } + @Test + @Secured(apiType = ApiType.INNER_API) + void testCheckServerIdentityWithoutIdentityConfig() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityWithoutIdentityConfig"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + } + + @Test + @Secured(apiType = ApiType.INNER_API) + void testCheckServerIdentityNotMatched() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityNotMatched"); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + namingRequest.putHeader("1", "3"); + result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + } + + @Test + @Secured(apiType = ApiType.INNER_API) + void testCheckServerIdentityMatched() throws NoSuchMethodException { + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + namingRequest.putHeader("1", "2"); + Secured secured = getMethodSecure("testCheckServerIdentityMatched"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.MATCHED, result.getStatus()); + } + + @Test + @Secured + void testCheckServerIdentityForOtherTypeApi() throws NoSuchMethodException { + namingRequest.putHeader("1", "2"); + Secured secured = getMethodSecure("testCheckServerIdentityForOtherTypeApi"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(namingRequest, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + } + private Secured getMethodSecure(String methodName) throws NoSuchMethodException { Method method = GrpcProtocolAuthServiceTest.class.getDeclaredMethod(methodName); return method.getAnnotation(Secured.class); diff --git a/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java b/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java index e5e4ae7045c..d10e214e2a7 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/HttpProtocolAuthServiceTest.java @@ -19,9 +19,10 @@ import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.naming.CommonParams; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.auth.mock.MockAuthPluginService; import com.alibaba.nacos.auth.mock.MockResourceParser; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; @@ -31,7 +32,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; @@ -45,6 +45,7 @@ import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) // todo remove this @@ -52,7 +53,7 @@ class HttpProtocolAuthServiceTest { @Mock - private AuthConfigs authConfigs; + private NacosAuthConfig authConfig; @Mock private HttpServletRequest request; @@ -61,14 +62,14 @@ class HttpProtocolAuthServiceTest { @BeforeEach void setUp() throws Exception { - protocolAuthService = new HttpProtocolAuthService(authConfigs); + protocolAuthService = new HttpProtocolAuthService(authConfig); protocolAuthService.initialize(); - Mockito.when(request.getParameter(eq(CommonParams.NAMESPACE_ID))).thenReturn("testNNs"); - Mockito.when(request.getParameter(eq(CommonParams.GROUP_NAME))).thenReturn("testNG"); - Mockito.when(request.getParameter(eq(CommonParams.SERVICE_NAME))).thenReturn("testS"); - Mockito.when(request.getParameter(eq("tenant"))).thenReturn("testCNs"); - Mockito.when(request.getParameter(eq(Constants.GROUP))).thenReturn("testCG"); - Mockito.when(request.getParameter(eq(Constants.DATA_ID))).thenReturn("testD"); + when(request.getParameter(eq(CommonParams.NAMESPACE_ID))).thenReturn("testNNs"); + when(request.getParameter(eq(CommonParams.GROUP_NAME))).thenReturn("testNG"); + when(request.getParameter(eq(CommonParams.SERVICE_NAME))).thenReturn("testS"); + when(request.getParameter(eq("tenant"))).thenReturn("testCNs"); + when(request.getParameter(eq(Constants.GROUP))).thenReturn("testCG"); + when(request.getParameter(eq(Constants.DATA_ID))).thenReturn("testD"); } @Test @@ -139,7 +140,7 @@ void testValidateIdentityWithoutPlugin() throws AccessException { @Test void testValidateIdentityWithPlugin() throws AccessException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + when(authConfig.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); IdentityContext identityContext = new IdentityContext(); assertFalse(protocolAuthService.validateIdentity(identityContext, Resource.EMPTY_RESOURCE)); } @@ -152,7 +153,7 @@ void testValidateAuthorityWithoutPlugin() throws AccessException { @Test void testValidateAuthorityWithPlugin() throws AccessException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + when(authConfig.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); assertFalse(protocolAuthService.validateAuthority(new IdentityContext(), new Permission(Resource.EMPTY_RESOURCE, ""))); } @@ -160,7 +161,7 @@ void testValidateAuthorityWithPlugin() throws AccessException { @Test @Secured(signType = SignType.CONFIG) void testEnabledAuthWithPlugin() throws NoSuchMethodException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); + when(authConfig.getNacosAuthSystemType()).thenReturn(MockAuthPluginService.TEST_PLUGIN); Secured secured = getMethodSecure("testEnabledAuthWithPlugin"); assertTrue(protocolAuthService.enableAuth(secured)); } @@ -168,11 +169,49 @@ void testEnabledAuthWithPlugin() throws NoSuchMethodException { @Test @Secured(signType = SignType.CONFIG) void testEnabledAuthWithoutPlugin() throws NoSuchMethodException { - Mockito.when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist-plugin"); + when(authConfig.getNacosAuthSystemType()).thenReturn("non-exist-plugin"); Secured secured = getMethodSecure("testEnabledAuthWithoutPlugin"); assertFalse(protocolAuthService.enableAuth(secured)); } + @Test + void testCheckServerIdentityWithoutIdentityConfig() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityWithoutIdentityConfig"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.FAIL, result.getStatus()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + result.getMessage()); + } + + @Test + void testCheckServerIdentityNotMatched() throws NoSuchMethodException { + Secured secured = getMethodSecure("testCheckServerIdentityNotMatched"); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + when(request.getHeader("1")).thenReturn("3"); + result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.NOT_MATCHED, result.getStatus()); + } + + @Test + void testCheckServerIdentityMatched() throws NoSuchMethodException { + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + when(request.getHeader("1")).thenReturn("2"); + Secured secured = getMethodSecure("testCheckServerIdentityMatched"); + ServerIdentityResult result = protocolAuthService.checkServerIdentity(request, secured); + assertEquals(ServerIdentityResult.ResultStatus.MATCHED, result.getStatus()); + } + private Secured getMethodSecure(String methodName) throws NoSuchMethodException { Method method = HttpProtocolAuthServiceTest.class.getDeclaredMethod(methodName); return method.getAnnotation(Secured.class); diff --git a/auth/src/test/java/com/alibaba/nacos/auth/config/AuthConfigsTest.java b/auth/src/test/java/com/alibaba/nacos/auth/config/AuthConfigsTest.java index 1c12c3b5683..7ea2c61bbc1 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/config/AuthConfigsTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/config/AuthConfigsTest.java @@ -22,8 +22,6 @@ import org.junit.jupiter.api.Test; import org.springframework.mock.env.MockEnvironment; -import java.util.Optional; - import static org.junit.jupiter.api.Assertions.assertEquals; class AuthConfigsTest { @@ -36,8 +34,6 @@ class AuthConfigsTest { private static final String TEST_SERVER_IDENTITY_VALUE = "testValue"; - private static final boolean TEST_ENABLE_UA_WHITE = true; - private AuthConfigs authConfigs; private MockEnvironment environment; @@ -56,16 +52,11 @@ void testUpgradeFromEvent() { environment.setProperty("nacos.core.auth.caching.enabled", String.valueOf(TEST_CACHING_ENABLED)); environment.setProperty("nacos.core.auth.server.identity.key", TEST_SERVER_IDENTITY_KEY); environment.setProperty("nacos.core.auth.server.identity.value", TEST_SERVER_IDENTITY_VALUE); - environment.setProperty("nacos.core.auth.enable.userAgentAuthWhite", String.valueOf(TEST_ENABLE_UA_WHITE)); authConfigs.onEvent(ServerConfigChangeEvent.newEvent()); - assertEquals(Optional.of(TEST_AUTH_ENABLED).orElse(Boolean.FALSE), - Optional.of(authConfigs.isAuthEnabled()).orElse(Boolean.FALSE)); - assertEquals(Optional.of(TEST_CACHING_ENABLED).orElse(Boolean.FALSE), - Optional.of(authConfigs.isCachingEnabled()).orElse(Boolean.FALSE)); + assertEquals(TEST_AUTH_ENABLED, authConfigs.isAuthEnabled()); + assertEquals(TEST_CACHING_ENABLED, authConfigs.isCachingEnabled()); assertEquals(TEST_SERVER_IDENTITY_KEY, authConfigs.getServerIdentityKey()); assertEquals(TEST_SERVER_IDENTITY_VALUE, authConfigs.getServerIdentityValue()); - assertEquals(Optional.of(TEST_ENABLE_UA_WHITE).orElse(Boolean.FALSE), - Optional.of(authConfigs.isEnableUserAgentAuthWhite()).orElse(Boolean.FALSE)); } } diff --git a/auth/src/test/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilderTest.java b/auth/src/test/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilderTest.java index 74001e1cb71..ac5a5d93f75 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilderTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/context/GrpcIdentityContextBuilderTest.java @@ -17,7 +17,7 @@ package com.alibaba.nacos.auth.context; import com.alibaba.nacos.api.remote.request.Request; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.constant.Constants; import org.junit.jupiter.api.BeforeEach; @@ -47,7 +47,7 @@ class GrpcIdentityContextBuilderTest { private static final String IDENTITY_TEST_VALUE = "identity-test-value"; @Mock - private AuthConfigs authConfigs; + private NacosAuthConfig authConfig; @Mock private Request request; @@ -56,8 +56,8 @@ class GrpcIdentityContextBuilderTest { @BeforeEach void setUp() throws Exception { - identityContextBuilder = new GrpcIdentityContextBuilder(authConfigs); - when(authConfigs.getNacosAuthSystemType()).thenReturn(TEST_PLUGIN); + identityContextBuilder = new GrpcIdentityContextBuilder(authConfig); + when(authConfig.getNacosAuthSystemType()).thenReturn(TEST_PLUGIN); Map headers = new HashMap<>(); headers.put(IDENTITY_TEST_KEY, IDENTITY_TEST_VALUE); when(request.getHeaders()).thenReturn(headers); @@ -66,7 +66,7 @@ void setUp() throws Exception { @Test void testBuildWithoutPlugin() { - when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist"); + when(authConfig.getNacosAuthSystemType()).thenReturn("non-exist"); IdentityContext actual = identityContextBuilder.build(request); assertNull(actual.getParameter(IDENTITY_TEST_KEY)); } diff --git a/auth/src/test/java/com/alibaba/nacos/auth/context/HtppIdentityContextBuilderTest.java b/auth/src/test/java/com/alibaba/nacos/auth/context/HtppIdentityContextBuilderTest.java index 06abf9d4420..602722f66f8 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/context/HtppIdentityContextBuilderTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/context/HtppIdentityContextBuilderTest.java @@ -16,7 +16,7 @@ package com.alibaba.nacos.auth.context; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.constant.Constants; import org.junit.jupiter.api.BeforeEach; @@ -46,7 +46,7 @@ class HtppIdentityContextBuilderTest { private static final String IDENTITY_TEST_VALUE = "identity-test-value"; @Mock - private AuthConfigs authConfigs; + private NacosAuthConfig authConfig; @Mock private HttpServletRequest request; @@ -61,15 +61,15 @@ class HtppIdentityContextBuilderTest { @BeforeEach void setUp() throws Exception { - identityContextBuilder = new HttpIdentityContextBuilder(authConfigs); - when(authConfigs.getNacosAuthSystemType()).thenReturn(TEST_PLUGIN); + identityContextBuilder = new HttpIdentityContextBuilder(authConfig); + when(authConfig.getNacosAuthSystemType()).thenReturn(TEST_PLUGIN); } @Test void testBuildWithoutPlugin() { mockHeader(true); mockParameter(true); - when(authConfigs.getNacosAuthSystemType()).thenReturn("non-exist"); + when(authConfig.getNacosAuthSystemType()).thenReturn("non-exist"); IdentityContext actual = identityContextBuilder.build(request); assertNull(actual.getParameter(IDENTITY_TEST_KEY)); } diff --git a/auth/src/test/java/com/alibaba/nacos/auth/parser/http/ConfigHttpResourceParserTest.java b/auth/src/test/java/com/alibaba/nacos/auth/parser/http/ConfigHttpResourceParserTest.java index 6f8c697697c..6043ba7534b 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/parser/http/ConfigHttpResourceParserTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/parser/http/ConfigHttpResourceParserTest.java @@ -100,7 +100,7 @@ void testParseWithoutNamespace() throws NoSuchMethodException { Mockito.when(request.getParameter(eq(Constants.GROUP))).thenReturn("testG"); Mockito.when(request.getParameter(eq(Constants.DATA_ID))).thenReturn("testD"); Resource actual = resourceParser.parse(request, secured); - assertEquals(StringUtils.EMPTY, actual.getNamespaceId()); + assertEquals(Constants.DEFAULT_NAMESPACE_ID, actual.getNamespaceId()); assertEquals("testG", actual.getGroup()); assertEquals("testD", actual.getName()); assertEquals(Constants.Config.CONFIG_MODULE, actual.getType()); diff --git a/auth/src/test/java/com/alibaba/nacos/auth/parser/http/NamingHttpResourceParserTest.java b/auth/src/test/java/com/alibaba/nacos/auth/parser/http/NamingHttpResourceParserTest.java index 89adf0ddc87..86b3398bed8 100644 --- a/auth/src/test/java/com/alibaba/nacos/auth/parser/http/NamingHttpResourceParserTest.java +++ b/auth/src/test/java/com/alibaba/nacos/auth/parser/http/NamingHttpResourceParserTest.java @@ -73,7 +73,7 @@ void testParseWithoutNamespace() throws NoSuchMethodException { Mockito.when(request.getParameter(eq(CommonParams.GROUP_NAME))).thenReturn("testG"); Mockito.when(request.getParameter(eq(CommonParams.SERVICE_NAME))).thenReturn("testS"); Resource actual = resourceParser.parse(request, secured); - assertEquals(StringUtils.EMPTY, actual.getNamespaceId()); + assertEquals(Constants.DEFAULT_NAMESPACE_ID, actual.getNamespaceId()); assertEquals("testG", actual.getGroup()); assertEquals("testS", actual.getName()); assertEquals(Constants.Naming.NAMING_MODULE, actual.getType()); diff --git a/auth/src/test/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolderTest.java b/auth/src/test/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolderTest.java new file mode 100644 index 00000000000..6b5646c21bd --- /dev/null +++ b/auth/src/test/java/com/alibaba/nacos/auth/serveridentity/ServerIdentityCheckerHolderTest.java @@ -0,0 +1,88 @@ +/* + * 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.auth.serveridentity; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.NacosAuthConfig; +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.test.util.ReflectionTestUtils; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Collection; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertInstanceOf; + +class ServerIdentityCheckerHolderTest { + + Map, Collection>> servicesMap; + + @BeforeEach + void setUp() { + servicesMap = (Map, Collection>>) ReflectionTestUtils.getField(NacosServiceLoader.class, + "SERVICES"); + } + + @AfterEach + void tearDown() { + servicesMap.remove(ServerIdentityChecker.class); + } + + @Test + void testConstructorWithSingleImplementation() + throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + ServerIdentityCheckerHolder holder = getNewHolder(1); + assertInstanceOf(MockChecker.class, holder.getChecker()); + } + + @Test + void testConstructorWithMultipleImplementation() + throws InvocationTargetException, NoSuchMethodException, InstantiationException, IllegalAccessException { + ServerIdentityCheckerHolder holder = getNewHolder(2); + assertInstanceOf(MockChecker.class, holder.getChecker()); + } + + ServerIdentityCheckerHolder getNewHolder(int size) + throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + List> classes = new LinkedList<>(); + for (int i = 0; i < size; i++) { + classes.add(MockChecker.class); + } + servicesMap.put(ServerIdentityChecker.class, classes); + Constructor constructor = ServerIdentityCheckerHolder.class.getDeclaredConstructor(); + constructor.setAccessible(true); + return constructor.newInstance(); + } + + public static class MockChecker implements ServerIdentityChecker { + + @Override + public void init(NacosAuthConfig authConfig) { + } + + @Override + public ServerIdentityResult check(ServerIdentity serverIdentity, Secured secured) { + return ServerIdentityResult.success(); + } + } +} \ No newline at end of file diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml new file mode 100644 index 00000000000..691812b08d9 --- /dev/null +++ b/bootstrap/pom.xml @@ -0,0 +1,79 @@ + + + + + 4.0.0 + + com.alibaba.nacos + nacos-all + ${revision} + ../pom.xml + + + nacos-bootstrap + nacos-bootstrap ${project.version} + + + + com.alibaba.nacos + nacos-console + + + com.alibaba.nacos + nacos-server + + + + + + release-nacos + + nacos-server + + + maven-jar-plugin + + + + true + true + + + + + + org.springframework.boot + spring-boot-maven-plugin + + com.alibaba.nacos.bootstrap.NacosBootstrap + ZIP + + + + + repackage + + + + + + + + + \ No newline at end of file diff --git a/bootstrap/src/main/java/com/alibaba/nacos/bootstrap/NacosBootstrap.java b/bootstrap/src/main/java/com/alibaba/nacos/bootstrap/NacosBootstrap.java new file mode 100644 index 00000000000..994690e6e28 --- /dev/null +++ b/bootstrap/src/main/java/com/alibaba/nacos/bootstrap/NacosBootstrap.java @@ -0,0 +1,98 @@ +/* + * 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.bootstrap; + +import com.alibaba.nacos.NacosServerBasicApplication; +import com.alibaba.nacos.NacosServerWebApplication; +import com.alibaba.nacos.console.NacosConsole; +import com.alibaba.nacos.core.listener.startup.NacosStartUp; +import com.alibaba.nacos.core.listener.startup.NacosStartUpManager; +import com.alibaba.nacos.sys.env.Constants; +import org.springframework.boot.Banner; +import org.springframework.boot.ResourceBanner; +import org.springframework.boot.WebApplicationType; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.builder.SpringApplicationBuilder; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.io.ClassPathResource; +import org.springframework.jmx.export.MBeanExporter; +import org.springframework.jmx.support.RegistrationPolicy; + +/** + * Nacos bootstrap class. + * + * @author xiweng.yy + */ +@SpringBootApplication +public class NacosBootstrap { + + private static final String SPRING_JXM_ENABLED = "spring.jmx.enabled"; + + public static void main(String[] args) { + ConfigurableApplicationContext coreContext = startCoreContext(args); + String type = prepareCoreContext(coreContext); + if (Constants.NACOS_DEPLOYMENT_TYPE_MERGED.equals(type)) { + startWithConsole(args, coreContext); + } else if (Constants.NACOS_DEPLOYMENT_TYPE_SERVER.equals(type)) { + startWithoutConsole(args, coreContext); + } else { + throw new IllegalArgumentException("Unsupported type " + type); + } + } + + private static String prepareCoreContext(ConfigurableApplicationContext coreContext) { + if (coreContext.getEnvironment().getProperty(SPRING_JXM_ENABLED, Boolean.class, false)) { + // Avoid duplicate registration MBean to exporter. + coreContext.getBean(MBeanExporter.class).setRegistrationPolicy(RegistrationPolicy.IGNORE_EXISTING); + } + return coreContext.getEnvironment() + .getProperty(Constants.NACOS_DEPLOYMENT_TYPE, Constants.NACOS_DEPLOYMENT_TYPE_MERGED); + } + + private static void startWithoutConsole(String[] args, ConfigurableApplicationContext coreContext) { + ConfigurableApplicationContext webContext = startServerWebContext(args, coreContext); + } + + private static void startWithConsole(String[] args, ConfigurableApplicationContext coreContext) { + ConfigurableApplicationContext serverWebContext = startServerWebContext(args, coreContext); + ConfigurableApplicationContext consoleContext = startConsoleContext(args, coreContext); + } + + private static ConfigurableApplicationContext startServerWebContext(String[] args, + ConfigurableApplicationContext coreContext) { + NacosStartUpManager.start(NacosStartUp.WEB_START_UP_PHASE); + return new SpringApplicationBuilder(NacosServerWebApplication.class).parent(coreContext) + .banner(getBanner("nacos-server-web-banner.txt")).run(args); + } + + private static ConfigurableApplicationContext startConsoleContext(String[] args, + ConfigurableApplicationContext coreContext) { + NacosStartUpManager.start(NacosStartUp.CONSOLE_START_UP_PHASE); + return new SpringApplicationBuilder(NacosConsole.class).parent(coreContext) + .banner(getBanner("nacos-console-banner.txt")).run(args); + } + + private static ConfigurableApplicationContext startCoreContext(String[] args) { + NacosStartUpManager.start(NacosStartUp.CORE_START_UP_PHASE); + return new SpringApplicationBuilder(NacosServerBasicApplication.class).web(WebApplicationType.NONE) + .banner(getBanner("core-banner.txt")).run(args); + } + + private static Banner getBanner(String bannerFileName) { + return new ResourceBanner(new ClassPathResource(bannerFileName)); + } +} \ No newline at end of file diff --git a/distribution/conf/application.properties.example b/bootstrap/src/main/resources/application.properties similarity index 59% rename from distribution/conf/application.properties.example rename to bootstrap/src/main/resources/application.properties index 453630e9c57..e45a7a015d9 100644 --- a/distribution/conf/application.properties.example +++ b/bootstrap/src/main/resources/application.properties @@ -1,5 +1,5 @@ # -# Copyright 1999-2021 Alibaba Group Holding Ltd. +# Copyright 1999-2025 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. @@ -14,13 +14,11 @@ # limitations under the License. # -#*************** Spring Boot Related Configurations ***************# -### Default web context path: -server.servlet.contextPath=/nacos -### Include message field -server.error.include-message=ALWAYS -### Default web server port: -server.port=8848 +#--------------- Nacos Common Configurations ---------------# + +#*************** Nacos port Related Configurations ***************# +### Nacos Server Main port +nacos.server.main.port=8848 #*************** Network Related Configurations ***************# ### If prefer hostname over ip for Nacos server addresses in cluster.conf: @@ -29,85 +27,20 @@ server.port=8848 ### Specify local server's IP: # nacos.inetutils.ip-address= - -#*************** Config Module Related Configurations ***************# -### If use MySQL as datasource: -### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced. -# spring.datasource.platform=mysql -# spring.sql.init.platform=mysql - +#*************** Datasource Related Configurations ***************# +### nacos.plugin.datasource.log.enabled=true +#spring.sql.init.platform=mysql ### Count of DB: # db.num=1 ### Connect URL of DB: # db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC -# db.user.0=nacos -# db.password.0=nacos - -### Connection pool configuration: hikariCP -db.pool.config.connectionTimeout=30000 -db.pool.config.validationTimeout=10000 -db.pool.config.maximumPoolSize=20 -db.pool.config.minimumIdle=2 - -### the maximum retry times for push -nacos.config.push.maxRetryTime=50 - -#*************** Naming Module Related Configurations ***************# -### If enable data warmup. If set to false, the server would accept request without local data preparation: -# nacos.naming.data.warmup=true - -### If enable the instance auto expiration, kind like of health check of instance: -# nacos.naming.expireInstance=true - -### will be removed and replaced by `nacos.naming.clean` properties -nacos.naming.empty-service.auto-clean=true -nacos.naming.empty-service.clean.initial-delay-ms=50000 -nacos.naming.empty-service.clean.period-time-ms=30000 - -### Add in 2.0.0 -### The interval to clean empty service, unit: milliseconds. -# nacos.naming.clean.empty-service.interval=60000 - -### The expired time to clean empty service, unit: milliseconds. -# nacos.naming.clean.empty-service.expired-time=60000 - -### The interval to clean expired metadata, unit: milliseconds. -# nacos.naming.clean.expired-metadata.interval=5000 - -### The expired time to clean metadata, unit: milliseconds. -# nacos.naming.clean.expired-metadata.expired-time=60000 - -### The delay time before push task to execute from service changed, unit: milliseconds. -# nacos.naming.push.pushTaskDelay=500 - -### The timeout for push task execute, unit: milliseconds. -# nacos.naming.push.pushTaskTimeout=5000 - -### The delay time for retrying failed push task, unit: milliseconds. -# nacos.naming.push.pushTaskRetryDelay=1000 - -### Since 2.0.3 -### The expired time for inactive client, unit: milliseconds. -# nacos.naming.client.expired.time=180000 - -#*************** CMDB Module Related Configurations ***************# -### The interval to dump external CMDB in seconds: -# nacos.cmdb.dumpTaskInterval=3600 - -### The interval of polling data change event in seconds: -# nacos.cmdb.eventTaskInterval=10 - -### The interval of loading labels in seconds: -# nacos.cmdb.labelTaskInterval=300 - -### If turn on data loading task: -# nacos.cmdb.loadDataAtStart=false - +# db.user=nacos +# db.password=nacos #*************** Metrics Related Configurations ***************# ### Metrics for prometheus -#management.endpoints.web.exposure.include=* +#management.endpoints.web.exposure.include=prometheus ### Metrics for elastic search management.metrics.export.elastic.enabled=false @@ -121,63 +54,6 @@ management.metrics.export.influx.enabled=false #management.metrics.export.influx.consistency=one #management.metrics.export.influx.compressed=true -#*************** Access Log Related Configurations ***************# -### If turn on the access log: -server.tomcat.accesslog.enabled=true - -### The access log pattern: -server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i - -### The directory of access log: -server.tomcat.basedir=file:. - -#*************** Access Control Related Configurations ***************# -### If enable spring security, this option is deprecated in 1.2.0: -#spring.security.enabled=false - -### The ignore urls of auth, is deprecated in 1.2.0: -nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** - -### The auth system to use, currently only 'nacos' and 'ldap' is supported: -nacos.core.auth.system.type=nacos - -### If turn on auth system: -nacos.core.auth.enabled=false - -### worked when nacos.core.auth.system.type=ldap,{0} is Placeholder,replace login username -#nacos.core.auth.system.type=ldap -#nacos.core.auth.ldap.url=ldap://localhost:389 -#nacos.core.auth.ldap.basedc=dc=example,dc=org -#nacos.core.auth.ldap.userDn=cn=admin,${nacos.core.auth.ldap.basedc} -#nacos.core.auth.ldap.password=admin -#nacos.core.auth.ldap.userdn=cn={0},dc=example,dc=org -#nacos.core.auth.ldap.filter.prefix=uid -#nacos.core.auth.ldap.case.sensitive=true -#nacos.core.auth.ldap.ignore.partial.result.exception=false - - -### worked when nacos.core.auth.system.type=nacos -### The token expiration in seconds: -nacos.core.auth.plugin.nacos.token.cache.enable=false -nacos.core.auth.plugin.nacos.token.expire.seconds=18000 -### The default token (Base64 String): -nacos.core.auth.plugin.nacos.token.secret.key= - -### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. -nacos.core.auth.caching.enabled=true - -### Since 1.4.1, Turn on/off white auth for user-agent: nacos-server, only for upgrade from old version. -nacos.core.auth.enable.userAgentAuthWhite=false - -### Since 1.4.1, worked when nacos.core.auth.enabled=true and nacos.core.auth.enable.userAgentAuthWhite=false. -### The two properties is the white list for auth and used by identity the request from other server. -#nacos.core.auth.server.identity.key=example -#nacos.core.auth.server.identity.value=example - -#*************** Istio Related Configurations ***************# -### If turn on the MCP server: -nacos.istio.mcp.server.enabled=false - #*************** Core Related Configurations ***************# ### set the WorkerID manually @@ -191,8 +67,10 @@ nacos.istio.mcp.server.enabled=false ### MemberLookup ### Addressing pattern category, If set, the priority is highest # nacos.core.member.lookup.type=[file,address-server] + ## Set the cluster list with a configuration file or command-line argument # nacos.member.list=192.168.16.101:8847?raft_port=8807,192.168.16.101?raft_port=8808,192.168.16.101:8849?raft_port=8809 + ## for AddressServerMemberLookup # Maximum number of retries to query the address server upon initialization # nacos.core.address-server.retry=5 @@ -217,55 +95,200 @@ nacos.istio.mcp.server.enabled=false # nacos.core.protocol.raft.data.read_index_type=ReadOnlySafe ### rpc request timeout, default 5 seconds # nacos.core.protocol.raft.data.rpc_request_timeout_ms=5000 +### enable to support prometheus service discovery +#nacos.prometheus.metrics.enabled=true #*************** Distro Related Configurations ***************# ### Distro data sync delay time, when sync task delayed, task will be merged for same data key. Default 1 second. # nacos.core.protocol.distro.data.sync.delayMs=1000 - ### Distro data sync timeout for one sync data, default 3 seconds. # nacos.core.protocol.distro.data.sync.timeoutMs=3000 - ### Distro data sync retry delay time when sync data failed or timeout, same behavior with delayMs, default 3 seconds. # nacos.core.protocol.distro.data.sync.retryDelayMs=3000 - ### Distro data verify interval time, verify synced data whether expired for a interval. Default 5 seconds. # nacos.core.protocol.distro.data.verify.intervalMs=5000 - ### Distro data verify timeout for one verify, default 3 seconds. # nacos.core.protocol.distro.data.verify.timeoutMs=3000 - ### Distro data load retry delay when load snapshot data failed, default 30 seconds. # nacos.core.protocol.distro.data.load.retryDelayMs=30000 - ### enable to support prometheus service discovery #nacos.prometheus.metrics.enabled=true -### Since 2.3 #*************** Grpc Configurations ***************# -## sdk grpc(between nacos server and client) configuration -## Sets the maximum message size allowed to be received on the server. +### Sets the maximum message size allowed to be received on the server. #nacos.remote.server.grpc.sdk.max-inbound-message-size=10485760 - -## Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. +### Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. #nacos.remote.server.grpc.sdk.keep-alive-time=7200000 - -## Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. +### Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. #nacos.remote.server.grpc.sdk.keep-alive-timeout=20000 +### Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes +#nacos.remote.server.grpc.sdk.permit-keep-alive-time=300000 +### cluster grpc(inside the nacos server) configuration +#nacos.remote.server.grpc.cluster.max-inbound-message-size=10485760 +### Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. +#nacos.remote.server.grpc.cluster.keep-alive-time=7200000 +### Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. +#nacos.remote.server.grpc.cluster.keep-alive-timeout=20000 +### Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes +#nacos.remote.server.grpc.cluster.permit-keep-alive-time=300000 +#*************** Config Module Related Configurations ***************# -## Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes -#nacos.remote.server.grpc.sdk.permit-keep-alive-time=300000 +### the maximum retry times for push +nacos.config.push.maxRetryTime=50 -## cluster grpc(inside the nacos server) configuration -#nacos.remote.server.grpc.cluster.max-inbound-message-size=10485760 +#*************** Naming Module Related Configurations ***************# +### Data dispatch task execution period in milliseconds: -## Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. -#nacos.remote.server.grpc.cluster.keep-alive-time=7200000 +### If enable data warmup. If set to false, the server would accept request without local data preparation: +# nacos.naming.data.warmup=true -## Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. -#nacos.remote.server.grpc.cluster.keep-alive-timeout=20000 +### If enable the instance auto expiration, kind like of health check of instance: +# nacos.naming.expireInstance=true + +nacos.naming.empty-service.auto-clean=true +nacos.naming.empty-service.clean.initial-delay-ms=50000 +nacos.naming.empty-service.clean.period-time-ms=30000 + +#--------------- Nacos Web Server Configurations ---------------# + +#*************** Nacos Web Server Related Configurations ***************# +### Nacos Server Web context path: +nacos.server.contextPath=/nacos + +#*************** Access Log Related Configurations ***************# +### If turn on the access log: +server.tomcat.accesslog.enabled=true + +### accesslog automatic cleaning time +server.tomcat.accesslog.max-days=30 + +### The access log pattern: +server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i + +### The directory of access log: +server.tomcat.basedir=file:. + +#*************** API Related Configurations ***************# +### Include message field +server.error.include-message=ALWAYS + +### Enabled for open API compatibility +# nacos.core.api.compatibility.client.enabled=true +### Enabled for admin API compatibility +# nacos.core.api.compatibility.admin.enabled=true +### Enabled for console API compatibility +# nacos.core.api.compatibility.console.enabled=false + +#--------------- Nacos Console Configurations ---------------# + +#*************** Nacos Console Related Configurations ***************# +### Nacos Console Main port +nacos.console.port=8080 +### Nacos Server Web context path: +nacos.console.contextPath= + +#************** Console UI Configuration ***************# + +### Turn on/off the nacos console ui. +#nacos.console.ui.enabled=true + +#--------------- Nacos Plugin Configurations ---------------# + +#*************** CMDB Plugin Related Configurations ***************# +### The interval to dump external CMDB in seconds: +# nacos.cmdb.dumpTaskInterval=3600 + +### The interval of polling data change event in seconds: +# nacos.cmdb.eventTaskInterval=10 + +### The interval of loading labels in seconds: +# nacos.cmdb.labelTaskInterval=300 + +### If turn on data loading task: +# nacos.cmdb.loadDataAtStart=false + +#*************** Auth Plugin Related Configurations ***************# +### The ignore urls of auth, will be deprecated in the future: +nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** + +### The auth system to use, default 'nacos' and 'ldap' is supported, other type should be implemented by yourself: +nacos.core.auth.system.type=nacos + +### If turn on auth system: +# Whether open nacos server API auth system +nacos.core.auth.enabled=false +# Whether open nacos console API auth system +nacos.core.auth.console.enabled=true + +### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. +nacos.core.auth.caching.enabled=true + +### worked when nacos.core.auth.enabled=true +### The two properties is the white list for auth and used by identity the request from other server. +nacos.core.auth.server.identity.key= +nacos.core.auth.server.identity.value= + +### worked when nacos.core.auth.system.type=nacos or nacos.core.auth.console.enabled=true +### The token expiration in seconds: +nacos.core.auth.plugin.nacos.token.cache.enable=false +nacos.core.auth.plugin.nacos.token.expire.seconds=18000 +### The default token (Base64 string): +#nacos.core.auth.plugin.nacos.token.secret.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg= +nacos.core.auth.plugin.nacos.token.secret.key= + +### worked when nacos.core.auth.system.type=ldap,{0} is Placeholder,replace login username +#nacos.core.auth.ldap.url=ldap://localhost:389 +#nacos.core.auth.ldap.basedc=dc=example,dc=org +#nacos.core.auth.ldap.userDn=cn=admin,${nacos.core.auth.ldap.basedc} +#nacos.core.auth.ldap.password=admin +#nacos.core.auth.ldap.userdn=cn={0},dc=example,dc=org +#nacos.core.auth.ldap.filter.prefix=uid +#nacos.core.auth.ldap.case.sensitive=true +#nacos.core.auth.ldap.ignore.partial.result.exception=false + +#*************** Control Plugin Related Configurations ***************# +# plugin type +#nacos.plugin.control.manager.type=nacos + +# local control rule storage dir, default ${nacos.home}/data/connection and ${nacos.home}/data/tps +#nacos.plugin.control.rule.local.basedir=${nacos.home} + +# external control rule storage type, if exist +#nacos.plugin.control.rule.external.storage= + +#*************** Config Change Plugin Related Configurations ***************# +# webhook +#nacos.core.config.plugin.webhook.enabled=false +# It is recommended to use EB https://help.aliyun.com/document_detail/413974.html +#nacos.core.config.plugin.webhook.url=http://localhost:8080/webhook/send?token=*** +# The content push max capacity ,byte +#nacos.core.config.plugin.webhook.contentMaxCapacity=102400 + +# whitelist +#nacos.core.config.plugin.whitelist.enabled=false +# The import file suffixs +#nacos.core.config.plugin.whitelist.suffixs=xml,text,properties,yaml,html +# fileformatcheck,which validate the import file of type and content +#nacos.core.config.plugin.fileformatcheck.enabled=false + +#*************** Istio Plugin Related Configurations ***************# +### If turn on the MCP server: +nacos.istio.mcp.server.enabled=false + +#--------------- Nacos Experimental Features Configurations ---------------# + +#*************** K8s Related Configurations ***************# +### If turn on the K8s sync: +nacos.k8s.sync.enabled=false + +### If use the Java API from an application outside a kubernetes cluster +#nacos.k8s.sync.outsideCluster=false +#nacos.k8s.sync.kubeConfig=/.kube/config + +#*************** Deployment Type Configuration ***************# -## Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes -#nacos.remote.server.grpc.cluster.permit-keep-alive-time=300000 \ No newline at end of file +### Sets the deployment type: 'merged' for joint deployment, 'server' for separate deployment server only, 'console' for separate deployment console only. +nacos.deployment.type=merged diff --git a/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosAuthLoginConstant.java b/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosAuthLoginConstant.java index df1d6369cba..aa38a97e70a 100644 --- a/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosAuthLoginConstant.java +++ b/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosAuthLoginConstant.java @@ -37,5 +37,5 @@ public class NacosAuthLoginConstant { public static final String SERVER = "server"; - + public static final String RELOGINFLAG = "reLoginFlag"; } diff --git a/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImpl.java b/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImpl.java index c374c1a9b77..eb3b41a2583 100644 --- a/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImpl.java +++ b/client/src/main/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImpl.java @@ -59,6 +59,10 @@ public class NacosClientAuthServiceImpl extends AbstractClientAuthService { */ private volatile LoginIdentityContext loginIdentityContext = new LoginIdentityContext(); + /** + * Re-login window in milliseconds. + */ + private final long reLoginWindow = 60000; /** * Login to servers. @@ -69,9 +73,16 @@ public class NacosClientAuthServiceImpl extends AbstractClientAuthService { @Override public Boolean login(Properties properties) { try { - if ((System.currentTimeMillis() - lastRefreshTime) < TimeUnit.SECONDS - .toMillis(tokenTtl - tokenRefreshWindow)) { - return true; + boolean reLoginFlag = Boolean.parseBoolean(loginIdentityContext.getParameter(NacosAuthLoginConstant.RELOGINFLAG, "false")); + if (reLoginFlag) { + if ((System.currentTimeMillis() - lastRefreshTime) < reLoginWindow) { + return true; + } + } else { + if ((System.currentTimeMillis() - lastRefreshTime) < TimeUnit.SECONDS + .toMillis(tokenTtl - tokenRefreshWindow)) { + return true; + } } if (StringUtils.isBlank(properties.getProperty(PropertyKeyConst.USERNAME))) { diff --git a/client/src/main/java/com/alibaba/nacos/client/auth/ram/RamClientAuthServiceImpl.java b/client/src/main/java/com/alibaba/nacos/client/auth/ram/RamClientAuthServiceImpl.java index e66565d084a..fcb523ed73f 100644 --- a/client/src/main/java/com/alibaba/nacos/client/auth/ram/RamClientAuthServiceImpl.java +++ b/client/src/main/java/com/alibaba/nacos/client/auth/ram/RamClientAuthServiceImpl.java @@ -21,6 +21,7 @@ import com.alibaba.nacos.client.auth.ram.identify.StsConfig; import com.alibaba.nacos.client.auth.ram.injector.AbstractResourceInjector; import com.alibaba.nacos.client.auth.ram.injector.ConfigResourceInjector; +import com.alibaba.nacos.client.auth.ram.injector.LockResourceInjector; import com.alibaba.nacos.client.auth.ram.injector.NamingResourceInjector; import com.alibaba.nacos.client.auth.ram.utils.RamUtil; import com.alibaba.nacos.client.auth.ram.utils.SpasAdapter; @@ -54,6 +55,7 @@ public RamClientAuthServiceImpl() { resourceInjectors = new HashMap<>(); resourceInjectors.put(SignType.NAMING, new NamingResourceInjector()); resourceInjectors.put(SignType.CONFIG, new ConfigResourceInjector()); + resourceInjectors.put(SignType.LOCK, new LockResourceInjector()); } @Override diff --git a/client/src/main/java/com/alibaba/nacos/client/auth/ram/injector/LockResourceInjector.java b/client/src/main/java/com/alibaba/nacos/client/auth/ram/injector/LockResourceInjector.java new file mode 100644 index 00000000000..82745d7ac58 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/auth/ram/injector/LockResourceInjector.java @@ -0,0 +1,54 @@ +/* + * 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.client.auth.ram.injector; + +import com.alibaba.nacos.client.auth.ram.RamContext; +import com.alibaba.nacos.client.auth.ram.identify.IdentifyConstants; +import com.alibaba.nacos.client.auth.ram.identify.StsConfig; +import com.alibaba.nacos.client.auth.ram.identify.StsCredential; +import com.alibaba.nacos.client.auth.ram.identify.StsCredentialHolder; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext; +import com.alibaba.nacos.plugin.auth.api.RequestResource; + +/** + * lock resource injector. + * + * @author 985492783@qq.com + * @date 2023/9/17 1:10 + */ +public class LockResourceInjector extends AbstractResourceInjector { + + private static final String AK_FIELD = "ak"; + + @Override + public void doInject(RequestResource resource, RamContext context, LoginIdentityContext result) { + String accessKey = context.getAccessKey(); + String secretKey = context.getSecretKey(); + // STS 临时凭证鉴权的优先级高于 AK/SK 鉴权 + if (StsConfig.getInstance().isStsOn()) { + StsCredential stsCredential = StsCredentialHolder.getInstance().getStsCredential(); + accessKey = stsCredential.getAccessKeyId(); + secretKey = stsCredential.getAccessKeySecret(); + result.setParameter(IdentifyConstants.SECURITY_TOKEN_HEADER, stsCredential.getSecurityToken()); + } + + if (StringUtils.isNotEmpty(accessKey) && StringUtils.isNotBlank(secretKey)) { + result.setParameter(AK_FIELD, accessKey); + } + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java index bf5099e6009..a8629ecb0dc 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ClientWorker.java @@ -1221,10 +1221,17 @@ private Response requestProxy(RpcClient rpcClientInner, Request request, long ti throw new NacosException(NacosException.CLIENT_OVER_THRESHOLD, "More than client-side current limit threshold"); } + Response response; if (timeoutMills < 0) { - return rpcClientInner.request(request); + response = rpcClientInner.request(request); + } else { + response = rpcClientInner.request(request, timeoutMills); + } + // If the 403 login operation is triggered, refresh the accessToken of the client + if (response.getErrorCode() == ConfigQueryResponse.NO_RIGHT) { + reLogin(); } - return rpcClientInner.request(request, timeoutMills); + return response; } private RequestResource resourceBuild(Request request) { diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java index 86de1116312..93b4287fe59 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigHttpClientManager.java @@ -71,14 +71,14 @@ public static ConfigHttpClientManager getInstance() { @Override public void shutdown() throws NacosException { - NAMING_LOGGER.warn("[ConfigHttpClientManager] Start destroying NacosRestTemplate"); + NAMING_LOGGER.info("[ConfigHttpClientManager] Start destroying NacosRestTemplate"); try { HttpClientBeanHolder.shutdownNacosSyncRest(HTTP_CLIENT_FACTORY.getClass().getName()); } catch (Exception ex) { NAMING_LOGGER.error("[ConfigHttpClientManager] An exception occurred when the HTTP client was closed : {}", ExceptionUtil.getStackTrace(ex)); } - NAMING_LOGGER.warn("[ConfigHttpClientManager] Destruction of the end"); + NAMING_LOGGER.info("[ConfigHttpClientManager] Completed destruction of NacosRestTemplate"); } /** diff --git a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java index 60d321f1620..1bd5a8e10c5 100644 --- a/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java +++ b/client/src/main/java/com/alibaba/nacos/client/config/impl/ConfigTransportClient.java @@ -79,7 +79,7 @@ public ConfigTransportClient(NacosClientProperties properties, ConfigServerListM this.tenant = properties.getProperty(PropertyKeyConst.NAMESPACE); this.serverListManager = serverListManager; this.properties = properties.asProperties(); - this.securityProxy = new SecurityProxy(serverListManager.getServerList(), + this.securityProxy = new SecurityProxy(serverListManager, ConfigHttpClientManager.getInstance().getNacosRestTemplate()); } @@ -136,6 +136,10 @@ public void start() throws NacosException { startInternal(); } + public void reLogin() { + securityProxy.reLogin(); + } + /** * start client inner. * diff --git a/client/src/main/java/com/alibaba/nacos/client/lock/NacosLockService.java b/client/src/main/java/com/alibaba/nacos/client/lock/NacosLockService.java new file mode 100644 index 00000000000..fcaf42f3073 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/lock/NacosLockService.java @@ -0,0 +1,99 @@ +/* + * 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.client.lock; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.LockService; +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.client.address.AbstractServerListManager; +import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.client.lock.remote.grpc.LockGrpcClient; +import com.alibaba.nacos.client.naming.core.NamingServerListManager; +import com.alibaba.nacos.client.naming.remote.http.NamingHttpClientManager; +import com.alibaba.nacos.client.security.SecurityProxy; + +import java.util.Properties; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.client.constant.Constants.Security.SECURITY_INFO_REFRESH_INTERVAL_MILLS; + +/** + * nacos lock Service. + * + * @author 985492783@qq.com + * @date 2023/8/24 19:51 + */ +@SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule") +public class NacosLockService implements LockService { + + private final Properties properties; + + private final LockGrpcClient lockGrpcClient; + + private final SecurityProxy securityProxy; + + private ScheduledExecutorService executorService; + + public NacosLockService(Properties properties) throws NacosException { + this.properties = properties; + NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(properties); + AbstractServerListManager serverListManager = new NamingServerListManager(properties); + this.securityProxy = new SecurityProxy(serverListManager, + NamingHttpClientManager.getInstance().getNacosRestTemplate()); + initSecurityProxy(nacosClientProperties); + this.lockGrpcClient = new LockGrpcClient(nacosClientProperties, serverListManager, securityProxy); + } + + private void initSecurityProxy(NacosClientProperties properties) { + this.executorService = new ScheduledThreadPoolExecutor(1, r -> { + Thread t = new Thread(r); + t.setName("com.alibaba.nacos.client.lock.security"); + t.setDaemon(true); + return t; + }); + final Properties nacosClientPropertiesView = properties.asProperties(); + this.securityProxy.login(nacosClientPropertiesView); + this.executorService.scheduleWithFixedDelay(() -> securityProxy.login(nacosClientPropertiesView), 0, + SECURITY_INFO_REFRESH_INTERVAL_MILLS, TimeUnit.MILLISECONDS); + } + + @Override + public Boolean lock(LockInstance instance) throws NacosException { + return instance.lock(this); + } + + @Override + public Boolean unLock(LockInstance instance) throws NacosException { + return instance.unLock(this); + } + + @Override + public Boolean remoteTryLock(LockInstance instance) throws NacosException { + return lockGrpcClient.lock(instance); + } + + @Override + public Boolean remoteReleaseLock(LockInstance instance) throws NacosException { + return lockGrpcClient.unLock(instance); + } + + public Properties getProperties() { + return properties; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/lock/core/NLock.java b/client/src/main/java/com/alibaba/nacos/client/lock/core/NLock.java new file mode 100644 index 00000000000..3582cbb6eee --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/lock/core/NLock.java @@ -0,0 +1,39 @@ +/* + * 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.client.lock.core; + +import com.alibaba.nacos.api.lock.common.LockConstants; +import com.alibaba.nacos.api.lock.model.LockInstance; + +/** + * Nacos client lock entity. + * + * @author 985492783@qq.com + * @date 2023/8/24 19:52 + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class NLock extends LockInstance { + + private static final long serialVersionUID = -346054842454875524L; + + public NLock() { + } + + public NLock(String key, Long expireTimestamp) { + super(key, expireTimestamp, LockConstants.NACOS_LOCK_TYPE); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/lock/core/NLockFactory.java b/client/src/main/java/com/alibaba/nacos/client/lock/core/NLockFactory.java new file mode 100644 index 00000000000..188259fb351 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/lock/core/NLockFactory.java @@ -0,0 +1,47 @@ +/* + * 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.client.lock.core; + +/** + * NLock factory. + * + * @author 985492783@qq.com + * @date 2023/8/27 15:23 + */ +@SuppressWarnings("PMD.ClassNamingShouldBeCamelRule") +public class NLockFactory { + + /** + * create NLock without expireTime. + * + * @param key key + * @return NLock + */ + public static NLock getLock(String key) { + return new NLock(key, -1L); + } + + /** + * create NLock with expireTime. + * + * @param key key + * @return NLock + */ + public static NLock getLock(String key, Long expireTimestamp) { + return new NLock(key, expireTimestamp); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/lock/remote/AbstractLockClient.java b/client/src/main/java/com/alibaba/nacos/client/lock/remote/AbstractLockClient.java new file mode 100644 index 00000000000..06b7b5a9bbe --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/lock/remote/AbstractLockClient.java @@ -0,0 +1,53 @@ +/* + * 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.client.lock.remote; + +import com.alibaba.nacos.client.security.SecurityProxy; +import com.alibaba.nacos.client.utils.AppNameUtils; +import com.alibaba.nacos.plugin.auth.api.RequestResource; + +import java.util.HashMap; +import java.util.Map; + +/** + * abstract lock client. + * @author 985492783@qq.com + * @description AbstractLockClient + * @date 2023/6/28 17:19 + */ +public abstract class AbstractLockClient implements LockClient { + private final SecurityProxy securityProxy; + + private static final String APP_FILED = "app"; + + protected AbstractLockClient(SecurityProxy securityProxy) { + this.securityProxy = securityProxy; + } + + protected Map getSecurityHeaders() { + RequestResource resource = RequestResource.lockBuilder().build(); + Map result = this.securityProxy.getIdentityContext(resource); + result.putAll(getAppHeaders()); + return result; + } + + protected Map getAppHeaders() { + Map result = new HashMap<>(1); + result.put(APP_FILED, AppNameUtils.getAppName()); + return result; + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/lock/remote/LockClient.java b/client/src/main/java/com/alibaba/nacos/client/lock/remote/LockClient.java new file mode 100644 index 00000000000..facc6ba5ec3 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/lock/remote/LockClient.java @@ -0,0 +1,50 @@ +/* + * 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.client.lock.remote; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.common.lifecycle.Closeable; + +/** + * lock client interface. + * + * @author 985492783@qq.com + * @description LockClient + * @date 2023/6/28 17:19 + */ +public interface LockClient extends Closeable { + + /** + * lock client get lock. + * + * @param instance instance. + * @return Boolean. + * @throws NacosException nacos Exception. + */ + Boolean lock(LockInstance instance) throws NacosException; + + /** + * lock client unLock. + * + * @param instance instance. + * @return Boolean. + * @throws NacosException nacos Exception. + */ + Boolean unLock(LockInstance instance) throws NacosException; + +} diff --git a/client/src/main/java/com/alibaba/nacos/client/lock/remote/grpc/LockGrpcClient.java b/client/src/main/java/com/alibaba/nacos/client/lock/remote/grpc/LockGrpcClient.java new file mode 100644 index 00000000000..e46ebbec137 --- /dev/null +++ b/client/src/main/java/com/alibaba/nacos/client/lock/remote/grpc/LockGrpcClient.java @@ -0,0 +1,120 @@ +/* + * 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.client.lock.remote.grpc; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.constant.PropertyConstants; +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.api.lock.remote.AbstractLockRequest; +import com.alibaba.nacos.api.lock.remote.LockOperationEnum; +import com.alibaba.nacos.api.lock.remote.request.LockOperationRequest; +import com.alibaba.nacos.api.lock.remote.response.LockOperationResponse; +import com.alibaba.nacos.api.remote.RemoteConstants; +import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ResponseCode; +import com.alibaba.nacos.client.env.NacosClientProperties; +import com.alibaba.nacos.client.lock.remote.AbstractLockClient; +import com.alibaba.nacos.client.security.SecurityProxy; +import com.alibaba.nacos.client.utils.AppNameUtils; +import com.alibaba.nacos.common.remote.ConnectionType; +import com.alibaba.nacos.common.remote.client.RpcClient; +import com.alibaba.nacos.common.remote.client.RpcClientFactory; +import com.alibaba.nacos.common.remote.client.RpcClientTlsConfigFactory; +import com.alibaba.nacos.common.remote.client.ServerListFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +/** + * lock grpc client. + * + * @author 985492783@qq.com + * @description LockGrpcClient + * @date 2023/6/28 17:35 + */ +public class LockGrpcClient extends AbstractLockClient { + + private final String uuid; + + private final Long requestTimeout; + + private final RpcClient rpcClient; + + public LockGrpcClient(NacosClientProperties properties, ServerListFactory serverListFactory, + SecurityProxy securityProxy) throws NacosException { + super(securityProxy); + this.uuid = UUID.randomUUID().toString(); + this.requestTimeout = Long.parseLong(properties.getProperty(PropertyConstants.LOCK_REQUEST_TIMEOUT, "-1")); + Map labels = new HashMap<>(); + labels.put(RemoteConstants.LABEL_SOURCE, RemoteConstants.LABEL_SOURCE_SDK); + labels.put(RemoteConstants.LABEL_MODULE, RemoteConstants.LABEL_MODULE_LOCK); + labels.put(Constants.APPNAME, AppNameUtils.getAppName()); + this.rpcClient = RpcClientFactory.createClient(uuid, ConnectionType.GRPC, labels, + RpcClientTlsConfigFactory.getInstance().createSdkConfig(properties.asProperties())); + start(serverListFactory); + } + + private void start(ServerListFactory serverListFactory) throws NacosException { + rpcClient.serverListFactory(serverListFactory); + rpcClient.start(); + } + + @Override + public Boolean lock(LockInstance instance) throws NacosException { + LockOperationRequest request = new LockOperationRequest(); + request.setLockInstance(instance); + request.setLockOperationEnum(LockOperationEnum.ACQUIRE); + LockOperationResponse acquireLockResponse = requestToServer(request, LockOperationResponse.class); + return (Boolean) acquireLockResponse.getResult(); + } + + @Override + public Boolean unLock(LockInstance instance) throws NacosException { + LockOperationRequest request = new LockOperationRequest(); + request.setLockInstance(instance); + request.setLockOperationEnum(LockOperationEnum.RELEASE); + LockOperationResponse acquireLockResponse = requestToServer(request, LockOperationResponse.class); + return (Boolean) acquireLockResponse.getResult(); + } + + @Override + public void shutdown() throws NacosException { + + } + + private T requestToServer(AbstractLockRequest request, Class responseClass) + throws NacosException { + try { + request.putAllHeader(getSecurityHeaders()); + Response response = + requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout); + if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) { + throw new NacosException(response.getErrorCode(), response.getMessage()); + } + if (responseClass.isAssignableFrom(response.getClass())) { + return (T) response; + } + } catch (NacosException e) { + throw e; + } catch (Exception e) { + throw new NacosException(NacosException.SERVER_ERROR, "Request nacos server failed: ", e); + } + throw new NacosException(NacosException.SERVER_ERROR, "Server return invalid response"); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java index 85351f12d5f..136ee66c824 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingMaintainService.java @@ -81,7 +81,7 @@ private void init(Properties properties) throws NacosException { InitUtils.initWebRootContext(nacosClientProperties); serverListManager = new NamingServerListManager(nacosClientProperties, namespace); serverListManager.start(); - securityProxy = new SecurityProxy(serverListManager.getServerList(), + securityProxy = new SecurityProxy(serverListManager, NamingHttpClientManager.getInstance().getNacosRestTemplate()); initSecurityProxy(properties); serverProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, nacosClientProperties); diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java index 7bab2fa7f60..bf382f02e27 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/NacosNamingService.java @@ -24,6 +24,8 @@ import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ListView; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import com.alibaba.nacos.api.naming.selector.NamingContext; +import com.alibaba.nacos.api.naming.selector.NamingResult; import com.alibaba.nacos.api.naming.selector.NamingSelector; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.api.selector.AbstractSelector; @@ -37,6 +39,7 @@ import com.alibaba.nacos.client.naming.remote.NamingClientProxyDelegate; import com.alibaba.nacos.client.naming.selector.NamingSelectorFactory; import com.alibaba.nacos.client.naming.selector.NamingSelectorWrapper; +import com.alibaba.nacos.client.naming.selector.ServiceInfoContext; import com.alibaba.nacos.client.naming.utils.CollectionUtils; import com.alibaba.nacos.client.naming.utils.InitUtils; import com.alibaba.nacos.client.naming.utils.UtilAndComs; @@ -324,38 +327,67 @@ private List selectInstances(ServiceInfo serviceInfo, boolean healthy) return list; } - private ServiceInfo getServiceInfoByFailover(String serviceName, String groupName, String clusterString) { - return serviceInfoHolder.getFailoverServiceInfo(serviceName, groupName, clusterString); + private ServiceInfo getServiceInfo(String serviceName, String groupName, List clusters, boolean subscribe) + throws NacosException { + ServiceInfo serviceInfo; + NamingSelector clusterSelector = NamingSelectorFactory.newClusterSelector(clusters); + if (serviceInfoHolder.isFailoverSwitch()) { + serviceInfo = getServiceInfoByFailover(serviceName, groupName, clusterSelector); + if (serviceInfo != null && !serviceInfo.getHosts().isEmpty()) { + NAMING_LOGGER.debug("getServiceInfo from failover,serviceName: {} data:{}", serviceName, + JacksonUtils.toJson(serviceInfo.getHosts())); + return serviceInfo; + } + } + serviceInfo = getServiceInfoBySubscribe(serviceName, groupName, clusters, clusterSelector, subscribe); + return serviceInfo; } - private ServiceInfo getServiceInfoBySubscribe(String serviceName, String groupName, String clusterString, - boolean subscribe) throws NacosException { + private ServiceInfo getServiceInfoByFailover(String serviceName, String groupName, NamingSelector clusterSelector) { + ServiceInfo result = serviceInfoHolder.getFailoverServiceInfo(serviceName, groupName, StringUtils.EMPTY); + return doSelectInstance(result, clusterSelector); + } + + private ServiceInfo getServiceInfoBySubscribe(String serviceName, String groupName, List clusters, + NamingSelector selector, boolean subscribe) throws NacosException { ServiceInfo serviceInfo; if (subscribe) { - serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, clusterString); - if (null == serviceInfo || !clientProxy.isSubscribed(serviceName, groupName, clusterString)) { - serviceInfo = clientProxy.subscribe(serviceName, groupName, clusterString); - } + serviceInfo = serviceInfoHolder.getServiceInfo(serviceName, groupName, StringUtils.EMPTY); + serviceInfo = tryToSubscribe(serviceName, groupName, serviceInfo); + serviceInfo = doSelectInstance(serviceInfo, selector); } else { + String clusterString = NamingSelectorFactory.getUniqueClusterString(clusters); serviceInfo = clientProxy.queryInstancesOfService(serviceName, groupName, clusterString, false); } return serviceInfo; } - private ServiceInfo getServiceInfo(String serviceName, String groupName, List clusters, boolean subscribe) - throws NacosException { - ServiceInfo serviceInfo; - String clusterString = StringUtils.join(clusters, ","); - if (serviceInfoHolder.isFailoverSwitch()) { - serviceInfo = getServiceInfoByFailover(serviceName, groupName, clusterString); - if (serviceInfo != null && serviceInfo.getHosts().size() > 0) { - NAMING_LOGGER.debug("getServiceInfo from failover,serviceName: {} data:{}", serviceName, - JacksonUtils.toJson(serviceInfo.getHosts())); - return serviceInfo; - } + private ServiceInfo tryToSubscribe(String serviceName, String groupName, ServiceInfo cachedServiceInfo) throws NacosException { + // not found in cache, service never subscribed. + if (null == cachedServiceInfo) { + return clientProxy.subscribe(serviceName, groupName, StringUtils.EMPTY); } - - serviceInfo = getServiceInfoBySubscribe(serviceName, groupName, clusterString, subscribe); + // found in cache, and subscribed. + if (clientProxy.isSubscribed(serviceName, groupName, StringUtils.EMPTY)) { + return cachedServiceInfo; + } + // found in cached, but not subscribed, such as cached from local file when starting. + ServiceInfo result = cachedServiceInfo; + try { + result = clientProxy.subscribe(serviceName, groupName, StringUtils.EMPTY); + } catch (NacosException e) { + NAMING_LOGGER.warn("Subscribe from Server failed, will use local cache. fail message: ", e); + } + return result; + } + + private ServiceInfo doSelectInstance(ServiceInfo serviceInfo, NamingSelector clusterSelector) { + if (null == serviceInfo) { + return null; + } + NamingContext context = new ServiceInfoContext(serviceInfo); + NamingResult result = clusterSelector.select(context); + serviceInfo.setHosts(result.getResult()); return serviceInfo; } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxy.java index 7fa7a8ab9e5..9a684617a7c 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/AbstractNamingClientProxy.java @@ -54,4 +54,8 @@ protected Map getAppHeaders() { result.put(APP_FILED, AppNameUtils.getAppName()); return result; } + + protected void reLogin() { + securityProxy.reLogin(); + } } diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java index beef78ad036..efd7bdabf46 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/NamingClientProxyDelegate.java @@ -74,7 +74,7 @@ public NamingClientProxyDelegate(String namespace, ServiceInfoHolder serviceInfo this.serverListManager = new NamingServerListManager(properties, namespace); this.serverListManager.start(); this.serviceInfoHolder = serviceInfoHolder; - this.securityProxy = new SecurityProxy(this.serverListManager.getServerList(), + this.securityProxy = new SecurityProxy(this.serverListManager, NamingHttpClientManager.getInstance().getNacosRestTemplate()); initSecurityProxy(properties); this.httpClientProxy = new NamingHttpClientProxy(namespace, securityProxy, serverListManager, properties); diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java index 27e44ce1ff3..508b76f0ab3 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxy.java @@ -446,6 +446,10 @@ private T requestToServer(AbstractNamingRequest request, Cl getSecurityHeaders(request.getNamespace(), request.getGroupName(), request.getServiceName())); response = requestTimeout < 0 ? rpcClient.request(request) : rpcClient.request(request, requestTimeout); if (ResponseCode.SUCCESS.getCode() != response.getResultCode()) { + // If the 403 login operation is triggered, refresh the accessToken of the client + if (NacosException.NO_RIGHT == response.getErrorCode()) { + reLogin(); + } throw new NacosException(response.getErrorCode(), response.getMessage()); } if (responseClass.isAssignableFrom(response.getClass())) { diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientManager.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientManager.java index 64ea659d1f9..4b6865b0c94 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientManager.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientManager.java @@ -68,14 +68,14 @@ public NacosRestTemplate getNacosRestTemplate() { @Override public void shutdown() throws NacosException { - NAMING_LOGGER.warn("[NamingHttpClientManager] Start destroying NacosRestTemplate"); + NAMING_LOGGER.info("[NamingHttpClientManager] Start destroying NacosRestTemplate"); try { HttpClientBeanHolder.shutdownNacosSyncRest(HTTP_CLIENT_FACTORY.getClass().getName()); } catch (Exception ex) { NAMING_LOGGER.error("[NamingHttpClientManager] An exception occurred when the HTTP client was closed : {}", ExceptionUtil.getStackTrace(ex)); } - NAMING_LOGGER.warn("[NamingHttpClientManager] Destruction of the end"); + NAMING_LOGGER.info("[NamingHttpClientManager] Completed destruction of NacosRestTemplate"); } private static class NamingHttpClientFactory extends AbstractHttpClientFactory { diff --git a/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java b/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java index f2ed0fe768e..30ccc039b2d 100644 --- a/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxy.java @@ -440,6 +440,12 @@ public String callServer(String api, Map params, Map getInstances() { + return serviceInfo.getHosts(); + } +} diff --git a/client/src/main/java/com/alibaba/nacos/client/security/SecurityProxy.java b/client/src/main/java/com/alibaba/nacos/client/security/SecurityProxy.java index d0362719be3..5199662d6c9 100644 --- a/client/src/main/java/com/alibaba/nacos/client/security/SecurityProxy.java +++ b/client/src/main/java/com/alibaba/nacos/client/security/SecurityProxy.java @@ -17,15 +17,22 @@ package com.alibaba.nacos.client.security; import com.alibaba.nacos.api.exception.NacosException; -import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthPluginManager; -import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext; -import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthService; -import com.alibaba.nacos.plugin.auth.api.RequestResource; +import com.alibaba.nacos.client.address.AbstractServerListManager; +import com.alibaba.nacos.client.address.ServerListChangeEvent; +import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant; import com.alibaba.nacos.common.http.client.NacosRestTemplate; import com.alibaba.nacos.common.lifecycle.Closeable; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext; +import com.alibaba.nacos.plugin.auth.api.RequestResource; +import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthPluginManager; +import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.Properties; @@ -37,18 +44,30 @@ */ public class SecurityProxy implements Closeable { + private static final Logger LOGGER = LoggerFactory.getLogger(SecurityProxy.class); + private ClientAuthPluginManager clientAuthPluginManager; /** - * Construct from serverList, nacosRestTemplate, init client auth plugin. - * // TODO change server list to serverListManager after serverListManager refactor and unite. + * Construct from serverListManager, nacosRestTemplate, init client auth plugin. * - * @param serverList a server list that client request to. + * @param serverListManager a server list manager that client request to. * @Param nacosRestTemplate http request template. */ - public SecurityProxy(List serverList, NacosRestTemplate nacosRestTemplate) { + public SecurityProxy(AbstractServerListManager serverListManager, NacosRestTemplate nacosRestTemplate) { clientAuthPluginManager = new ClientAuthPluginManager(); - clientAuthPluginManager.init(serverList, nacosRestTemplate); + clientAuthPluginManager.init(serverListManager.getServerList(), nacosRestTemplate); + NotifyCenter.registerSubscriber(new Subscriber() { + @Override + public void onEvent(ServerListChangeEvent event) { + clientAuthPluginManager.refreshServerList(serverListManager.getServerList()); + } + + @Override + public Class subscribeType() { + return ServerListChangeEvent.class; + } + }); } /** @@ -85,4 +104,23 @@ public Map getIdentityContext(RequestResource resource) { public void shutdown() throws NacosException { clientAuthPluginManager.shutdown(); } + + /** + * Login again to refresh the accessToken. + */ + public void reLogin() { + if (clientAuthPluginManager.getAuthServiceSpiImplSet().isEmpty()) { + return; + } + for (ClientAuthService clientAuthService : clientAuthPluginManager.getAuthServiceSpiImplSet()) { + try { + LoginIdentityContext loginIdentityContext = clientAuthService.getLoginIdentityContext(new RequestResource()); + if (loginIdentityContext != null) { + loginIdentityContext.setParameter(NacosAuthLoginConstant.RELOGINFLAG, "true"); + } + } catch (Exception e) { + LOGGER.error("[SecurityProxy] set reLoginFlag failed.", e); + } + } + } } diff --git a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java index 593407911bf..4325d60adcc 100644 --- a/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java +++ b/client/src/main/java/com/alibaba/nacos/client/utils/ParamUtil.java @@ -239,7 +239,7 @@ public static String parseNamespace(NacosClientProperties properties) { if (StringUtils.isBlank(namespaceTmp)) { namespaceTmp = properties.getProperty(PropertyKeyConst.NAMESPACE); } - return StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : StringUtils.EMPTY; + return StringUtils.isNotBlank(namespaceTmp) ? namespaceTmp.trim() : Constants.DEFAULT_NAMESPACE_ID; } /** diff --git a/client/src/test/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImplTest.java b/client/src/test/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImplTest.java index a2312d8b984..6508350e877 100644 --- a/client/src/test/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImplTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/auth/impl/NacosClientAuthServiceImplTest.java @@ -235,4 +235,19 @@ void testGetAccessTokenWithInvalidTtl() throws Exception { //when assertFalse(nacosClientAuthService.login(properties)); } + + @Test + void testReLogin() { + NacosClientAuthServiceImpl nacosClientAuthService = new NacosClientAuthServiceImpl(); + nacosClientAuthService.login(new Properties()); + // reLogin + nacosClientAuthService.getLoginIdentityContext(null).setParameter(NacosAuthLoginConstant.RELOGINFLAG, "true"); + Properties properties = new Properties(); + properties.setProperty(PropertyKeyConst.USERNAME, "aaa"); + properties.setProperty(PropertyKeyConst.PASSWORD, "123456"); + List serverList = new ArrayList<>(); + serverList.add("localhost"); + //when + assertTrue(nacosClientAuthService.login(properties)); + } } diff --git a/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java b/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java index e5287eadee5..7e56dde22d4 100644 --- a/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/config/NacosConfigServiceTest.java @@ -80,12 +80,12 @@ void clean() { void testGetConfigFromServer() throws NacosException { final String dataId = "1"; final String group = "2"; - final String tenant = ""; + final String tenant = "public"; final int timeout = 3000; ConfigResponse response = new ConfigResponse(); response.setContent("aa"); response.setConfigType("bb"); - Mockito.when(mockWoker.getServerConfig(dataId, group, "", timeout, false)).thenReturn(response); + Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false)).thenReturn(response); final String config = nacosConfigService.getConfig(dataId, group, timeout); assertEquals("aa", config); Mockito.verify(mockWoker, Mockito.times(1)).getServerConfig(dataId, group, tenant, timeout, false); @@ -96,7 +96,7 @@ void testGetConfigFromServer() throws NacosException { void testGetConfigFromFailOver() throws NacosException { final String dataId = "1failover"; final String group = "2"; - final String tenant = ""; + final String tenant = "public"; MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic(LocalConfigInfoProcessor.class); try { @@ -116,7 +116,7 @@ void testGetConfigFromFailOver() throws NacosException { void testGetConfigFromLocalCache() throws NacosException { final String dataId = "1localcache"; final String group = "2"; - final String tenant = ""; + final String tenant = "public"; MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic(LocalConfigInfoProcessor.class); try { @@ -129,7 +129,7 @@ void testGetConfigFromLocalCache() throws NacosException { .thenReturn(contentFailOver); //form server error. final int timeout = 3000; - Mockito.when(mockWoker.getServerConfig(dataId, group, "", timeout, false)).thenThrow(new NacosException()); + Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false)).thenThrow(new NacosException()); final String config = nacosConfigService.getConfig(dataId, group, timeout); assertEquals(contentFailOver, config); @@ -143,7 +143,7 @@ void testGetConfigFromLocalCache() throws NacosException { void testGetConfig403() throws NacosException { final String dataId = "1localcache403"; final String group = "2"; - final String tenant = ""; + final String tenant = "public"; MockedStatic localConfigInfoProcessorMockedStatic = Mockito.mockStatic(LocalConfigInfoProcessor.class); try { @@ -153,7 +153,7 @@ void testGetConfig403() throws NacosException { //form server error. final int timeout = 3000; - Mockito.when(mockWoker.getServerConfig(dataId, group, "", timeout, false)) + Mockito.when(mockWoker.getServerConfig(dataId, group, tenant, timeout, false)) .thenThrow(new NacosException(NacosException.NO_RIGHT, "no right")); try { nacosConfigService.getConfig(dataId, group, timeout); @@ -271,7 +271,7 @@ void testPublishConfig() throws NacosException { String dataId = "1"; String group = "2"; String content = "123"; - String namespace = ""; + String namespace = "public"; String type = ConfigType.getDefaultType().getType(); Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type)).thenReturn(true); @@ -286,7 +286,7 @@ void testPublishConfig2() throws NacosException { String dataId = "1"; String group = "2"; String content = "123"; - String namespace = ""; + String namespace = "public"; String type = ConfigType.PROPERTIES.getType(); Mockito.when(mockWoker.publishConfig(dataId, group, namespace, null, null, null, content, "", null, type)).thenReturn(true); @@ -302,7 +302,7 @@ void testPublishConfigCas() throws NacosException { String dataId = "1"; String group = "2"; String content = "123"; - String namespace = ""; + String namespace = "public"; String casMd5 = "96147704e3cb8be8597d55d75d244a02"; String type = ConfigType.getDefaultType().getType(); @@ -319,7 +319,7 @@ void testPublishConfigCas2() throws NacosException { String dataId = "1"; String group = "2"; String content = "123"; - String namespace = ""; + String namespace = "public"; String casMd5 = "96147704e3cb8be8597d55d75d244a02"; String type = ConfigType.PROPERTIES.getType(); @@ -335,7 +335,7 @@ void testPublishConfigCas2() throws NacosException { void testRemoveConfig() throws NacosException { String dataId = "1"; String group = "2"; - String tenant = ""; + String tenant = "public"; Mockito.when(mockWoker.removeConfig(dataId, group, tenant, null)).thenReturn(true); diff --git a/client/src/test/java/com/alibaba/nacos/client/config/impl/ClientWorkerTest.java b/client/src/test/java/com/alibaba/nacos/client/config/impl/ClientWorkerTest.java index 7c3cd312962..5659c2c3684 100644 --- a/client/src/test/java/com/alibaba/nacos/client/config/impl/ClientWorkerTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/config/impl/ClientWorkerTest.java @@ -30,6 +30,7 @@ import com.alibaba.nacos.api.config.remote.response.ConfigChangeBatchListenResponse; import com.alibaba.nacos.api.config.remote.response.ConfigPublishResponse; import com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse; +import com.alibaba.nacos.api.config.remote.response.ConfigRemoveResponse; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.client.config.common.GroupKey; import com.alibaba.nacos.client.config.filter.impl.ConfigFilterChainManager; @@ -761,4 +762,21 @@ void testAddTenantListenersWithContentEnsureCacheDataSafe() assertFalse(cacheDataFromCache2.isDiscard()); assertFalse(cacheDataFromCache2.isConsistentWithServer()); } + + @Test + void testResponse403() throws NacosException { + Properties prop = new Properties(); + ConfigFilterChainManager filter = new ConfigFilterChainManager(new Properties()); + ConfigServerListManager agent = Mockito.mock(ConfigServerListManager.class); + + final NacosClientProperties nacosClientProperties = NacosClientProperties.PROTOTYPE.derive(prop); + final ClientWorker clientWorker = new ClientWorker(filter, agent, nacosClientProperties); + + ConfigRemoveResponse response = ConfigRemoveResponse.buildFailResponse("accessToken invalid"); + response.setErrorCode(ConfigQueryResponse.NO_RIGHT); + Mockito.when(rpcClient.request(any(ConfigRemoveRequest.class))) + .thenReturn(response); + boolean result = clientWorker.removeConfig("a", "b", "c", "tag"); + assertFalse(result); + } } diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java index 7b1b0275bf0..b1c98ee7195 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/NacosNamingServiceTest.java @@ -34,6 +34,7 @@ import com.alibaba.nacos.client.naming.selector.NamingSelectorWrapper; import com.alibaba.nacos.client.naming.utils.CollectionUtils; import com.alibaba.nacos.client.naming.utils.UtilAndComs; +import com.alibaba.nacos.common.utils.StringUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -47,9 +48,11 @@ import java.util.Collections; import java.util.List; import java.util.Properties; +import java.util.concurrent.atomic.AtomicBoolean; import static com.alibaba.nacos.client.naming.selector.NamingSelectorFactory.getUniqueClusterString; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -118,7 +121,7 @@ private void injectMocks(NacosNamingService client) throws NoSuchFieldException, } @Test - void testRegisterInstance1() throws NacosException { + void testRegisterInstanceSingle() throws NacosException { //given String serviceName = "service1"; String ip = "1.1.1.1"; @@ -133,87 +136,7 @@ void testRegisterInstance1() throws NacosException { } @Test - void testBatchRegisterInstance() throws NacosException { - Instance instance = new Instance(); - String serviceName = "service1"; - String ip = "1.1.1.1"; - int port = 10000; - instance.setServiceName(serviceName); - instance.setEphemeral(true); - instance.setPort(port); - instance.setIp(ip); - List instanceList = new ArrayList<>(); - instanceList.add(instance); - //when - client.batchRegisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); - //then - verify(proxy, times(1)).batchRegisterService(eq(serviceName), eq(Constants.DEFAULT_GROUP), - argThat(instances -> CollectionUtils.isEqualCollection(instanceList, instances))); - } - - @Test - void testBatchRegisterInstanceWithGroupNamePrefix() throws NacosException { - Instance instance = new Instance(); - String serviceName = "service1"; - String ip = "1.1.1.1"; - int port = 10000; - instance.setServiceName(Constants.DEFAULT_GROUP + "@@" + serviceName); - instance.setEphemeral(true); - instance.setPort(port); - instance.setIp(ip); - List instanceList = new ArrayList<>(); - instanceList.add(instance); - //when - client.batchRegisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); - //then - verify(proxy, times(1)).batchRegisterService(eq(serviceName), eq(Constants.DEFAULT_GROUP), - argThat(instances -> CollectionUtils.isEqualCollection(instanceList, instances))); - } - - @Test - void testBatchRegisterInstanceWithWrongGroupNamePrefix() throws NacosException { - Instance instance = new Instance(); - String serviceName = "service1"; - String ip = "1.1.1.1"; - int port = 10000; - instance.setServiceName("WrongGroup" + "@@" + serviceName); - instance.setEphemeral(true); - instance.setPort(port); - instance.setIp(ip); - List instanceList = new ArrayList<>(); - instanceList.add(instance); - //when - try { - client.batchRegisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); - } catch (Exception e) { - assertTrue(e instanceof NacosException); - assertTrue(e.getMessage().contains("wrong group name prefix of instance service name")); - } - } - - @Test - void testBatchDeRegisterInstance() throws NacosException { - Instance instance = new Instance(); - String serviceName = "service1"; - String ip = "1.1.1.1"; - int port = 10000; - instance.setServiceName(serviceName); - instance.setEphemeral(true); - instance.setPort(port); - instance.setIp(ip); - List instanceList = new ArrayList<>(); - instanceList.add(instance); - //when - try { - client.batchDeregisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); - } catch (Exception e) { - assertTrue(e instanceof NacosException); - assertTrue(e.getMessage().contains("not found")); - } - } - - @Test - void testRegisterInstance2() throws NacosException { + void testRegisterInstanceSingleWithGroup() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -229,7 +152,7 @@ void testRegisterInstance2() throws NacosException { } @Test - void testRegisterInstance3() throws NacosException { + void testRegisterInstanceSingleWithCluster() throws NacosException { //given String serviceName = "service1"; String clusterName = "cluster1"; @@ -245,7 +168,7 @@ void testRegisterInstance3() throws NacosException { } @Test - void testRegisterInstance4() throws NacosException { + void testRegisterInstanceSingleFull() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -262,7 +185,7 @@ void testRegisterInstance4() throws NacosException { } @Test - void testRegisterInstance5() throws NacosException { + void testRegisterInstanceByInstanceOnlyService() throws NacosException { //given String serviceName = "service1"; Instance instance = new Instance(); @@ -273,7 +196,7 @@ void testRegisterInstance5() throws NacosException { } @Test - void testRegisterInstance6() throws NacosException { + void testRegisterInstanceByInstanceFullName() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -285,7 +208,7 @@ void testRegisterInstance6() throws NacosException { } @Test - void testRegisterInstance7() throws NacosException { + void testRegisterInstanceByInstanceWithCluster() throws NacosException { Throwable exception = assertThrows(NacosException.class, () -> { //given @@ -301,7 +224,87 @@ void testRegisterInstance7() throws NacosException { } @Test - void testDeregisterInstance1() throws NacosException { + void testBatchRegisterInstance() throws NacosException { + Instance instance = new Instance(); + String serviceName = "service1"; + String ip = "1.1.1.1"; + int port = 10000; + instance.setServiceName(serviceName); + instance.setEphemeral(true); + instance.setPort(port); + instance.setIp(ip); + List instanceList = new ArrayList<>(); + instanceList.add(instance); + //when + client.batchRegisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); + //then + verify(proxy, times(1)).batchRegisterService(eq(serviceName), eq(Constants.DEFAULT_GROUP), + argThat(instances -> CollectionUtils.isEqualCollection(instanceList, instances))); + } + + @Test + void testBatchRegisterInstanceWithGroupNamePrefix() throws NacosException { + Instance instance = new Instance(); + String serviceName = "service1"; + String ip = "1.1.1.1"; + int port = 10000; + instance.setServiceName(Constants.DEFAULT_GROUP + "@@" + serviceName); + instance.setEphemeral(true); + instance.setPort(port); + instance.setIp(ip); + List instanceList = new ArrayList<>(); + instanceList.add(instance); + //when + client.batchRegisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); + //then + verify(proxy, times(1)).batchRegisterService(eq(serviceName), eq(Constants.DEFAULT_GROUP), + argThat(instances -> CollectionUtils.isEqualCollection(instanceList, instances))); + } + + @Test + void testBatchRegisterInstanceWithWrongGroupNamePrefix() throws NacosException { + Instance instance = new Instance(); + String serviceName = "service1"; + String ip = "1.1.1.1"; + int port = 10000; + instance.setServiceName("WrongGroup" + "@@" + serviceName); + instance.setEphemeral(true); + instance.setPort(port); + instance.setIp(ip); + List instanceList = new ArrayList<>(); + instanceList.add(instance); + //when + try { + client.batchRegisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); + } catch (Exception e) { + assertTrue(e instanceof NacosException); + assertTrue(e.getMessage().contains("wrong group name prefix of instance service name")); + } + } + + @Test + void testBatchDeRegisterInstance() throws NacosException { + Instance instance = new Instance(); + String serviceName = "service1"; + String ip = "1.1.1.1"; + int port = 10000; + instance.setServiceName(serviceName); + instance.setEphemeral(true); + instance.setPort(port); + instance.setIp(ip); + List instanceList = new ArrayList<>(); + instanceList.add(instance); + //when + try { + client.batchDeregisterInstance(serviceName, Constants.DEFAULT_GROUP, instanceList); + } catch (Exception e) { + assertTrue(e instanceof NacosException); + assertTrue(e.getMessage().contains("not found")); + } + } + + @Test + void testDeregisterInstanceSingle() throws NacosException { //given String serviceName = "service1"; String ip = "1.1.1.1"; @@ -316,7 +319,7 @@ void testDeregisterInstance1() throws NacosException { } @Test - void testDeregisterInstance2() throws NacosException { + void testDeregisterInstanceSingleWithGroup() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -332,7 +335,7 @@ void testDeregisterInstance2() throws NacosException { } @Test - void testDeregisterInstance3() throws NacosException { + void testDeregisterInstanceSingleWithCluster() throws NacosException { //given String serviceName = "service1"; String clusterName = "cluster1"; @@ -348,7 +351,7 @@ void testDeregisterInstance3() throws NacosException { } @Test - void testDeregisterInstance4() throws NacosException { + void testDeregisterInstanceSingleFull() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -365,7 +368,7 @@ void testDeregisterInstance4() throws NacosException { } @Test - void testDeregisterInstance5() throws NacosException { + void testDeregisterInstanceByInstanceOnlyService() throws NacosException { //given String serviceName = "service1"; Instance instance = new Instance(); @@ -376,7 +379,7 @@ void testDeregisterInstance5() throws NacosException { } @Test - void testDeregisterInstance6() throws NacosException { + void testDeregisterInstanceByInstanceFullName() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -388,94 +391,135 @@ void testDeregisterInstance6() throws NacosException { } @Test - void testGetAllInstances1() throws NacosException { + void testGetAllInstancesOnlyService() throws NacosException { //given String serviceName = "service1"; //when - client.getAllInstances(serviceName); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(new Instance()); + when(proxy.subscribe(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName); //then - verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, ""); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testGetAllInstances2() throws NacosException { + void testGetAllInstancesFullName() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; //when - client.getAllInstances(serviceName, groupName); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.setGroupName(groupName); + serviceInfo.addHost(new Instance()); + when(proxy.subscribe(serviceName, groupName, "")).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName, groupName); //then - verify(proxy, times(1)).subscribe(serviceName, groupName, ""); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testGetAllInstances3() throws NacosException { + void testGetAllInstancesOnlyServiceNotSubscribe() throws NacosException { //given String serviceName = "service1"; //when - client.getAllInstances(serviceName, false); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(new Instance()); + when(proxy.queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "", false)).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "", false); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testGetAllInstances4() throws NacosException { + void testGetAllInstancesFullNameNotSubscribe() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; //when - client.getAllInstances(serviceName, groupName, false); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.setGroupName(groupName); + serviceInfo.addHost(new Instance()); + when(proxy.queryInstancesOfService(serviceName, groupName, "", false)).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName, groupName, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, groupName, "", false); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testGetAllInstances5() throws NacosException { + void testGetAllInstancesWithServiceAndClusters() throws NacosException { //given String serviceName = "service1"; - List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.getAllInstances(serviceName, clusterList); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance("cluster1", false)); + serviceInfo.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, false)); + List clusterList = Arrays.asList("cluster1", "cluster2"); + when(proxy.subscribe(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName, clusterList); //then - verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2"); + assertEquals(1, result.size()); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testGetAllInstances6() throws NacosException { + void testGetAllInstancesWithFullNameAndClusters() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; + // when + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.setGroupName(groupName); + serviceInfo.addHost(mockInstance("cluster1", false)); + serviceInfo.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, false)); List clusterList = Arrays.asList("cluster1", "cluster2"); - //when - client.getAllInstances(serviceName, groupName, clusterList); + serviceInfo.getHosts().get(1).setClusterName(Constants.DEFAULT_CLUSTER_NAME); + when(proxy.subscribe(serviceName, groupName, "")).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName, groupName, clusterList); //then - verify(proxy, times(1)).subscribe(serviceName, groupName, "cluster1,cluster2"); + assertEquals(1, result.size()); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testGetAllInstances7() throws NacosException { + void testGetAllInstancesWithServiceAndClustersNotSubscribe() throws NacosException { //given String serviceName = "service1"; - List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.getAllInstances(serviceName, clusterList, false); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance("cluster1", false)); + List clusterList = Arrays.asList("cluster1", "cluster2"); + when(proxy.queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2", + false)).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName, clusterList, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2", - false); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testGetAllInstances8() throws NacosException { + void testGetAllInstancesWithFullNameAndClustersNotSubscribe() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; + // when + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.setGroupName(groupName); + serviceInfo.addHost(mockInstance("cluster1", false)); List clusterList = Arrays.asList("cluster1", "cluster2"); - //when - client.getAllInstances(serviceName, groupName, clusterList, false); + when(proxy.queryInstancesOfService(serviceName, groupName, "cluster1,cluster2", false)).thenReturn(serviceInfo); + List result = client.getAllInstances(serviceName, groupName, clusterList, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, groupName, "cluster1,cluster2", false); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test @@ -483,7 +527,7 @@ void testGetAllInstanceFromFailover() throws NacosException { when(serviceInfoHolder.isFailoverSwitch()).thenReturn(true); ServiceInfo serviceInfo = new ServiceInfo("group1@@service1"); serviceInfo.setHosts(Collections.singletonList(new Instance())); - when(serviceInfoHolder.getFailoverServiceInfo(anyString(), anyString(), anyString())).thenReturn(serviceInfo); + when(serviceInfoHolder.getFailoverServiceInfo("service1", "group1", "")).thenReturn(serviceInfo); List actual = client.getAllInstances("service1", "group1", false); verify(proxy, never()).queryInstancesOfService(anyString(), anyString(), anyString(), anyBoolean()); assertEquals(1, actual.size()); @@ -494,101 +538,172 @@ void testGetAllInstanceFromFailover() throws NacosException { void testGetAllInstanceFromFailoverEmpty() throws NacosException { when(serviceInfoHolder.isFailoverSwitch()).thenReturn(true); ServiceInfo serviceInfo = new ServiceInfo("group1@@service1"); - when(serviceInfoHolder.getFailoverServiceInfo(anyString(), anyString(), anyString())).thenReturn(serviceInfo); + when(serviceInfoHolder.getFailoverServiceInfo("service1", "group1", "")).thenReturn(serviceInfo); List actual = client.getAllInstances("service1", "group1", false); verify(proxy).queryInstancesOfService(anyString(), anyString(), anyString(), anyBoolean()); assertEquals(0, actual.size()); } @Test - void testSelectInstances1() throws NacosException { + void testGetAllInstanceWithCacheAndSubscribeException() throws NacosException { + String serviceName = "service1"; + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(new Instance()); + when(serviceInfoHolder.getServiceInfo(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(serviceInfo); + when(proxy.subscribe(serviceName, Constants.DEFAULT_GROUP, "")).thenThrow(new NacosException(500, "test")); + List result = client.getAllInstances(serviceName); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); + } + + @Test + void testGetAllInstanceWithoutCacheAndSubscribeException() throws NacosException { + String serviceName = "service1"; + when(proxy.subscribe(serviceName, Constants.DEFAULT_GROUP, "")).thenThrow(new NacosException(500, "test")); + assertThrows(NacosException.class, () -> client.getAllInstances(serviceName)); + } + + @Test + void testGetAllInstanceWithCacheAndSubscribed() throws NacosException { + String serviceName = "service1"; + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(new Instance()); + when(serviceInfoHolder.getServiceInfo(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(serviceInfo); + when(proxy.isSubscribed(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(true); + List result = client.getAllInstances(serviceName); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); + } + + @Test + void testSelectInstancesOnlyService() throws NacosException { //given String serviceName = "service1"; //when - client.selectInstances(serviceName, true); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); + when(proxy.subscribe(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, true); //then - verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, ""); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testSelectInstances2() throws NacosException { + void testSelectInstancesFullName() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; //when - client.selectInstances(serviceName, groupName, true); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); + when(proxy.subscribe(serviceName, groupName, "")).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, groupName, true); //then - verify(proxy, times(1)).subscribe(serviceName, groupName, ""); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testSelectInstances3() throws NacosException { + void testSelectInstancesOnlyServiceNotSubscribe() throws NacosException { //given String serviceName = "service1"; //when - client.selectInstances(serviceName, true, false); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); + when(proxy.queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "", false)).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, true, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "", false); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testSelectInstances4() throws NacosException { + void testSelectInstancesFullNameNotSubscribe() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; //when - client.selectInstances(serviceName, groupName, true, false); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); + when(proxy.queryInstancesOfService(serviceName, groupName, "", false)).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, groupName, true, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, groupName, "", false); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testSelectInstances5() throws NacosException { + void testSelectInstancesWithServiceAndClusters() throws NacosException { //given String serviceName = "service1"; - List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.selectInstances(serviceName, clusterList, true); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance("cluster1", true)); + serviceInfo.addHost(mockInstance("cluster1", false)); + List clusterList = Arrays.asList("cluster1", "cluster2"); + when(proxy.subscribe(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, clusterList, true); //then - verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2"); + assertEquals(1, result.size()); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testSelectInstances6() throws NacosException { + void testSelectInstancesWithFullNameAndClusters() throws NacosException { //given String serviceName = "service1"; - String groupName = "group1"; - List clusterList = Arrays.asList("cluster1", "cluster2"); + final String groupName = "group1"; //when - client.selectInstances(serviceName, groupName, clusterList, true); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance("cluster1", true)); + serviceInfo.addHost(mockInstance("cluster1", false)); + List clusterList = Arrays.asList("cluster1", "cluster2"); + when(proxy.subscribe(serviceName, groupName, "")).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, groupName, clusterList, true); //then - verify(proxy, times(1)).subscribe(serviceName, groupName, "cluster1,cluster2"); + assertEquals(1, result.size()); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testSelectInstances7() throws NacosException { + void testSelectInstancesWithServiceAndClustersNotSubscribe() throws NacosException { //given String serviceName = "service1"; - List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.selectInstances(serviceName, clusterList, true, false); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance("cluster1", true)); + serviceInfo.addHost(mockInstance("cluster1", false)); + List clusterList = Arrays.asList("cluster1", "cluster2"); + when(proxy.queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2", + false)).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, clusterList, true, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2", - false); + assertEquals(1, result.size()); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test - void testSelectInstances8() throws NacosException { + void testSelectInstancesWithFullNameAndClustersNotSubscribe() throws NacosException { //given String serviceName = "service1"; - String groupName = "group1"; - List clusterList = Arrays.asList("cluster1", "cluster2"); + final String groupName = "group1"; //when - client.selectInstances(serviceName, groupName, clusterList, true, false); + ServiceInfo serviceInfo = new ServiceInfo(); + serviceInfo.setName(serviceName); + serviceInfo.addHost(mockInstance("cluster1", true)); + serviceInfo.addHost(mockInstance("cluster1", false)); + List clusterList = Arrays.asList("cluster1", "cluster2"); + when(proxy.queryInstancesOfService(serviceName, groupName, "cluster1,cluster2", false)).thenReturn(serviceInfo); + List result = client.selectInstances(serviceName, groupName, clusterList, true, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, groupName, "cluster1,cluster2", false); + assertEquals(1, result.size()); + assertEquals(serviceInfo.getHosts().get(0), result.get(0)); } @Test @@ -628,160 +743,123 @@ void testSelectInstancesWithHealthyFlag() throws NacosException { } @Test - void testSelectOneHealthyInstance1() throws NacosException { + void testSelectOneHealthyInstanceOnlyService() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); + infoWithHealthyInstance.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); when(proxy.subscribe(anyString(), anyString(), anyString())).thenReturn(infoWithHealthyInstance); String serviceName = "service1"; //when - client.selectOneHealthyInstance(serviceName); + Instance instance = client.selectOneHealthyInstance(serviceName); //then - verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, ""); + assertNotNull(instance); } @Test - void testSelectOneHealthyInstance2() throws NacosException { + void testSelectOneHealthyInstanceFullName() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); + infoWithHealthyInstance.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); when(proxy.subscribe(anyString(), anyString(), anyString())).thenReturn(infoWithHealthyInstance); String serviceName = "service1"; String groupName = "group1"; //when - client.selectOneHealthyInstance(serviceName, groupName); + Instance instance = client.selectOneHealthyInstance(serviceName, groupName); //then - verify(proxy, times(1)).subscribe(serviceName, groupName, ""); + assertNotNull(instance); } @Test - void testSelectOneHealthyInstance3() throws NacosException { + void testSelectOneHealthyInstanceOnlyServiceNotSubscribe() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); + infoWithHealthyInstance.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); when(proxy.queryInstancesOfService(anyString(), anyString(), anyString(), anyBoolean())).thenReturn( infoWithHealthyInstance); String serviceName = "service1"; //when - client.selectOneHealthyInstance(serviceName, false); + Instance instance = client.selectOneHealthyInstance(serviceName, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "", false); + assertNotNull(instance); } @Test - void testSelectOneHealthyInstance4() throws NacosException { + void testSelectOneHealthyInstanceFullNameNotSubscribe() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); + infoWithHealthyInstance.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); when(proxy.queryInstancesOfService(anyString(), anyString(), anyString(), anyBoolean())).thenReturn( infoWithHealthyInstance); String serviceName = "service1"; String groupName = "group1"; //when - client.selectOneHealthyInstance(serviceName, groupName, false); + Instance instance = client.selectOneHealthyInstance(serviceName, groupName, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, groupName, "", false); + assertNotNull(instance); } @Test - void testSelectOneHealthyInstance5() throws NacosException { + void testSelectOneHealthyInstanceWithServiceAndClusters() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); - when(proxy.subscribe(anyString(), anyString(), anyString())).thenReturn(infoWithHealthyInstance); - + infoWithHealthyInstance.addHost(mockInstance("cluster1", true)); + infoWithHealthyInstance.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); String serviceName = "service1"; + when(proxy.subscribe(serviceName, Constants.DEFAULT_GROUP, StringUtils.EMPTY)).thenReturn( + infoWithHealthyInstance); List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.selectOneHealthyInstance(serviceName, clusterList); + Instance instance = client.selectOneHealthyInstance(serviceName, clusterList); //then - verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2"); + assertNotNull(instance); + assertEquals("cluster1", instance.getClusterName()); } @Test - void testSelectOneHealthyInstance6() throws NacosException { + void testSelectOneHealthyInstanceWithFullNameAndClusters() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); + infoWithHealthyInstance.addHost(mockInstance("cluster1", true)); + infoWithHealthyInstance.addHost(mockInstance(Constants.DEFAULT_CLUSTER_NAME, true)); when(proxy.subscribe(anyString(), anyString(), anyString())).thenReturn(infoWithHealthyInstance); String serviceName = "service1"; String groupName = "group1"; List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.selectOneHealthyInstance(serviceName, groupName, clusterList); + Instance instance = client.selectOneHealthyInstance(serviceName, groupName, clusterList); //then - verify(proxy, times(1)).subscribe(serviceName, groupName, "cluster1,cluster2"); - + assertNotNull(instance); + assertEquals("cluster1", instance.getClusterName()); } @Test - void testSelectOneHealthyInstance7() throws NacosException { + void testSelectOneHealthyInstanceWithServiceAndClustersNotSubscribe() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); + infoWithHealthyInstance.addHost(mockInstance("cluster1", true)); when(proxy.queryInstancesOfService(anyString(), anyString(), anyString(), anyBoolean())).thenReturn( infoWithHealthyInstance); String serviceName = "service1"; List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.selectOneHealthyInstance(serviceName, clusterList, false); + Instance instance = client.selectOneHealthyInstance(serviceName, clusterList, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, Constants.DEFAULT_GROUP, "cluster1,cluster2", - false); + assertNotNull(instance); + assertEquals("cluster1", instance.getClusterName()); } @Test - void testSelectOneHealthyInstance8() throws NacosException { + void testSelectOneHealthyInstanceWithFullNameAndClustersNotSubscribe() throws NacosException { //given - Instance healthyInstance = new Instance(); - healthyInstance.setIp("1.1.1.1"); - healthyInstance.setPort(1000); - List hosts = new ArrayList<>(); - hosts.add(healthyInstance); ServiceInfo infoWithHealthyInstance = new ServiceInfo(); - infoWithHealthyInstance.setHosts(hosts); + infoWithHealthyInstance.addHost(mockInstance("cluster1", true)); when(proxy.queryInstancesOfService(anyString(), anyString(), anyString(), anyBoolean())).thenReturn( infoWithHealthyInstance); @@ -789,13 +867,14 @@ void testSelectOneHealthyInstance8() throws NacosException { String groupName = "group1"; List clusterList = Arrays.asList("cluster1", "cluster2"); //when - client.selectOneHealthyInstance(serviceName, groupName, clusterList, false); + Instance instance = client.selectOneHealthyInstance(serviceName, groupName, clusterList, false); //then - verify(proxy, times(1)).queryInstancesOfService(serviceName, groupName, "cluster1,cluster2", false); + assertNotNull(instance); + assertEquals("cluster1", instance.getClusterName()); } @Test - void testSubscribe1() throws NacosException { + void testSubscribeOnlyService() throws NacosException { //given String serviceName = "service1"; EventListener listener = event -> { @@ -811,7 +890,7 @@ void testSubscribe1() throws NacosException { } @Test - void testSubscribe2() throws NacosException { + void testSubscribeFullName() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -828,7 +907,7 @@ void testSubscribe2() throws NacosException { } @Test - void testSubscribe3() throws NacosException { + void testSubscribeWithServiceAndClusters() throws NacosException { //given String serviceName = "service1"; List clusterList = Arrays.asList("cluster1", "cluster2"); @@ -845,7 +924,7 @@ void testSubscribe3() throws NacosException { } @Test - void testSubscribe4() throws NacosException { + void testSubscribeWithFullNameAndClusters() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -863,7 +942,22 @@ void testSubscribe4() throws NacosException { } @Test - public void testSubscribe5() throws NacosException { + public void testSubscribeWithServiceAndCustomSelector() throws NacosException { + String serviceName = "service1"; + EventListener listener = event -> { + + }; + //when + client.subscribe(serviceName, NamingSelectorFactory.HEALTHY_SELECTOR, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(serviceName, Constants.DEFAULT_GROUP, Constants.NULL, + NamingSelectorFactory.HEALTHY_SELECTOR, listener); + //then + verify(changeNotifier, times(1)).registerListener(Constants.DEFAULT_GROUP, serviceName, wrapper); + verify(proxy, times(1)).subscribe(serviceName, Constants.DEFAULT_GROUP, Constants.NULL); + } + + @Test + public void testSubscribeWithFullNameAndCustomSelector() throws NacosException { String serviceName = "service1"; String groupName = "group1"; EventListener listener = event -> { @@ -892,7 +986,19 @@ void testSubscribeWithNullListener() throws NacosException { } @Test - void testUnSubscribe1() throws NacosException { + void testSubscribeDuplicate() throws NacosException { + String serviceName = "service1"; + when(changeNotifier.isSubscribed(Constants.DEFAULT_GROUP, serviceName)).thenReturn(true); + ServiceInfo serviceInfo = new ServiceInfo(Constants.DEFAULT_GROUP + "@@" + serviceName); + serviceInfo.addHost(new Instance()); + when(serviceInfoHolder.getServiceInfo(serviceName, Constants.DEFAULT_GROUP, "")).thenReturn(serviceInfo); + final AtomicBoolean flag = new AtomicBoolean(false); + client.subscribe(serviceName, event -> flag.set(true)); + assertTrue(flag.get()); + } + + @Test + void testUnSubscribeOnlyService() throws NacosException { //given String serviceName = "service1"; EventListener listener = event -> { @@ -909,7 +1015,7 @@ void testUnSubscribe1() throws NacosException { } @Test - void testUnSubscribe2() throws NacosException { + void testUnSubscribeFullName() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -928,7 +1034,7 @@ void testUnSubscribe2() throws NacosException { } @Test - void testUnSubscribe3() throws NacosException { + void testUnSubscribeWithServiceAndClusters() throws NacosException { //given String serviceName = "service1"; List clusterList = Arrays.asList("cluster1", "cluster2"); @@ -947,7 +1053,7 @@ void testUnSubscribe3() throws NacosException { } @Test - void testUnSubscribe4() throws NacosException { + void testUnSubscribeWithFullNameAndClusters() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -967,7 +1073,24 @@ void testUnSubscribe4() throws NacosException { } @Test - public void testUnSubscribe5() throws NacosException { + public void testUnSubscribeWithServiceAndCustomSelector() throws NacosException { + //given + String serviceName = "service1"; + EventListener listener = event -> { + + }; + when(changeNotifier.isSubscribed(Constants.DEFAULT_GROUP, serviceName)).thenReturn(false); + + //when + client.unsubscribe(serviceName, NamingSelectorFactory.HEALTHY_SELECTOR, listener); + NamingSelectorWrapper wrapper = new NamingSelectorWrapper(NamingSelectorFactory.HEALTHY_SELECTOR, listener); + //then + verify(changeNotifier, times(1)).deregisterListener(Constants.DEFAULT_GROUP, serviceName, wrapper); + verify(proxy, times(1)).unsubscribe(serviceName, Constants.DEFAULT_GROUP, Constants.NULL); + } + + @Test + public void testUnSubscribeWithFullNameAndCustomSelector() throws NacosException { //given String serviceName = "service1"; String groupName = "group1"; @@ -984,6 +1107,19 @@ public void testUnSubscribe5() throws NacosException { verify(proxy, times(1)).unsubscribe(serviceName, groupName, Constants.NULL); } + @Test + void testUnSubscribeWithNullListener() throws NacosException { + String serviceName = "service1"; + String groupName = "group1"; + //when + client.unsubscribe(serviceName, groupName, null); + //then + verify(changeNotifier, never()).deregisterListener(groupName, serviceName, + new NamingSelectorWrapper(NamingSelectorFactory.newIpSelector(""), null)); + verify(proxy, never()).unsubscribe(serviceName, groupName, ""); + + } + @Test void testGetServicesOfServer1() throws NacosException { //given @@ -1091,4 +1227,11 @@ void testConstructorWithServerList() throws NacosException, NoSuchFieldException namingService.shutDown(); } } + + private Instance mockInstance(String clusterName, boolean healthy) { + Instance instance = new Instance(); + instance.setClusterName(clusterName); + instance.setHealthy(healthy); + return instance; + } } \ No newline at end of file diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java index 62314db5473..bdb9472cfbc 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/gprc/NamingGrpcClientProxyTest.java @@ -709,4 +709,21 @@ void testConfigAppNameLabels() throws Exception { String appName = config.labels().get(Constants.APPNAME); assertNotNull(appName); } + + @Test + void testResponseCode403Exception() throws NacosException { + Throwable exception = assertThrows(NacosException.class, () -> { + + when(this.rpcClient.request(Mockito.any())).thenReturn(ErrorResponse.build(403, "Invalid signature")); + + try { + client.registerService(SERVICE_NAME, GROUP_NAME, instance); + } catch (NacosException ex) { + assertNull(ex.getCause()); + + throw ex; + } + }); + assertTrue(exception.getMessage().contains("Invalid signature")); + } } diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxyTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxyTest.java index dc0fccc2681..4cebc7f72de 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxyTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/remote/http/NamingHttpClientProxyTest.java @@ -645,4 +645,29 @@ void testRegApiForDomain() throws NacosException { }); } + + @Test + void testCallServerFail403() throws Exception { + //given + NacosRestTemplate nacosRestTemplate = mock(NacosRestTemplate.class); + + when(nacosRestTemplate.exchangeForm(any(), any(), any(), any(), any(), any())).thenAnswer(invocationOnMock -> { + //return url + HttpRestResult res = new HttpRestResult(); + res.setMessage("Invalid signature"); + res.setCode(403); + return res; + }); + + final Field nacosRestTemplateField = NamingHttpClientProxy.class.getDeclaredField("nacosRestTemplate"); + nacosRestTemplateField.setAccessible(true); + nacosRestTemplateField.set(clientProxy, nacosRestTemplate); + String api = "/api"; + Map params = new HashMap<>(); + Map body = new HashMap<>(); + String method = HttpMethod.GET; + String curServer = "127.0.0.1"; + //then + assertThrows(NacosException.class, () -> clientProxy.callServer(api, params, body, curServer, method)); + } } diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java index 3c3aa27ad95..f4520de8801 100644 --- a/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/naming/selector/NamingSelectorFactoryTest.java @@ -24,7 +24,7 @@ import java.util.Arrays; import java.util.Collections; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -92,6 +92,7 @@ public void testNewIpSelector() { @Test public void testNewMetadataSelector() { Instance ins1 = new Instance(); + ins1.setMetadata(new LinkedHashMap<>()); ins1.addMetadata("a", "1"); ins1.addMetadata("b", "2"); Instance ins2 = new Instance(); @@ -102,7 +103,7 @@ public void testNewMetadataSelector() { NamingContext namingContext = mock(NamingContext.class); when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); - NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new HashMap() { + NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new LinkedHashMap() { { put("a", "1"); put("b", "2"); @@ -117,6 +118,7 @@ public void testNewMetadataSelector() { @Test public void testNewMetadataSelector2() { Instance ins1 = new Instance(); + ins1.setMetadata(new LinkedHashMap<>()); ins1.addMetadata("a", "1"); ins1.addMetadata("c", "3"); Instance ins2 = new Instance(); @@ -127,7 +129,7 @@ public void testNewMetadataSelector2() { NamingContext namingContext = mock(NamingContext.class); when(namingContext.getInstances()).thenReturn(Arrays.asList(ins1, ins2, ins3)); - NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new HashMap() { + NamingSelector metadataSelector = NamingSelectorFactory.newMetadataSelector(new LinkedHashMap() { { put("a", "1"); put("b", "2"); diff --git a/client/src/test/java/com/alibaba/nacos/client/naming/selector/ServiceInfoContextTest.java b/client/src/test/java/com/alibaba/nacos/client/naming/selector/ServiceInfoContextTest.java new file mode 100644 index 00000000000..a9e41e3b52c --- /dev/null +++ b/client/src/test/java/com/alibaba/nacos/client/naming/selector/ServiceInfoContextTest.java @@ -0,0 +1,37 @@ +/* + * 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.client.naming.selector; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class ServiceInfoContextTest { + + @Test + void testGetAll() { + ServiceInfo serviceInfo = ServiceInfo.fromKey("aaa@@bbb@@ccc,ddd"); + serviceInfo.addHost(new Instance()); + ServiceInfoContext context = new ServiceInfoContext(serviceInfo); + assertEquals(1, context.getInstances().size()); + assertEquals("aaa", context.getGroupName()); + assertEquals("bbb", context.getServiceName()); + assertEquals("ccc,ddd", context.getClusters()); + } +} \ No newline at end of file diff --git a/client/src/test/java/com/alibaba/nacos/client/security/SecurityProxyTest.java b/client/src/test/java/com/alibaba/nacos/client/security/SecurityProxyTest.java index 24288008357..ef9e1693e51 100644 --- a/client/src/test/java/com/alibaba/nacos/client/security/SecurityProxyTest.java +++ b/client/src/test/java/com/alibaba/nacos/client/security/SecurityProxyTest.java @@ -17,11 +17,16 @@ package com.alibaba.nacos.client.security; import com.alibaba.nacos.api.PropertyKeyConst; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.client.address.AbstractServerListManager; import com.alibaba.nacos.client.auth.impl.NacosAuthLoginConstant; +import com.alibaba.nacos.client.env.NacosClientProperties; import com.alibaba.nacos.common.http.HttpRestResult; import com.alibaba.nacos.common.http.client.NacosRestTemplate; import com.alibaba.nacos.common.http.param.Header; +import com.alibaba.nacos.plugin.auth.api.LoginIdentityContext; import com.alibaba.nacos.plugin.auth.api.RequestResource; +import com.alibaba.nacos.plugin.auth.spi.client.AbstractClientAuthService; import com.alibaba.nacos.plugin.auth.spi.client.ClientAuthPluginManager; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -65,7 +70,34 @@ void setUp() throws Exception { List serverList = new ArrayList<>(); serverList.add("localhost"); - securityProxy = new SecurityProxy(serverList, nacosRestTemplate); + NacosClientProperties properties = NacosClientProperties.PROTOTYPE.derive(new Properties()); + AbstractServerListManager serverListManager = new AbstractServerListManager(properties) { + @Override + protected String getModuleName() { + return "Test"; + } + + @Override + protected NacosRestTemplate getNacosRestTemplate() { + return nacosRestTemplate; + } + + @Override + public String genNextServer() { + return serverList.get(0); + } + + @Override + public String getCurrentServer() { + return serverList.get(0); + } + + @Override + public List getServerList() { + return serverList; + } + }; + securityProxy = new SecurityProxy(serverListManager, nacosRestTemplate); } @Test @@ -100,4 +132,37 @@ void testLoginWithoutAnyPlugin() throws NoSuchFieldException, IllegalAccessExcep Map header = securityProxy.getIdentityContext(new RequestResource()); assertTrue(header.isEmpty()); } + + @Test + void testReLogin() throws NoSuchFieldException, IllegalAccessException { + Field clientAuthPluginManagerField = SecurityProxy.class.getDeclaredField("clientAuthPluginManager"); + clientAuthPluginManagerField.setAccessible(true); + ClientAuthPluginManager clientAuthPluginManager = mock(ClientAuthPluginManager.class); + clientAuthPluginManagerField.set(securityProxy, clientAuthPluginManager); + when(clientAuthPluginManager.getAuthServiceSpiImplSet()).thenReturn(Collections.singleton(new AbstractClientAuthService() { + + private LoginIdentityContext loginIdentityContext; + + @Override + public Boolean login(Properties properties) { + return null; + } + + @Override + public LoginIdentityContext getLoginIdentityContext(RequestResource resource) { + if (loginIdentityContext == null) { + loginIdentityContext = new LoginIdentityContext(); + } + return loginIdentityContext; + } + + @Override + public void shutdown() throws NacosException { + + } + })); + securityProxy.reLogin(); + Map identityContext = securityProxy.getIdentityContext(new RequestResource()); + assertEquals(identityContext.get(NacosAuthLoginConstant.RELOGINFLAG), "true"); + } } diff --git a/common/src/main/java/com/alibaba/nacos/common/executor/ThreadPoolManager.java b/common/src/main/java/com/alibaba/nacos/common/executor/ThreadPoolManager.java index b03a2b5222b..83e8876f02e 100644 --- a/common/src/main/java/com/alibaba/nacos/common/executor/ThreadPoolManager.java +++ b/common/src/main/java/com/alibaba/nacos/common/executor/ThreadPoolManager.java @@ -51,9 +51,9 @@ public final class ThreadPoolManager { static { INSTANCE.init(); ThreadUtils.addShutdownHook(new Thread(() -> { - LOGGER.warn("[ThreadPoolManager] Start destroying ThreadPool"); + LOGGER.info("[ThreadPoolManager] Start destroying ThreadPool"); shutdown(); - LOGGER.warn("[ThreadPoolManager] Destruction of the end"); + LOGGER.info("[ThreadPoolManager] Completed destruction of ThreadPool"); })); } diff --git a/common/src/main/java/com/alibaba/nacos/common/http/HttpClientBeanHolder.java b/common/src/main/java/com/alibaba/nacos/common/http/HttpClientBeanHolder.java index 117bd1c8399..4126bccc21d 100644 --- a/common/src/main/java/com/alibaba/nacos/common/http/HttpClientBeanHolder.java +++ b/common/src/main/java/com/alibaba/nacos/common/http/HttpClientBeanHolder.java @@ -99,7 +99,7 @@ private static void shutdown() { if (!ALREADY_SHUTDOWN.compareAndSet(false, true)) { return; } - LOGGER.warn("[HttpClientBeanHolder] Start destroying common HttpClient"); + LOGGER.info("[HttpClientBeanHolder] Start destroying common HttpClient"); try { shutdown(DefaultHttpClientFactory.class.getName()); @@ -108,7 +108,7 @@ private static void shutdown() { ExceptionUtil.getStackTrace(ex)); } - LOGGER.warn("[HttpClientBeanHolder] Destruction of the end"); + LOGGER.info("[HttpClientBeanHolder] Completed destruction of HttpClient"); } /** diff --git a/common/src/main/java/com/alibaba/nacos/common/notify/NotifyCenter.java b/common/src/main/java/com/alibaba/nacos/common/notify/NotifyCenter.java index 72a5d1fdd62..6a1e01f1891 100644 --- a/common/src/main/java/com/alibaba/nacos/common/notify/NotifyCenter.java +++ b/common/src/main/java/com/alibaba/nacos/common/notify/NotifyCenter.java @@ -131,7 +131,7 @@ public static void shutdown() { if (!CLOSED.compareAndSet(false, true)) { return; } - LOGGER.warn("[NotifyCenter] Start destroying Publisher"); + LOGGER.info("[NotifyCenter] Start destroying Publisher"); for (Map.Entry entry : INSTANCE.publisherMap.entrySet()) { try { @@ -148,7 +148,7 @@ public static void shutdown() { LOGGER.error("[SharePublisher] shutdown has error : ", e); } - LOGGER.warn("[NotifyCenter] Destruction of the end"); + LOGGER.info("[NotifyCenter] Completed destruction of Publisher"); } /** diff --git a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java index b7612dbccde..5ad466a14b9 100644 --- a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java +++ b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClient.java @@ -374,7 +374,7 @@ public Connection connectToServer(ServerInfo serverInfo) { grpcConn.setChannel(managedChannel); //send a setup request. ConnectionSetupRequest conSetupRequest = new ConnectionSetupRequest(); - conSetupRequest.setClientVersion(VersionUtils.getFullClientVersion()); + conSetupRequest.setClientVersion(getClientVersion()); conSetupRequest.setLabels(super.getLabels()); // set ability table conSetupRequest.setAbilityTable( @@ -404,6 +404,10 @@ public Connection connectToServer(ServerInfo serverInfo) { return null; } + protected String getClientVersion() { + return VersionUtils.getFullClientVersion(); + } + /** * ability mode: sdk client or cluster client. * diff --git a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java index 69530afd833..b949352e5de 100644 --- a/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java +++ b/common/src/main/java/com/alibaba/nacos/common/remote/client/grpc/GrpcClusterClient.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.api.ability.constant.AbilityMode; import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.common.remote.client.RpcClientTlsConfig; +import com.alibaba.nacos.common.utils.VersionUtils; import java.util.Map; @@ -30,6 +31,8 @@ */ public class GrpcClusterClient extends GrpcClient { + private static final String CLUSTER_CLIENT_VERSION_PREFIX = "Nacos-Server:v"; + /** * Empty constructor. * @@ -71,6 +74,11 @@ protected AbilityMode abilityMode() { return AbilityMode.CLUSTER_CLIENT; } + @Override + protected String getClientVersion() { + return CLUSTER_CLIENT_VERSION_PREFIX + VersionUtils.version; + } + @Override public int rpcPortOffset() { return Integer.parseInt(System.getProperty(GrpcConstants.NACOS_SERVER_GRPC_PORT_OFFSET_KEY, diff --git a/common/src/main/java/com/alibaba/nacos/common/utils/NamespaceUtil.java b/common/src/main/java/com/alibaba/nacos/common/utils/NamespaceUtil.java index ea25935a54e..282cbc6910e 100644 --- a/common/src/main/java/com/alibaba/nacos/common/utils/NamespaceUtil.java +++ b/common/src/main/java/com/alibaba/nacos/common/utils/NamespaceUtil.java @@ -16,6 +16,8 @@ package com.alibaba.nacos.common.utils; +import com.alibaba.nacos.api.common.Constants; + /** * namespace(tenant) util. Because config and naming treat namespace(tenant) differently, this tool class can only be * used by the config module. @@ -24,28 +26,23 @@ * @date 2020/10/12 17:56 */ public class NamespaceUtil { - + private NamespaceUtil() { } - private static final String NAMESPACE_PUBLIC_KEY = "public"; - /** - * public id,默认值为 "". + * public id,默认值为 "public". */ - public static String namespaceDefaultId = ""; - - private static final String NAMESPACE_NULL_KEY = "null"; + public static String namespaceDefaultId = Constants.DEFAULT_NAMESPACE_ID; /** - * Treat the namespace(tenant) parameters with values of "public" and "null" as an empty string. + * Treat the namespace(tenant) parameters with values of empty string as "public". * * @param tenant namespace(tenant) id * @return java.lang.String A namespace(tenant) string processed */ public static String processNamespaceParameter(String tenant) { - if (StringUtils.isBlank(tenant) || NAMESPACE_PUBLIC_KEY.equalsIgnoreCase(tenant) - || NAMESPACE_NULL_KEY.equalsIgnoreCase(tenant)) { + if (StringUtils.isBlank(tenant)) { return getNamespaceDefaultId(); } return tenant.trim(); @@ -68,4 +65,14 @@ public static void setNamespaceDefaultId(String namespaceDefaultId) { public static String getNamespaceDefaultId() { return NamespaceUtil.namespaceDefaultId; } + + /** + * Judge whether is default namespaceId. + * + * @param namespaceId to check namespaceId + * @return {@code true} if equals default namespaceId, otherwise {@code false}. + */ + public static boolean isDefaultNamespaceId(String namespaceId) { + return StringUtils.equals(namespaceId, getNamespaceDefaultId()); + } } diff --git a/common/src/test/java/com/alibaba/nacos/common/notify/DefaultSharePublisherTest.java b/common/src/test/java/com/alibaba/nacos/common/notify/DefaultSharePublisherTest.java index 3d297743186..834c8e632a3 100644 --- a/common/src/test/java/com/alibaba/nacos/common/notify/DefaultSharePublisherTest.java +++ b/common/src/test/java/com/alibaba/nacos/common/notify/DefaultSharePublisherTest.java @@ -106,7 +106,7 @@ void testIgnoreExpiredEvent() throws InterruptedException { defaultSharePublisher.addSubscriber(smartSubscriber2, MockSlowEvent2.class); defaultSharePublisher.publish(mockSlowEvent1); defaultSharePublisher.publish(mockSlowEvent2); - TimeUnit.MILLISECONDS.sleep(1100); + TimeUnit.MILLISECONDS.sleep(1500); verify(smartSubscriber1).onEvent(mockSlowEvent1); verify(smartSubscriber2).onEvent(mockSlowEvent2); reset(smartSubscriber1); diff --git a/common/src/test/java/com/alibaba/nacos/common/utils/NamespaceUtilTest.java b/common/src/test/java/com/alibaba/nacos/common/utils/NamespaceUtilTest.java index 25f5325eea1..4e175eaa620 100644 --- a/common/src/test/java/com/alibaba/nacos/common/utils/NamespaceUtilTest.java +++ b/common/src/test/java/com/alibaba/nacos/common/utils/NamespaceUtilTest.java @@ -16,10 +16,13 @@ package com.alibaba.nacos.common.utils; +import com.alibaba.nacos.api.common.Constants; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; /** * test NamespaceUtil. @@ -31,18 +34,18 @@ class NamespaceUtilTest { @AfterEach void tearDown() { - NamespaceUtil.setNamespaceDefaultId(""); + NamespaceUtil.setNamespaceDefaultId(Constants.DEFAULT_NAMESPACE_ID); } @Test void testProcessTenantParameter() { String strPublic = "public"; + assertEquals(strPublic, NamespaceUtil.processNamespaceParameter(strPublic)); String strEmpty = ""; - assertEquals(strEmpty, NamespaceUtil.processNamespaceParameter(strPublic)); String strNull = "null"; - assertEquals(strEmpty, NamespaceUtil.processNamespaceParameter(strNull)); - assertEquals(strEmpty, NamespaceUtil.processNamespaceParameter(strEmpty)); - assertEquals(strEmpty, NamespaceUtil.processNamespaceParameter(null)); + assertEquals(strNull, NamespaceUtil.processNamespaceParameter(strNull)); + assertEquals(strPublic, NamespaceUtil.processNamespaceParameter(strEmpty)); + assertEquals(strPublic, NamespaceUtil.processNamespaceParameter(null)); String strAbc = "abc"; assertEquals(strAbc, NamespaceUtil.processNamespaceParameter(strAbc)); String strdef123 = "def123"; @@ -53,7 +56,9 @@ void testProcessTenantParameter() { @Test void testSetNamespaceDefaultId() { + assertTrue(NamespaceUtil.isDefaultNamespaceId(Constants.DEFAULT_NAMESPACE_ID)); NamespaceUtil.setNamespaceDefaultId("Deprecated"); assertEquals("Deprecated", NamespaceUtil.getNamespaceDefaultId()); + assertFalse(NamespaceUtil.isDefaultNamespaceId(Constants.DEFAULT_NAMESPACE_ID)); } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/configuration/NacosConfigConfiguration.java b/config/src/main/java/com/alibaba/nacos/config/server/configuration/NacosConfigConfiguration.java index 1c6038c2f72..537fc8e63b8 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/configuration/NacosConfigConfiguration.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/configuration/NacosConfigConfiguration.java @@ -18,12 +18,17 @@ import com.alibaba.nacos.config.server.filter.CircuitFilter; import com.alibaba.nacos.config.server.filter.NacosWebFilter; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.web.NacosWebBean; import com.alibaba.nacos.persistence.configuration.condition.ConditionDistributedEmbedStorage; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; +import javax.annotation.PostConstruct; + /** * Nacos Config {@link Configuration} includes required Spring components. * @@ -31,9 +36,22 @@ * @since 0.2.2 */ @Configuration +@NacosWebBean public class NacosConfigConfiguration { + private final ControllerMethodsCache methodsCache; + + public NacosConfigConfiguration(ControllerMethodsCache methodsCache) { + this.methodsCache = methodsCache; + } + + @PostConstruct + public void init() { + methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller"); + } + @Bean + @ConditionalOnProperty(name = "nacos.web.charset.filter", havingValue = "nacos", matchIfMissing = true) public FilterRegistrationBean nacosWebFilterRegistration() { FilterRegistrationBean registration = new FilterRegistrationBean<>(); registration.setFilter(nacosWebFilter()); diff --git a/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java b/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java index 889d58fd504..9e558c13809 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/constant/Constants.java @@ -292,6 +292,17 @@ public class Constants { public static final String CONFIG_SEARCH_ACCURATE = "accurate"; + /** + * Gray rule. + */ + public static final String GRAY_RULE_TYPE = "type"; + + public static final String GRAY_RULE_EXPR = "expr"; + + public static final String GRAY_RULE_VERSION = "version"; + + public static final String GRAY_RULE_PRIORITY = "priority"; + /** * default nacos encode. */ @@ -299,6 +310,13 @@ public class Constants { public static final String NACOS_PERSIST_ENCODE_KEY = "nacosPersistEncodingKey"; + /** + * config publish type. + */ + public static final String FORMAL = "formal"; + + public static final String GRAY = "gray"; + static String getPersistEncode() { String persistEncode = System.getenv(NACOS_PERSIST_ENCODE_KEY); if (StringUtils.isBlank(persistEncode)) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/constant/PropertiesConstant.java b/config/src/main/java/com/alibaba/nacos/config/server/constant/PropertiesConstant.java index 29ab7f38325..309d36b6de7 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/constant/PropertiesConstant.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/constant/PropertiesConstant.java @@ -64,5 +64,7 @@ public class PropertiesConstant { public static final String DUMP_CHANGE_WORKER_INTERVAL = "dumpChangeWorkerInterval"; public static final String CONFIG_RENTENTION_DAYS = "nacos.config.retention.days"; - + + public static final String GRAY_CAPATIBEL_MODEL = "nacos.config.gray.compatible.model"; + } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java index 6a23c054806..bf8a24c082f 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/CapacityController.java @@ -17,19 +17,21 @@ package com.alibaba.nacos.config.server.controller; import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.model.capacity.Capacity; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.config.server.service.capacity.CapacityService; -import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; +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.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RestController; import jakarta.servlet.http.HttpServletResponse; @@ -58,6 +60,7 @@ public CapacityController(CapacityService capacityService) { } @GetMapping + @Compatibility(apiType = ApiType.ADMIN_API) public RestResult getCapacity(HttpServletResponse response, @RequestParam(required = false) String group, @RequestParam(required = false) String tenant) { if (group == null && tenant == null) { @@ -100,6 +103,7 @@ public RestResult getCapacity(HttpServletResponse response, @RequestPa * Modify group or capacity of tenant, and init record when capacity information are still initial. */ @PostMapping + @Compatibility(apiType = ApiType.ADMIN_API) public RestResult updateCapacity(HttpServletResponse response, @RequestParam(required = false) String group, @RequestParam(required = false) String tenant, @RequestParam(required = false) Integer quota, @RequestParam(required = false) Integer maxSize, diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ClientMetricsController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ClientMetricsController.java index 24f425a5a34..6e21b7f2dea 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ClientMetricsController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ClientMetricsController.java @@ -27,6 +27,7 @@ import com.alibaba.nacos.common.http.param.Header; import com.alibaba.nacos.common.http.param.Query; import com.alibaba.nacos.common.model.RestResult; +import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; @@ -34,12 +35,14 @@ import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.core.cluster.Member; import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.remote.Connection; import com.alibaba.nacos.core.remote.ConnectionManager; import com.alibaba.nacos.core.utils.GenericType; 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 com.alibaba.nacos.sys.env.EnvUtil; import org.springframework.http.ResponseEntity; @@ -85,12 +88,14 @@ public ClientMetricsController(ServerMemberManager serverMemberManager, Connecti */ @GetMapping("/cluster") @Secured(resource = Constants.METRICS_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public ResponseEntity metric(@RequestParam("ip") String ip, @RequestParam(value = "dataId", required = false) String dataId, @RequestParam(value = "group", required = false) String group, @RequestParam(value = "tenant", required = false) String tenant) throws NacosException { ParamUtils.checkTenant(tenant); + tenant = NamespaceUtil.processNamespaceParameter(tenant); ParamUtils.checkParam(dataId, group, "default", "default"); Loggers.CORE.info("Get cluster config metrics received, ip={},dataId={},group={},tenant={}", ip, dataId, group, @@ -171,12 +176,14 @@ public void onCancel() { */ @GetMapping("/current") @Secured(resource = Constants.METRICS_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public Map getClientMetrics(@RequestParam("ip") String ip, @RequestParam(value = "dataId", required = false) String dataId, @RequestParam(value = "group", required = false) String group, @RequestParam(value = "tenant", required = false) String tenant) throws NacosException { ParamUtils.checkTenant(tenant); + tenant = NamespaceUtil.processNamespaceParameter(tenant); ParamUtils.checkParam(dataId, group, "default", "default"); Map metrics = new HashMap<>(16); diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java index 07d94906b41..16a58434b62 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/CommunicationController.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.config.server.controller; import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.model.SampleResult; @@ -24,9 +25,11 @@ import com.alibaba.nacos.config.server.remote.ConfigChangeListenContext; import com.alibaba.nacos.config.server.service.LongPollingService; import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.remote.Connection; import com.alibaba.nacos.core.remote.ConnectionManager; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -68,9 +71,11 @@ public CommunicationController(LongPollingService longPollingService, * Get client config information of subscriber in local machine. */ @GetMapping("/configWatchers") + @Compatibility(apiType = ApiType.INNER_API) public SampleResult getSubClientConfig(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false) String tenant, ModelMap modelMap) { group = StringUtils.isBlank(group) ? Constants.DEFAULT_GROUP : group; + tenant = NamespaceUtil.processNamespaceParameter(tenant); // long polling listeners. SampleResult result = longPollingService.getCollectSubscribleInfo(dataId, group, tenant); // rpc listeners. @@ -97,6 +102,7 @@ public SampleResult getSubClientConfig(@RequestParam("dataId") String dataId, @R * Get client config listener lists of subscriber in local machine. */ @GetMapping("/watcherConfigs") + @Compatibility(apiType = ApiType.INNER_API) public SampleResult getSubClientConfigByIp(HttpServletRequest request, HttpServletResponse response, @RequestParam("ip") String ip, ModelMap modelMap) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java index ec103ea258b..f3f5cd8fffe 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigController.java @@ -28,45 +28,54 @@ import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.constant.ParametersField; import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.ConfigAdvanceInfo; import com.alibaba.nacos.config.server.model.ConfigAllInfo; import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; import com.alibaba.nacos.config.server.model.ConfigMetadata; +import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; -import com.alibaba.nacos.config.server.paramcheck.ConfigBlurSearchHttpParamExtractor; -import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; -import com.alibaba.nacos.config.server.paramcheck.ConfigListenerHttpParamExtractor; -import com.alibaba.nacos.core.paramcheck.ExtractorManager; -import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.config.server.model.SameConfigPolicy; import com.alibaba.nacos.config.server.model.SampleResult; import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; -import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.model.form.ConfigForm; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; import com.alibaba.nacos.config.server.monitor.MetricsMonitor; +import com.alibaba.nacos.config.server.paramcheck.ConfigBlurSearchHttpParamExtractor; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.config.server.paramcheck.ConfigListenerHttpParamExtractor; import com.alibaba.nacos.config.server.result.code.ResultCodeEnum; import com.alibaba.nacos.config.server.service.ConfigChangePublisher; import com.alibaba.nacos.config.server.service.ConfigOperationService; import com.alibaba.nacos.config.server.service.ConfigSubService; -import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.GroupKey; import com.alibaba.nacos.config.server.utils.MD5Util; import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.config.server.utils.PropertyUtil; import com.alibaba.nacos.config.server.utils.RequestUtil; import com.alibaba.nacos.config.server.utils.TimeUtils; import com.alibaba.nacos.config.server.utils.YamlParserUtil; import com.alibaba.nacos.config.server.utils.ZipUtils; import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; 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 com.alibaba.nacos.plugin.encryption.handler.EncryptionHandler; import com.alibaba.nacos.sys.utils.InetUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.beans.BeanUtils; import org.springframework.http.HttpHeaders; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -122,6 +131,8 @@ public class ConfigController { private ConfigInfoBetaPersistService configInfoBetaPersistService; + private ConfigInfoGrayPersistService configInfoGrayPersistService; + private NamespacePersistService namespacePersistService; private final ConfigOperationService configOperationService; @@ -130,14 +141,15 @@ public class ConfigController { public ConfigController(ConfigServletInner inner, ConfigOperationService configOperationService, ConfigSubService configSubService, ConfigInfoPersistService configInfoPersistService, - NamespacePersistService namespacePersistService, - ConfigInfoBetaPersistService configInfoBetaPersistService) { + NamespacePersistService namespacePersistService, ConfigInfoBetaPersistService configInfoBetaPersistService, + ConfigInfoGrayPersistService configInfoGrayPersistService) { this.inner = inner; this.configOperationService = configOperationService; this.configSubService = configSubService; this.configInfoPersistService = configInfoPersistService; this.namespacePersistService = namespacePersistService; this.configInfoBetaPersistService = configInfoBetaPersistService; + this.configInfoGrayPersistService = configInfoGrayPersistService; } /** @@ -153,6 +165,7 @@ public ConfigController(ConfigServletInner inner, ConfigOperationService configO @PostMapping @TpsControl(pointName = "ConfigPublish") @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.OPEN_API) public Boolean publishConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @@ -177,6 +190,7 @@ public Boolean publishConfig(HttpServletRequest request, HttpServletResponse res } // check tenant + tenant = NamespaceUtil.processNamespaceParameter(tenant); ParamUtils.checkTenant(tenant); ParamUtils.checkParam(dataId, group, "datumId", content); ParamUtils.checkParam(tag); @@ -195,7 +209,6 @@ public Boolean publishConfig(HttpServletRequest request, HttpServletResponse res configForm.setEffect(effect); configForm.setType(type); configForm.setSchema(schema); - if (StringUtils.isBlank(srcUser)) { configForm.setSrcUser(RequestUtil.getSrcUserName(request)); } @@ -222,6 +235,7 @@ public Boolean publishConfig(HttpServletRequest request, HttpServletResponse res @GetMapping @TpsControl(pointName = "ConfigQuery") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.OPEN_API) public void getConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @@ -236,7 +250,7 @@ public void getConfig(HttpServletRequest request, HttpServletResponse response, final String clientIp = RequestUtil.getRemoteIp(request); String isNotify = request.getHeader("notify"); - inner.doGetConfig(request, response, dataId, group, tenant, tag, isNotify, clientIp); + inner.doGetConfig(request, response, dataId, group, tenant, tag, isNotify, clientIp, ApiVersionEnum.V1); } /** @@ -246,10 +260,12 @@ public void getConfig(HttpServletRequest request, HttpServletResponse response, */ @GetMapping(params = "show=all") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config") public ConfigAllInfo detailConfigInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant) throws NacosException { // check tenant + tenant = NamespaceUtil.processNamespaceParameter(tenant); ParamUtils.checkTenant(tenant); // check params ParamUtils.checkParam(dataId, group, "datumId", "content"); @@ -278,11 +294,13 @@ public ConfigAllInfo detailConfigInfo(@RequestParam("dataId") String dataId, @Re */ @DeleteMapping @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.OPEN_API) public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam(value = "tag", required = false) String tag) throws NacosException { // check tenant + tenant = NamespaceUtil.processNamespaceParameter(tenant); ParamUtils.checkTenant(tenant); ParamUtils.checkParam(dataId, group, "datumId", "rm"); ParamUtils.checkParam(tag); @@ -304,18 +322,19 @@ public Boolean deleteConfig(HttpServletRequest request, HttpServletResponse resp */ @DeleteMapping(params = "delType=ids") @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/batchDelete") public RestResult deleteConfigs(HttpServletRequest request, @RequestParam(value = "ids") List ids) { String clientIp = RequestUtil.getRemoteIp(request); String srcUser = RequestUtil.getSrcUserName(request); final Timestamp time = TimeUtils.getCurrentTime(); - List configInfoList = configInfoPersistService.removeConfigInfoByIds(ids, clientIp, srcUser); + List configInfoList = configInfoPersistService.removeConfigInfoByIds(ids, clientIp, srcUser); if (CollectionUtils.isEmpty(configInfoList)) { return RestResultUtils.success(true); } - for (ConfigInfo configInfo : configInfoList) { + for (ConfigAllInfo configInfo : configInfoList) { ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, configInfo.getDataId(), configInfo.getGroup(), - configInfo.getTenant(), time.getTime())); + new ConfigDataChangeEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), + time.getTime())); ConfigTraceService.logPersistenceEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), null, time.getTime(), clientIp, ConfigTraceService.PERSISTENCE_EVENT, @@ -326,9 +345,11 @@ public RestResult deleteConfigs(HttpServletRequest request, @RequestPar @GetMapping("/catalog") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public RestResult getConfigAdvanceInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant) { + tenant = NamespaceUtil.processNamespaceParameter(tenant); ConfigAdvanceInfo configInfo = configInfoPersistService.findConfigAdvanceInfo(dataId, group, tenant); return RestResultUtils.success(configInfo); } @@ -339,6 +360,7 @@ public RestResult getConfigAdvanceInfo(@RequestParam("dataId" @PostMapping("/listener") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) @ExtractorManager.Extractor(httpExtractor = ConfigListenerHttpParamExtractor.class) + @Compatibility(apiType = ApiType.OPEN_API) public void listener(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { @@ -367,10 +389,12 @@ public void listener(HttpServletRequest request, HttpServletResponse response) */ @GetMapping("/listener") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/listener") public GroupkeyListenserStatus getListeners(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false) String tenant, @RequestParam(value = "sampleTime", required = false, defaultValue = "1") int sampleTime) throws Exception { group = StringUtils.isBlank(group) ? Constants.DEFAULT_GROUP : group; + tenant = NamespaceUtil.processNamespaceParameter(tenant); SampleResult collectSampleResult = configSubService.getCollectSampleResult(dataId, group, tenant, sampleTime); GroupkeyListenserStatus gls = new GroupkeyListenserStatus(); gls.setCollectStatus(200); @@ -386,6 +410,7 @@ public GroupkeyListenserStatus getListeners(@RequestParam("dataId") String dataI @GetMapping(params = "search=accurate") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) @ExtractorManager.Extractor(httpExtractor = ConfigBlurSearchHttpParamExtractor.class) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/list") public Page searchConfig(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @@ -398,6 +423,7 @@ public Page searchConfig(@RequestParam("dataId") String dataId, @Req if (StringUtils.isNotBlank(configTags)) { configAdvanceInfo.put("config_tags", configTags); } + tenant = NamespaceUtil.processNamespaceParameter(tenant); try { return configInfoPersistService.findConfigInfo4Page(pageNo, pageSize, dataId, group, tenant, configAdvanceInfo); @@ -415,12 +441,13 @@ public Page searchConfig(@RequestParam("dataId") String dataId, @Req @GetMapping(params = "search=blur") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) @ExtractorManager.Extractor(httpExtractor = ConfigBlurSearchHttpParamExtractor.class) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/list") public Page fuzzySearchConfig(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam(value = "config_tags", required = false) String configTags, - @RequestParam(value = "types", required = false) String types, - @RequestParam("pageNo") int pageNo, @RequestParam("pageSize") int pageSize) { + @RequestParam(value = "types", required = false) String types, @RequestParam("pageNo") int pageNo, + @RequestParam("pageSize") int pageSize) { MetricsMonitor.getFuzzySearchMonitor().incrementAndGet(); Map configAdvanceInfo = new HashMap<>(50); if (StringUtils.isNotBlank(appName)) { @@ -432,6 +459,7 @@ public Page fuzzySearchConfig(@RequestParam("dataId") String dataId, if (StringUtils.isNotBlank(types)) { configAdvanceInfo.put(ParametersField.TYPES, types); } + tenant = NamespaceUtil.processNamespaceParameter(tenant); try { return configInfoPersistService.findConfigInfoLike4Page(pageNo, pageSize, dataId, group, tenant, configAdvanceInfo); @@ -452,21 +480,28 @@ public Page fuzzySearchConfig(@RequestParam("dataId") String dataId, */ @DeleteMapping(params = "beta=true") @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "DELETE ${contextPath:nacos}/v3/console/cs/config/beta") public RestResult stopBeta(HttpServletRequest httpServletRequest, @RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant) { String remoteIp = getRemoteIp(httpServletRequest); String requestIpApp = RequestUtil.getAppName(httpServletRequest); + tenant = NamespaceUtil.processNamespaceParameter(tenant); try { - configInfoBetaPersistService.removeConfigInfo4Beta(dataId, group, tenant); + configInfoGrayPersistService.removeConfigInfoGray(dataId, group, tenant, BetaGrayRule.TYPE_BETA, remoteIp, + RequestUtil.getSrcUserName(httpServletRequest)); } catch (Throwable e) { LOGGER.error("remove beta data error", e); return RestResultUtils.failed(500, false, "remove beta data error"); } ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp, System.currentTimeMillis(), remoteIp, ConfigTraceService.PERSISTENCE_EVENT_BETA, ConfigTraceService.PERSISTENCE_TYPE_REMOVE, null); + + if (PropertyUtil.isGrayCompatibleModel()) { + configInfoBetaPersistService.removeConfigInfo4Beta(dataId, group, tenant); + } ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(true, dataId, group, tenant, System.currentTimeMillis())); + new ConfigDataChangeEvent(dataId, group, tenant, BetaGrayRule.TYPE_BETA, System.currentTimeMillis())); return RestResultUtils.success("stop beta ok", true); } @@ -481,18 +516,26 @@ public RestResult stopBeta(HttpServletRequest httpServletRequest, */ @GetMapping(params = "beta=true") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/beta") public RestResult queryBeta(@RequestParam(value = "dataId") String dataId, @RequestParam(value = "group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant) { try { - ConfigInfo4Beta ci = configInfoBetaPersistService.findConfigInfo4Beta(dataId, group, tenant); - - if (Objects.nonNull(ci)) { - String encryptedDataKey = ci.getEncryptedDataKey(); - Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, ci.getContent()); - ci.setContent(pair.getSecond()); + tenant = NamespaceUtil.processNamespaceParameter(tenant); + ConfigInfo4Beta configInfo4Beta = null; + ConfigInfoGrayWrapper beta4Gray = configInfoGrayPersistService.findConfigInfo4Gray(dataId, group, tenant, + "beta"); + if (Objects.nonNull(beta4Gray)) { + String encryptedDataKey = beta4Gray.getEncryptedDataKey(); + Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, + beta4Gray.getContent()); + beta4Gray.setContent(pair.getSecond()); + configInfo4Beta = new ConfigInfo4Beta(); + BeanUtils.copyProperties(beta4Gray, configInfo4Beta); + configInfo4Beta.setBetaIps( + GrayRuleManager.deserializeConfigGrayPersistInfo(beta4Gray.getGrayRule()).getExpr()); } - return RestResultUtils.success("query beta ok", ci); + return RestResultUtils.success("query beta ok", configInfo4Beta); } catch (Throwable e) { LOGGER.error("query beta data error", e); return RestResultUtils.failed("query beta data error"); @@ -511,6 +554,7 @@ public RestResult queryBeta(@RequestParam(value = "dataId") Str */ @GetMapping(params = "export=true") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/export") public ResponseEntity exportConfig(@RequestParam(value = "dataId", required = false) String dataId, @RequestParam(value = "group", required = false) String group, @RequestParam(value = "appName", required = false) String appName, @@ -566,6 +610,7 @@ public ResponseEntity exportConfig(@RequestParam(value = "dataId", requi */ @GetMapping(params = "exportV2=true") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/export2") public ResponseEntity exportConfigV2(@RequestParam(value = "dataId", required = false) String dataId, @RequestParam(value = "group", required = false) String group, @RequestParam(value = "appName", required = false) String appName, @@ -615,6 +660,7 @@ public ResponseEntity exportConfigV2(@RequestParam(value = "dataId", req */ @PostMapping(params = "import=true") @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/import") public RestResult> importAndPublishConfig(HttpServletRequest request, @RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "namespace", required = false) String namespace, @@ -666,8 +712,8 @@ public RestResult> importAndPublishConfig(HttpServletRequest null, policy); for (ConfigInfo configInfo : configInfoList) { ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, configInfo.getDataId(), configInfo.getGroup(), - configInfo.getTenant(), time.getTime())); + new ConfigDataChangeEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), + time.getTime())); ConfigTraceService.logPersistenceEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), requestIpApp, time.getTime(), InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT, ConfigTraceService.PERSISTENCE_TYPE_PUB, @@ -854,6 +900,7 @@ private RestResult> parseImportDataV2(String srcUser, ZipUti */ @PostMapping(params = "clone=true") @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/clone") public RestResult> cloneConfig(HttpServletRequest request, @RequestParam(value = "src_user", required = false) String srcUser, @RequestParam(value = "tenant") String namespace, @@ -918,8 +965,8 @@ public RestResult> cloneConfig(HttpServletRequest request, srcIp, null, policy); for (ConfigInfo configInfo : configInfoList4Clone) { ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, configInfo.getDataId(), configInfo.getGroup(), - configInfo.getTenant(), time.getTime())); + new ConfigDataChangeEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), + time.getTime())); ConfigTraceService.logPersistenceEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), requestIpApp, time.getTime(), InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT, ConfigTraceService.PERSISTENCE_TYPE_PUB, diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigOpsController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigOpsController.java index 89523ae9134..139d202c277 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigOpsController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigOpsController.java @@ -26,6 +26,7 @@ import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.config.server.service.dump.DumpService; import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.persistence.configuration.DatasourceConfiguration; @@ -34,6 +35,7 @@ import com.alibaba.nacos.persistence.model.event.DerbyImportEvent; import com.alibaba.nacos.persistence.repository.embedded.operate.DatabaseOperate; 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 com.alibaba.nacos.sys.utils.ApplicationUtils; import org.slf4j.Logger; @@ -76,6 +78,7 @@ public ConfigOpsController(DumpService dumpService) { */ @PostMapping(value = "/localCache") @Secured(resource = Constants.OPS_CONTROLLER_PATH, action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.ADMIN_API) public String updateLocalCacheFromStore() { LOGGER.info("start to dump all data from store."); dumpService.dumpAll(); @@ -85,6 +88,7 @@ public String updateLocalCacheFromStore() { @PutMapping(value = "/log") @Secured(resource = Constants.OPS_CONTROLLER_PATH, action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.ADMIN_API) public String setLogLevel(@RequestParam String logName, @RequestParam String logLevel) { LogUtil.setLogLevel(logName, logLevel); return HttpServletResponse.SC_OK + ""; @@ -99,6 +103,7 @@ public String setLogLevel(@RequestParam String logName, @RequestParam String log */ @GetMapping(value = "/derby") @Secured(action = ActionTypes.READ, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public RestResult derbyOps(@RequestParam(value = "sql") String sql) { String selectSign = "SELECT"; String limitSign = "ROWS FETCH NEXT"; @@ -138,6 +143,7 @@ public RestResult derbyOps(@RequestParam(value = "sql") String sql) { */ @PostMapping(value = "/data/removal") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public DeferredResult> importDerby(@RequestParam(value = "file") MultipartFile multipartFile) { DeferredResult> response = new DeferredResult<>(); if (!DatasourceConfiguration.isEmbeddedStorage()) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java index 1afa4ba1449..2501a64003d 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ConfigServletInner.java @@ -24,19 +24,22 @@ import com.alibaba.nacos.common.utils.Pair; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.enums.FileTypeEnum; -import com.alibaba.nacos.config.server.model.CacheItem; -import com.alibaba.nacos.config.server.model.ConfigCache; -import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.config.server.exception.NacosConfigException; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.query.ConfigChainRequestExtractorService; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; import com.alibaba.nacos.config.server.service.LongPollingService; -import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.enums.ResponseCode; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; -import com.alibaba.nacos.config.server.utils.GroupKey2; -import com.alibaba.nacos.config.server.utils.LogUtil; import com.alibaba.nacos.config.server.utils.MD5Util; import com.alibaba.nacos.config.server.utils.Protocol; import com.alibaba.nacos.config.server.utils.RequestUtil; -import com.alibaba.nacos.config.server.utils.TimeUtils; import com.alibaba.nacos.plugin.encryption.handler.EncryptionHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -52,6 +55,9 @@ import java.util.List; import java.util.Map; +import static com.alibaba.nacos.api.common.Constants.CONFIG_TYPE; +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; +import static com.alibaba.nacos.config.server.constant.Constants.CONTENT_MD5; import static com.alibaba.nacos.config.server.utils.LogUtil.PULL_LOG; /** @@ -70,8 +76,11 @@ public class ConfigServletInner { private final LongPollingService longPollingService; - public ConfigServletInner(LongPollingService longPollingService) { + private final ConfigQueryChainService configQueryChainService; + + public ConfigServletInner(LongPollingService longPollingService, ConfigQueryChainService configQueryChainService) { this.longPollingService = longPollingService; + this.configQueryChainService = configQueryChainService; } /** @@ -115,159 +124,211 @@ public String doPollingConfig(HttpServletRequest request, HttpServletResponse re return HttpServletResponse.SC_OK + ""; } - /** - * Execute to get config [API V1]. - */ - public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group, - String tenant, String tag, String isNotify, String clientIp) throws IOException, ServletException { - return doGetConfig(request, response, dataId, group, tenant, tag, isNotify, clientIp, false); - } - /** * Execute to get config [API V1] or [API V2]. */ public String doGetConfig(HttpServletRequest request, HttpServletResponse response, String dataId, String group, - String tenant, String tag, String isNotify, String clientIp, boolean isV2) throws IOException { + String tenant, String tag, String isNotify, String clientIp, ApiVersionEnum apiVersion) throws IOException { + boolean notify = StringUtils.isNotBlank(isNotify) && Boolean.parseBoolean(isNotify); - if (isV2) { - response.setHeader(HttpHeaderConsts.CONTENT_TYPE, MediaType.APPLICATION_JSON); - } - final String groupKey = GroupKey2.getKey(dataId, group, tenant); - String autoTag = request.getHeader(com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG); String requestIpApp = RequestUtil.getAppName(request); - int lockResult = ConfigCacheService.tryConfigReadLock(groupKey); - CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey); - final String requestIp = RequestUtil.getRemoteIp(request); - if (lockResult > 0 && cacheItem != null) { - try { - return handleCacheItem(response, dataId, group, tenant, tag, - clientIp, isV2, notify, groupKey, autoTag, requestIpApp, cacheItem, requestIp); - } finally { - ConfigCacheService.releaseReadLock(groupKey); - } - } else if (lockResult == 0 || cacheItem == null) { - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT, - ConfigTraceService.PULL_TYPE_NOTFOUND, -1, requestIp, notify, "http"); - return get404Result(response, isV2); + + ConfigQueryChainRequest chainRequest = ConfigChainRequestExtractorService.getExtractor().extract(request); + ConfigQueryChainResponse chainResponse = configQueryChainService.handle(chainRequest); + + if (ResponseCode.FAIL.getCode() == chainResponse.getResultCode()) { + throw new NacosConfigException(chainResponse.getMessage()); + } + + logPullEvent(dataId, group, tenant, requestIpApp, chainResponse, clientIp, notify, tag); + + switch (chainResponse.getStatus()) { + case CONFIG_NOT_FOUND: + return handlerConfigNotFound(response, apiVersion); + case CONFIG_QUERY_CONFLICT: + return handlerConfigConflict(response, apiVersion); + default: + return handleResponse(response, chainResponse, dataId, group, apiVersion); + } + } + + private String handlerConfigNotFound(HttpServletResponse response, ApiVersionEnum apiVersion) throws IOException { + response.setStatus(HttpServletResponse.SC_NOT_FOUND); + if (apiVersion == ApiVersionEnum.V1) { + return writeResponseForV1(response, Result.failure(ErrorCode.RESOURCE_NOT_FOUND, "config data not exist")); } else { - PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey); - return get409Result(response, isV2); + return writeResponseForV2(response, Result.failure(ErrorCode.RESOURCE_NOT_FOUND, "config data not exist")); } } - private String handleCacheItem(HttpServletResponse response, String dataId, String group, - String tenant, String tag, String clientIp, boolean isV2, boolean notify, - String groupKey, String autoTag, String requestIpApp, CacheItem cacheItem, String requestIp) throws IOException { - long lastModified; - boolean isBeta = cacheItem.isBeta() && cacheItem.getConfigCacheBeta() != null && cacheItem.getIps4Beta() != null - && cacheItem.getIps4Beta().contains(clientIp); - final String configType = (null != cacheItem.getType()) ? cacheItem.getType() : FileTypeEnum.TEXT.getFileType(); - response.setHeader(com.alibaba.nacos.api.common.Constants.CONFIG_TYPE, configType); - FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(configType); - String contentTypeHeader = fileTypeEnum.getContentType(); - response.setHeader(HttpHeaderConsts.CONTENT_TYPE, isV2 ? MediaType.APPLICATION_JSON : contentTypeHeader); - String pullEvent; - String content; - String md5; - String encryptedDataKey; - if (isBeta) { - ConfigCache configCacheBeta = cacheItem.getConfigCacheBeta(); - pullEvent = ConfigTraceService.PULL_EVENT_BETA; - md5 = configCacheBeta.getMd5(Constants.ENCODE_UTF8); - lastModified = configCacheBeta.getLastModifiedTs(); - encryptedDataKey = configCacheBeta.getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance().getBetaContent(dataId, group, tenant); - response.setHeader("isBeta", "true"); + private String handlerConfigConflict(HttpServletResponse response, ApiVersionEnum apiVersion) throws IOException { + response.setStatus(HttpServletResponse.SC_CONFLICT); + if (apiVersion == ApiVersionEnum.V1) { + return writeResponseForV1(response, Result.failure(ErrorCode.RESOURCE_CONFLICT, "requested file is being modified, please try later.")); } else { - if (StringUtils.isBlank(tag)) { - if (isUseTag(cacheItem, autoTag)) { - ConfigCache configCacheTag = cacheItem.getConfigCacheTags().get(autoTag); - md5 = configCacheTag.getMd5(Constants.ENCODE_UTF8); - lastModified = configCacheTag.getLastModifiedTs(); - encryptedDataKey = configCacheTag.getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance().getTagContent(dataId, group, tenant, autoTag); - pullEvent = ConfigTraceService.PULL_EVENT_TAG + "-" + autoTag; - response.setHeader(com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG, - URLEncoder.encode(autoTag, StandardCharsets.UTF_8.displayName())); - } else { - pullEvent = ConfigTraceService.PULL_EVENT; - md5 = cacheItem.getConfigCache().getMd5(Constants.ENCODE_UTF8); - lastModified = cacheItem.getConfigCache().getLastModifiedTs(); - encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); - } - } else { - md5 = cacheItem.getTagMd5(tag, Constants.ENCODE_UTF8); - lastModified = cacheItem.getTagLastModified(tag); - encryptedDataKey = cacheItem.getTagEncryptedDataKey(tag); - content = ConfigDiskServiceFactory.getInstance().getTagContent(dataId, group, tenant, tag); - pullEvent = ConfigTraceService.PULL_EVENT_TAG + "-" + tag; - } + return writeResponseForV2(response, Result.failure(ErrorCode.RESOURCE_CONFLICT, "requested file is being modified, please try later.")); } - if (content == null) { - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, - pullEvent, ConfigTraceService.PULL_TYPE_NOTFOUND, -1, requestIp, notify, "http"); - return get404Result(response, isV2); + } + + private String handleResponse(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String dataId, + String group, ApiVersionEnum apiVersion) throws IOException { + if (apiVersion == ApiVersionEnum.V1) { + return handleResponseForV1(response, chainResponse, dataId, group); + } else { + return handleResponseForV2(response, chainResponse, dataId, group); } - setResponse(response, md5, lastModified, encryptedDataKey, isV2, dataId, content); - LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, requestIp, md5, TimeUtils.getCurrentTimeStr()); - final long delayed = notify ? -1 : System.currentTimeMillis() - lastModified; - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified, - pullEvent, ConfigTraceService.PULL_TYPE_OK, delayed, clientIp, notify, "http"); + } + + private String handleResponseForV1(HttpServletResponse response, ConfigQueryChainResponse chainResponse, + String dataId, String tag) throws IOException { + if (chainResponse.getContent() == null) { + return handlerConfigNotFound(response, ApiVersionEnum.V1); + } + + setCommonResponseHead(response, chainResponse, tag); + setResponseHeadForV1(response, chainResponse); + writeContentForV1(response, chainResponse, dataId); + return HttpServletResponse.SC_OK + ""; } - private void setResponse(HttpServletResponse response, String md5, - long lastModified, String encryptedDataKey, boolean isV2, String dataId, String content) + private String handleResponseForV2(HttpServletResponse response, ConfigQueryChainResponse chainResponse, + String dataId, String tag) throws IOException { + if (chainResponse.getContent() == null) { + return handlerConfigNotFound(response, ApiVersionEnum.V2); + } + + setCommonResponseHead(response, chainResponse, tag); + setResponseHeadForV2(response); + writeContentForV2(response, chainResponse, dataId); + + return HttpServletResponse.SC_OK + ""; + } + + private void setResponseHeadForV1(HttpServletResponse response, ConfigQueryChainResponse chainResponse) { + String contentType = chainResponse.getContentType() != null ? chainResponse.getContentType() : FileTypeEnum.TEXT.getFileType(); + FileTypeEnum fileTypeEnum = FileTypeEnum.getFileTypeEnumByFileExtensionOrFileType(contentType); + String contentTypeHeader = fileTypeEnum.getContentType(); + response.setHeader(HttpHeaderConsts.CONTENT_TYPE, contentTypeHeader); + } + + private void setResponseHeadForV2(HttpServletResponse response) { + response.setHeader(HttpHeaderConsts.CONTENT_TYPE, MediaType.APPLICATION_JSON); + } + + private void writeContentForV1(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String dataId) throws IOException { - response.setHeader(Constants.CONTENT_MD5, md5); - // Disable cache. - response.setHeader("Pragma", "no-cache"); - response.setDateHeader("Expires", 0); - response.setHeader("Cache-Control", "no-cache,no-store"); - response.setDateHeader("Last-Modified", lastModified); - if (encryptedDataKey != null) { - response.setHeader("Encrypted-Data-Key", encryptedDataKey); + PrintWriter out = response.getWriter(); + try { + String decryptContent = getDecryptContent(chainResponse, dataId); + out.print(decryptContent); + } finally { + out.flush(); + out.close(); } - PrintWriter out; - Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, content); - String decryptContent = pair.getSecond(); - out = response.getWriter(); - if (isV2) { + } + + private void writeContentForV2(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String dataId) + throws IOException { + PrintWriter out = response.getWriter(); + try { + String decryptContent = getDecryptContent(chainResponse, dataId); out.print(JacksonUtils.toJson(Result.success(decryptContent))); - } else { - out.print(decryptContent); + } finally { + out.flush(); + out.close(); } - out.flush(); - out.close(); } - private String get404Result(HttpServletResponse response, boolean isV2) throws IOException { - response.setStatus(HttpServletResponse.SC_NOT_FOUND); + private static String getDecryptContent(ConfigQueryChainResponse chainResponse, String dataId) { + Pair pair = EncryptionHandler.decryptHandler(dataId, chainResponse.getEncryptedDataKey(), + chainResponse.getContent()); + return pair.getSecond(); + } + + private String writeResponseForV1(HttpServletResponse response, Result result) throws IOException { PrintWriter writer = response.getWriter(); - if (isV2) { - writer.println(JacksonUtils.toJson(Result.failure(ErrorCode.RESOURCE_NOT_FOUND, "config data not exist"))); - } else { - writer.println("config data not exist"); - } - return HttpServletResponse.SC_NOT_FOUND + ""; + writer.println(result.getData()); + return response.getStatus() + ""; } - private String get409Result(HttpServletResponse response, boolean isV2) throws IOException { - response.setStatus(HttpServletResponse.SC_CONFLICT); + private String writeResponseForV2(HttpServletResponse response, Result result) throws IOException { PrintWriter writer = response.getWriter(); - if (isV2) { - writer.println(JacksonUtils.toJson(Result.failure(ErrorCode.RESOURCE_CONFLICT, - "requested file is being modified, please try later."))); - } else { - writer.println("requested file is being modified, please try later."); + writer.println(JacksonUtils.toJson(result)); + return response.getStatus() + ""; + } + + private String resolvePullEvent(ConfigQueryChainResponse chainResponse, String tag) { + switch (chainResponse.getStatus()) { + case CONFIG_FOUND_GRAY: + ConfigCacheGray matchedGray = chainResponse.getMatchedGray(); + if (matchedGray != null) { + return ConfigTraceService.PULL_EVENT + "-" + matchedGray.getGrayName(); + } else { + return ConfigTraceService.PULL_EVENT; + } + case SPECIAL_TAG_CONFIG_NOT_FOUND: + return ConfigTraceService.PULL_EVENT + "-" + TagGrayRule.TYPE_TAG + "-" + tag; + default: + return ConfigTraceService.PULL_EVENT; } - return HttpServletResponse.SC_CONFLICT + ""; } - private static boolean isUseTag(CacheItem cacheItem, String tag) { - return cacheItem != null && cacheItem.getConfigCacheTags() != null && cacheItem.getConfigCacheTags() - .containsKey(tag); + private void logPullEvent(String dataId, String group, String tenant, String requestIpApp, + ConfigQueryChainResponse chainResponse, String clientIp, boolean notify, String tag) { + + String pullEvent = resolvePullEvent(chainResponse, tag); + + ConfigQueryChainResponse.ConfigQueryStatus status = chainResponse.getStatus(); + + if (status == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_QUERY_CONFLICT) { + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, pullEvent, + ConfigTraceService.PULL_TYPE_CONFLICT, -1, clientIp, notify, "http"); + } else if (status == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_NOT_FOUND || chainResponse.getContent() == null) { + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, pullEvent, + ConfigTraceService.PULL_TYPE_NOTFOUND, -1, clientIp, notify, "http"); + } else { + long delayed = notify ? -1 : System.currentTimeMillis() - chainResponse.getLastModified(); + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, chainResponse.getLastModified(), pullEvent, + ConfigTraceService.PULL_TYPE_OK, delayed, clientIp, notify, "http"); + } } + private void setCommonResponseHead(HttpServletResponse response, ConfigQueryChainResponse chainResponse, String tag) { + String contentType = chainResponse.getContentType() != null ? chainResponse.getContentType() : FileTypeEnum.TEXT.getFileType(); + + response.setHeader(CONFIG_TYPE, contentType); + response.setHeader(CONTENT_MD5, chainResponse.getMd5()); + response.setHeader("Pragma", "no-cache"); + response.setDateHeader("Expires", 0); + response.setHeader("Cache-Control", "no-cache,no-store"); + response.setDateHeader("Last-Modified", chainResponse.getLastModified()); + + if (chainResponse.getEncryptedDataKey() != null) { + response.setHeader("Encrypted-Data-Key", chainResponse.getEncryptedDataKey()); + } + + // Check if there is a matched gray rule + if (ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_GRAY == chainResponse.getStatus()) { + if (BetaGrayRule.TYPE_BETA.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + response.setHeader("isBeta", "true"); + } else if (TagGrayRule.TYPE_TAG.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + try { + response.setHeader(TagGrayRule.TYPE_TAG, URLEncoder.encode(chainResponse.getMatchedGray().getGrayRule().getRawGrayRuleExp(), + StandardCharsets.UTF_8.displayName())); + } catch (Exception e) { + LOGGER.error("Error encoding tag", e); + } + } + } + + // Check if there is a special tag + if (ConfigQueryChainResponse.ConfigQueryStatus.SPECIAL_TAG_CONFIG_NOT_FOUND == chainResponse.getStatus()) { + try { + response.setHeader(VIPSERVER_TAG, URLEncoder.encode(tag, StandardCharsets.UTF_8.displayName())); + } catch (Exception e) { + LOGGER.error("Error encoding tag", e); + } + } + } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java index 7a6af68f60d..dffb86869bd 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/HealthController.java @@ -19,10 +19,12 @@ import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.core.cluster.MemberLookup; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.persistence.datasource.DataSourceService; import com.alibaba.nacos.persistence.datasource.DynamicDataSource; -import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.sys.utils.InetUtils; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -61,6 +63,7 @@ public void init() { } @GetMapping + @Compatibility(apiType = ApiType.ADMIN_API) public String getHealth() { // TODO UP DOWN WARN StringBuilder sb = new StringBuilder(); diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java index 81ea3984d36..ea2af1106f2 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/HistoryController.java @@ -23,11 +23,13 @@ import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; -import com.alibaba.nacos.core.paramcheck.ExtractorManager; -import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.config.server.service.HistoryService; import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; 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 com.alibaba.nacos.plugin.auth.exception.AccessException; import org.springframework.ui.ModelMap; @@ -69,12 +71,14 @@ public HistoryController(HistoryService historyService) { */ @GetMapping(params = "search=accurate") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/history/list") public Page listConfigHistory(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "pageNo", required = false) Integer pageNo, @RequestParam(value = "pageSize", required = false) Integer pageSize, ModelMap modelMap) { + tenant = NamespaceUtil.processNamespaceParameter(tenant); pageNo = null == pageNo ? 1 : pageNo; pageSize = null == pageSize ? 100 : pageSize; pageSize = Math.min(500, pageSize); @@ -94,13 +98,15 @@ public Page listConfigHistory(@RequestParam("dataId") String */ @GetMapping @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/history") public ConfigHistoryInfo getConfigHistoryInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam("nid") Long nid) throws AccessException { + tenant = NamespaceUtil.processNamespaceParameter(tenant); return historyService.getConfigHistoryInfo(dataId, group, tenant, nid); } - + /** * Query previous config history information. notes: * @@ -114,10 +120,12 @@ public ConfigHistoryInfo getConfigHistoryInfo(@RequestParam("dataId") String dat */ @GetMapping(value = "/previous") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/history/previous") public ConfigHistoryInfo getPreviousConfigHistoryInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @RequestParam("id") Long id) throws AccessException { + tenant = NamespaceUtil.processNamespaceParameter(tenant); return historyService.getPreviousConfigHistoryInfo(dataId, group, tenant, id); } @@ -130,6 +138,7 @@ public ConfigHistoryInfo getPreviousConfigHistoryInfo(@RequestParam("dataId") St */ @GetMapping(value = "/configs") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/history/configs") public List getDataIds(@RequestParam("tenant") String tenant) { // check tenant ParamUtils.checkTenant(tenant); diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java index db3a3f71e78..3e8d0b430a5 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/ListenerController.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.config.server.controller; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; @@ -24,8 +25,10 @@ import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; import com.alibaba.nacos.config.server.service.ConfigSubService; import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; 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.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; @@ -57,6 +60,7 @@ public ListenerController(ConfigSubService configSubService) { */ @GetMapping @Secured(resource = Constants.LISTENER_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/listener/ip") public GroupkeyListenserStatus getAllSubClientConfigByIp(@RequestParam("ip") String ip, @RequestParam(value = "all", required = false) boolean all, @RequestParam(value = "tenant", required = false) String tenant, @@ -69,6 +73,7 @@ public GroupkeyListenserStatus getAllSubClientConfigByIp(@RequestParam("ip") Str return gls; } Map status = collectSampleResult.getLisentersGroupkeyStatus(); + tenant = NamespaceUtil.processNamespaceParameter(tenant); for (Map.Entry config : status.entrySet()) { if (!StringUtils.isBlank(tenant) && config.getKey().contains(tenant)) { configMd5Status.put(config.getKey(), config.getValue()); diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java index 29541294d46..d5545d9c6d2 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2.java @@ -27,10 +27,12 @@ import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.controller.ConfigServletInner; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.paramcheck.ConfigBlurSearchHttpParamExtractor; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.config.server.model.form.ConfigForm; @@ -39,6 +41,7 @@ import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.config.server.utils.RequestUtil; 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 com.alibaba.nacos.plugin.encryption.handler.EncryptionHandler; import org.slf4j.Logger; @@ -92,6 +95,7 @@ public ConfigControllerV2(ConfigServletInner inner, ConfigOperationService confi */ @GetMapping @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public void getConfig(HttpServletRequest request, HttpServletResponse response, @RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -105,7 +109,7 @@ public void getConfig(HttpServletRequest request, HttpServletResponse response, ParamUtils.checkParamV2(tag); final String clientIp = RequestUtil.getRemoteIp(request); String isNotify = request.getHeader("notify"); - inner.doGetConfig(request, response, dataId, group, namespaceId, tag, isNotify, clientIp, true); + inner.doGetConfig(request, response, dataId, group, namespaceId, tag, isNotify, clientIp, ApiVersionEnum.V2); } /** @@ -115,9 +119,10 @@ public void getConfig(HttpServletRequest request, HttpServletResponse response, */ @PostMapping() @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public Result publishConfig(ConfigForm configForm, HttpServletRequest request) throws NacosException { // check required field - configForm.validate(); + configForm.validateWithContent(); String encryptedDataKeyFinal = configForm.getEncryptedDataKey(); if (StringUtils.isBlank(encryptedDataKeyFinal)) { // encrypted @@ -155,6 +160,7 @@ public Result publishConfig(ConfigForm configForm, HttpServletRequest r */ @DeleteMapping @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public Result deleteConfig(HttpServletRequest request, @RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -178,6 +184,7 @@ public Result deleteConfig(HttpServletRequest request, @RequestParam("d @GetMapping("/searchDetail") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) @ExtractorManager.Extractor(httpExtractor = ConfigBlurSearchHttpParamExtractor.class) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/cs/config/searchDetail") public Page searchConfigByDetails(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "appName", required = false) String appName, @RequestParam(value = "tenant", required = false, defaultValue = StringUtils.EMPTY) String tenant, @@ -195,6 +202,7 @@ public Page searchConfigByDetails(@RequestParam("dataId") String dat if (StringUtils.isNotBlank(configDetail)) { configAdvanceInfo.put("content", configDetail); } + tenant = NamespaceUtil.processNamespaceParameter(tenant); try { return configDetailService.findConfigInfoPage(search, pageNo, pageSize, dataId, group, tenant, configAdvanceInfo); } catch (Exception e) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2.java b/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2.java index 9cf181de196..3776f6e523d 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2.java @@ -25,13 +25,16 @@ import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfoDetail; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.config.server.service.HistoryService; import com.alibaba.nacos.config.server.utils.ParamUtils; 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 com.alibaba.nacos.plugin.auth.exception.AccessException; import org.springframework.dao.DataAccessException; @@ -74,6 +77,7 @@ public HistoryControllerV2(HistoryService historyService) { */ @GetMapping("/list") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public Result> listConfigHistory(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -96,6 +100,7 @@ public Result> listConfigHistory(@RequestParam("dataId") */ @GetMapping @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public Result getConfigHistoryInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -112,6 +117,27 @@ public Result getConfigHistoryInfo(@RequestParam("dataId") St return Result.success(configHistoryInfo); } + + /** + * Query the detailed configuration history information pair, including the original version and the updated version. notes: + * + * @param nid history_config_info nid + * @param dataId dataId @since 2.0.3 + * @param group groupId @since 2.0.3 + * @param namespaceId namespaceId @since 2.0.3 + * @return history config info + * @since 2.0.3 add {@link Secured}, dataId, groupId and tenant for history config permission check. + */ + @GetMapping(value = "/detail") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + public Result getConfigHistoryInfoDetail(@RequestParam("dataId") String dataId, + @RequestParam("group") String group, + @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, + @RequestParam(value = "nid") Long nid) throws AccessException { + return Result.success(historyService.getConfigHistoryInfoDetail(dataId, group, namespaceId, nid)); + } + + /** * Query previous config history information. notes: * @@ -123,6 +149,7 @@ public Result getConfigHistoryInfo(@RequestParam("dataId") St */ @GetMapping(value = "/previous") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public Result getPreviousConfigHistoryInfo(@RequestParam("dataId") String dataId, @RequestParam("group") String group, @RequestParam(value = "namespaceId", required = false, defaultValue = StringUtils.EMPTY) String namespaceId, @@ -147,6 +174,7 @@ public Result getPreviousConfigHistoryInfo(@RequestParam("dat */ @GetMapping(value = "/configs") @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + @Compatibility(apiType = ApiType.ADMIN_API) public Result> getConfigsByTenant(@RequestParam("namespaceId") String namespaceId) throws NacosApiException { // check namespaceId diff --git a/config/src/main/java/com/alibaba/nacos/config/server/enums/ApiVersionEnum.java b/config/src/main/java/com/alibaba/nacos/config/server/enums/ApiVersionEnum.java new file mode 100644 index 00000000000..7235ace37b0 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/enums/ApiVersionEnum.java @@ -0,0 +1,44 @@ +/* + * Copyright 1999-$toady.year 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.config.server.enums; + +/** + * Config Api Version enum. + * @author Nacos + */ +public enum ApiVersionEnum { + + /** + * API version v1. + */ + V1("v1"), + + /** + * API version v2. + */ + V2("v2"); + + private final String version; + + ApiVersionEnum(String version) { + this.version = version; + } + + public String getVersion() { + return version; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/enums/OperationType.java b/config/src/main/java/com/alibaba/nacos/config/server/enums/OperationType.java new file mode 100644 index 00000000000..791c41ffdf9 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/enums/OperationType.java @@ -0,0 +1,58 @@ +/* + * 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.config.server.enums; + +/** + * Operation type enum. + * + * @author dirtybit + */ +public enum OperationType { + + /** + * Insert. + */ + INSERT("I"), + + /** + * Update. + */ + UPDATE("U"), + + /** + * Delete. + */ + DELETE("D"); + + /** + * operation type value. + */ + private String value; + + OperationType(String value) { + this.value = value; + } + + public void setValue(String value) { + this.value = value; + } + + public String getValue() { + return this.value; + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java b/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java index 3721a8b34ca..edb7d741a68 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/CacheItem.java @@ -19,10 +19,10 @@ import com.alibaba.nacos.config.server.utils.SimpleReadWriteLock; import com.alibaba.nacos.core.utils.StringPool; -import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; /** * Cache item. @@ -30,198 +30,102 @@ * @author Nacos */ public class CacheItem { - + final String groupKey; - + public String type; - - ConfigCache configCache = new ConfigCache(); - - /** - * Use for beta. - */ - public volatile boolean isBeta = false; - - public volatile List ips4Beta; - - ConfigCache configCacheBeta = null; - - /** - * Use for batch. - */ - public volatile boolean isBatch = false; - - public volatile int delimiter = 0; - - ConfigCache configCacheBatch = null; - + + ConfigCache configCache = ConfigCacheFactoryDelegate.getInstance().createConfigCache(); + /** - * Use for tag. + * Use for gray. */ - private volatile Map configCacheTags = null; - + private volatile Map configCacheGray = null; + + List sortedConfigCacheGrayList = null; + private final SimpleReadWriteLock rwLock = new SimpleReadWriteLock(); - + public CacheItem(String groupKey, String encryptedDataKey) { this.groupKey = StringPool.get(groupKey); this.getConfigCache().setEncryptedDataKey(encryptedDataKey); } - + public CacheItem(String groupKey) { this.groupKey = StringPool.get(groupKey); } - + public ConfigCache getConfigCache() { return configCache; } - - public boolean isBeta() { - return isBeta; - } - - public void setBeta(boolean isBeta) { - this.isBeta = isBeta; - } - - /** - * remove beta. - */ - public void removeBeta() { - this.isBeta = false; - this.ips4Beta = null; - configCacheBeta = null; - } - - public List getIps4Beta() { - return ips4Beta; - } - - public void setIps4Beta(List ips4Beta) { - this.ips4Beta = ips4Beta; - } - + public SimpleReadWriteLock getRwLock() { return rwLock; } - + public String getType() { return type; } - + public void setType(String type) { this.type = type; } - + public String getGroupKey() { return groupKey; } - + /** - * init beta cache if empty. + * init config gray if empty. */ - public void initBetaCacheIfEmpty() { - if (this.configCacheBeta == null) { - this.configCacheBeta = new ConfigCache(); - } - if (this.ips4Beta == null) { - this.ips4Beta = new ArrayList<>(); + public void initConfigGrayIfEmpty() { + if (this.configCacheGray == null) { + this.configCacheGray = new HashMap<>(4); } } - + /** - * get config cache beta. + * init config gray if empty. * - * @return + * @param grayName gray name. */ - public ConfigCache getConfigCacheBeta() { - return configCacheBeta; - } - - /** - * init batch cache if empty. - */ - public void initBatchCacheIfEmpty() { - if (this.configCacheBatch == null) { - this.configCacheBatch = new ConfigCache(); + public void initConfigGrayIfEmpty(String grayName) { + initConfigGrayIfEmpty(); + if (!this.configCacheGray.containsKey(grayName)) { + this.configCacheGray.put(grayName, ConfigCacheFactoryDelegate.getInstance().createConfigCacheGray(grayName)); } } - - public ConfigCache getConfigCacheBatch() { - return configCacheBatch; - } - - /** - * remove batch. - */ - public void removeBatch() { - this.configCacheBatch = null; - this.isBatch = false; - } - - /** - * init config tags if empty. - */ - public void initConfigTagsIfEmpty() { - if (this.getConfigCacheTags() == null) { - this.configCacheTags = new HashMap<>(16); - } + + public List getSortConfigGrays() { + return sortedConfigCacheGrayList; } - + /** - * init config tag if empty. - * - * @param tag tag. + * sort config gray. */ - public void initConfigTagsIfEmpty(String tag) { - initConfigTagsIfEmpty(); - if (!this.configCacheTags.containsKey(tag)) { - this.configCacheTags.put(tag, new ConfigCache()); + public void sortConfigGray() { + if (configCacheGray == null || configCacheGray.isEmpty()) { + sortedConfigCacheGrayList = null; + return; } + + sortedConfigCacheGrayList = configCacheGray.values().stream().sorted((o1, o2) -> { + if (o1.getPriority() != o2.getPriority()) { + return Integer.compare(o1.getPriority(), o2.getPriority()) * -1; + } else { + return o1.getGrayName().compareTo(o2.getGrayName()); + } + + }).collect(Collectors.toList()); } - - public void clearConfigTags() { - this.configCacheTags = null; - } - - public Map getConfigCacheTags() { - return configCacheTags; - } - - public boolean isBatch() { - return isBatch; - } - - public void setBatch(boolean batch) { - isBatch = batch; - } - - public int getDelimiter() { - return delimiter; - } - - public void setDelimiter(int delimiter) { - this.delimiter = delimiter; - } - - public long getTagLastModified(String tag) { - if (configCacheTags == null || !configCacheTags.containsKey(tag)) { - return -1L; - } - return configCacheTags.get(tag).getLastModifiedTs(); - } - - public String getTagEncryptedDataKey(String tag) { - if (configCacheTags == null || !configCacheTags.containsKey(tag)) { - return null; - } - return configCacheTags.get(tag).getEncryptedDataKey(); + + public Map getConfigCacheGray() { + return configCacheGray; } - - public String getTagMd5(String tag, String encode) { - if (configCacheTags == null || !configCacheTags.containsKey(tag)) { - return null; - } - return configCacheTags.get(tag).getMd5(encode); + + public void clearConfigGrays() { + this.configCacheGray = null; + this.sortedConfigCacheGrayList = null; } - + } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCache.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCache.java index 21e43ab23c7..c1aa1643ae6 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCache.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCache.java @@ -21,8 +21,6 @@ import java.io.Serializable; -import static java.nio.charset.StandardCharsets.UTF_8; - /** * config cache . * @@ -30,9 +28,7 @@ */ public class ConfigCache implements Serializable { - volatile String md5Gbk = Constants.NULL; - - volatile String md5Utf8 = Constants.NULL; + volatile String md5 = Constants.NULL; volatile String encryptedDataKey; @@ -42,8 +38,7 @@ public class ConfigCache implements Serializable { * clear cache. */ public void clear() { - this.md5Gbk = Constants.NULL; - this.md5Utf8 = Constants.NULL; + this.md5 = Constants.NULL; this.encryptedDataKey = null; this.lastModifiedTs = -1L; } @@ -51,12 +46,13 @@ public void clear() { public ConfigCache() { } - public String getMd5(String encode) { - if (UTF_8.name().equalsIgnoreCase(encode)) { - return md5Utf8; - } else { - return md5Gbk; - } + public ConfigCache(String md5, long lastModifiedTs) { + this.md5 = StringPool.get(md5); + this.lastModifiedTs = lastModifiedTs; + } + + public String getMd5() { + return md5; } public String getEncryptedDataKey() { @@ -67,26 +63,8 @@ public void setEncryptedDataKey(String encryptedDataKey) { this.encryptedDataKey = encryptedDataKey; } - public ConfigCache(String md5Gbk, String md5Utf8, long lastModifiedTs) { - this.md5Gbk = StringPool.get(md5Gbk); - this.md5Utf8 = StringPool.get(md5Utf8); - this.lastModifiedTs = lastModifiedTs; - } - - public String getMd5Gbk() { - return md5Gbk; - } - - public void setMd5Gbk(String md5Gbk) { - this.md5Gbk = StringPool.get(md5Gbk); - } - - public String getMd5Utf8() { - return md5Utf8; - } - - public void setMd5Utf8(String md5Utf8) { - this.md5Utf8 = StringPool.get(md5Utf8); + public void setMd5(String md5) { + this.md5 = StringPool.get(md5); } public long getLastModifiedTs() { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheFactory.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheFactory.java new file mode 100644 index 00000000000..bf9d86c8f66 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheFactory.java @@ -0,0 +1,46 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +/** + * The interface Config cache factory. + * + * @author Sunrisea + */ +public interface ConfigCacheFactory { + + /** + * Create config cache config cache. + * + * @return the config cache + */ + public ConfigCache createConfigCache(); + + /** + * Create config cache gray config cache gray. + * + * @return the config cache gray + */ + public ConfigCacheGray createConfigCacheGray(); + + /** + * Gets config cache factroy name. + * + * @return the config cache factory name + */ + public String getName(); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheFactoryDelegate.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheFactoryDelegate.java new file mode 100644 index 00000000000..643bfe9c38b --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheFactoryDelegate.java @@ -0,0 +1,119 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; + +/** + * The type Config cache factory delegate. + * + * @author Sunrisea + */ +public class ConfigCacheFactoryDelegate { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigCacheFactoryDelegate.class); + + private static final ConfigCacheFactoryDelegate INSTANCE = new ConfigCacheFactoryDelegate(); + + private String configCacheFactoryType = EnvUtil.getProperty("nacos.config.cache.type", "nacos"); + + private ConfigCacheFactory configCacheFactory = null; + + private ConfigCacheFactoryDelegate() { + Collection configCacheFactories = NacosServiceLoader.load(ConfigCacheFactory.class); + for (ConfigCacheFactory each : configCacheFactories) { + if (StringUtils.isEmpty(each.getName())) { + LOGGER.warn( + "[ConfigCacheFactoryDelegate] Load ConfigCacheFactory({}) ConfigFactroyName (null/empty) fail. " + + "Please add ConfigFactoryName to resolve", each.getClass().getName()); + continue; + } + LOGGER.info( + "[ConfigCacheFactoryDelegate] Load ConfigCacheFactory({}) ConfigCacheFactoryName({}) successfully. ", + each.getClass().getName(), each.getName()); + if (StringUtils.equals(configCacheFactoryType, each.getName())) { + LOGGER.info("[ConfigCacheFactoryDelegate] Matched ConfigCacheFactory found,set configCacheFactory={}", + each.getClass().getName()); + this.configCacheFactory = each; + } + } + if (this.configCacheFactory == null) { + LOGGER.info( + "[ConfigCacheFactoryDelegate] Matched ConfigCacheFactory not found, Load Default NacosConfigCacheFactory successfully."); + this.configCacheFactory = new NacosConfigCacheFactory(); + } + } + + /** + * Gets instance. + * + * @return the instance + */ + public static ConfigCacheFactoryDelegate getInstance() { + return INSTANCE; + } + + /** + * Create config cache config cache. + * + * @return the config cache + */ + public ConfigCache createConfigCache() { + return configCacheFactory.createConfigCache(); + } + + /** + * Create config cache config cache. + * + * @param md5 the md 5 + * @param lastModifiedTs the last modified ts + * @return the config cache + */ + public ConfigCache createConfigCache(String md5, long lastModifiedTs) { + ConfigCache configCache = this.createConfigCache(); + configCache.setMd5(md5); + configCache.setLastModifiedTs(lastModifiedTs); + return configCache; + } + + /** + * Create config cache gray config cache gray. + * + * @return the config cache gray + */ + public ConfigCacheGray createConfigCacheGray() { + return configCacheFactory.createConfigCacheGray(); + } + + /** + * Create config cache gray config cache gray. + * + * @param grayName the gray name + * @return the config cache gray + */ + public ConfigCacheGray createConfigCacheGray(String grayName) { + ConfigCacheGray configCacheGray = configCacheFactory.createConfigCacheGray(); + configCacheGray.setGrayName(grayName); + return configCacheGray; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheGray.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheGray.java new file mode 100644 index 00000000000..be2a8d561bb --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCacheGray.java @@ -0,0 +1,110 @@ +/* + * 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.config.server.model; + +import com.alibaba.nacos.config.server.model.gray.GrayRule; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; + +import java.io.Serializable; +import java.util.Map; + +/** + * extensible config cache. + * + * @author rong + */ +public class ConfigCacheGray extends ConfigCache implements Serializable { + + private String grayName; + + private GrayRule grayRule; + + /** + * clear cache. + */ + @Override + public void clear() { + super.clear(); + } + + public ConfigCacheGray() {} + + public ConfigCacheGray(String grayName) { + this.grayName = grayName; + } + + public GrayRule getGrayRule() { + return grayRule; + } + + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + + /** + * get raw gray rule from db. + * + * @return raw gray rule from db. + * @date 2024/3/14 + */ + public String getRawGrayRule() { + return grayRule.getRawGrayRuleExp(); + } + + /** + * reset gray rule. + * + * @param grayRule raw gray rule from db. + * @throws RuntimeException if gray rule is invalid. + * @date 2024/3/14 + */ + public void resetGrayRule(String grayRule) throws RuntimeException { + this.grayRule = GrayRuleManager.constructGrayRule(GrayRuleManager.deserializeConfigGrayPersistInfo(grayRule)); + if (this.grayRule == null || !this.grayRule.isValid()) { + throw new RuntimeException("raw gray rule is invalid"); + } + } + + /** + * judge whether match gray rule. + * + * @param tags conn tags. + * @return true if match, false otherwise. + * @date 2024/3/14 + */ + public boolean match(Map tags) { + return grayRule.match(tags); + } + + public int getPriority() { + return grayRule.getPriority(); + } + + /** + * if gray rule is valid. + * + * @return true if valid, false otherwise. + * @date 2024/3/14 + */ + public boolean isValid() { + return grayRule != null && grayRule.isValid(); + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessor.java new file mode 100644 index 00000000000..c21bdc54f99 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessor.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +/** + * The interface Config cache md5 post processor. + * + * @author Sunrisea + */ +public interface ConfigCachePostProcessor { + + /** + * Gets post processor name. + * + * @return the post processor name + */ + public String getName(); + + /** + * Post process. + * + * @param configCache the config cache + * @param content the content + */ + public void postProcess(ConfigCache configCache, String content); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessorDelegate.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessorDelegate.java new file mode 100644 index 00000000000..981e5352eb6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessorDelegate.java @@ -0,0 +1,76 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collection; + +/** + * The type Config cache md5 post processor delegate. + * + * @author Sunrisea + */ +public class ConfigCachePostProcessorDelegate { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigCacheFactoryDelegate.class); + + private static final ConfigCachePostProcessorDelegate INSTANCE = new ConfigCachePostProcessorDelegate(); + + private String configCacheMd5PostProcessorType = EnvUtil.getProperty("nacos.config.cache.type", "nacos"); + + private ConfigCachePostProcessor configCachePostProcessor; + + private ConfigCachePostProcessorDelegate() { + Collection processors = NacosServiceLoader.load(ConfigCachePostProcessor.class); + for (ConfigCachePostProcessor processor : processors) { + if (StringUtils.isEmpty(processor.getName())) { + LOGGER.warn( + "[ConfigCachePostProcessorDelegate] Load ConfigCachePostProcessor({}) PostProcessorName(null/empty) fail. " + + "Please add PostProcessorName to resolve", processor.getClass().getName()); + continue; + } + LOGGER.info( + "[ConfigCachePostProcessorDelegate] Load ConfigCachePostProcessor({}) PostProcessorName({}) successfully. ", + processor.getClass().getName(), processor.getName()); + if (StringUtils.equals(configCacheMd5PostProcessorType, processor.getName())) { + LOGGER.info( + "[ConfigCachePostProcessorDelegate] Matched ConfigCachePostProcessor found,set configCacheFactory={}", + processor.getClass().getName()); + this.configCachePostProcessor = processor; + } + } + if (configCachePostProcessor == null) { + LOGGER.info( + "[ConfigCachePostProcessorDelegate] Matched ConfigCachePostProcessor not found, " + + "load Default NacosConfigCachePostProcessor successfully"); + configCachePostProcessor = new NacosConfigCachePostProcessor(); + } + } + + public static ConfigCachePostProcessorDelegate getInstance() { + return INSTANCE; + } + + public void postProcess(ConfigCache configCache, String content) { + configCachePostProcessor.postProcess(configCache, content); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java index d99505af8a6..545d66179c5 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfo.java @@ -58,6 +58,12 @@ public class ConfigHistoryInfo implements Serializable { */ private String opType; + private String publishType; + + private String grayName; + + private String extInfo; + private Timestamp createdTime; private Timestamp lastModifiedTime; @@ -136,6 +142,22 @@ public void setOpType(String opType) { this.opType = opType; } + public String getPublishType() { + return publishType; + } + + public void setPublishType(String publishType) { + this.publishType = publishType; + } + + public String getExtInfo() { + return extInfo; + } + + public void setExtInfo(String extInfo) { + this.extInfo = extInfo; + } + public Timestamp getCreatedTime() { return new Timestamp(createdTime.getTime()); } @@ -152,6 +174,14 @@ public void setLastModifiedTime(Timestamp lastModifiedTime) { this.lastModifiedTime = new Timestamp(lastModifiedTime.getTime()); } + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + public String getAppName() { return appName; } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfoDetail.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfoDetail.java new file mode 100644 index 00000000000..807aa82a099 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigHistoryInfoDetail.java @@ -0,0 +1,243 @@ +/* + * 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.config.server.model; + +import java.io.Serializable; +import java.sql.Timestamp; + +/** + * ConfigHistoryInfoPair. + * + * @author dirtybit + */ +public class ConfigHistoryInfoDetail implements Serializable { + + private static final long serialVersionUID = -7827521105376245603L; + + private long id; + + private long lastId = -1; + + private String dataId; + + private String group; + + private String tenant; + + /** + * Operation type, include inserting, updating and deleting. + */ + private String opType; + + private String publishType; + + private String grayName; + + private String appName; + + private String srcIp; + + private String srcUser; + + private String originalMd5; + + private String originalContent; + + private String originalEncryptedDataKey; + + private String originalExtInfo; + + private String updatedMd5; + + private String updatedContent; + + private String updatedEncryptedDataKey; + + private String updateExtInfo; + + private Timestamp createdTime; + + private Timestamp lastModifiedTime; + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + + public long getLastId() { + return lastId; + } + + public void setLastId(long lastId) { + this.lastId = lastId; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getOpType() { + return opType; + } + + public void setOpType(String opType) { + this.opType = opType; + } + + public String getPublishType() { + return publishType; + } + + public void setPublishType(String publishType) { + this.publishType = publishType; + } + + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + + public String getAppName() { + return appName; + } + + public void setAppName(String appName) { + this.appName = appName; + } + + public String getSrcIp() { + return srcIp; + } + + public void setSrcIp(String srcIp) { + this.srcIp = srcIp; + } + + public String getSrcUser() { + return srcUser; + } + + public void setSrcUser(String srcUser) { + this.srcUser = srcUser; + } + + public String getOriginalMd5() { + return originalMd5; + } + + public void setOriginalMd5(String originalMd5) { + this.originalMd5 = originalMd5; + } + + public String getOriginalContent() { + return originalContent; + } + + public void setOriginalContent(String originalContent) { + this.originalContent = originalContent; + } + + public String getOriginalEncryptedDataKey() { + return originalEncryptedDataKey; + } + + public void setOriginalEncryptedDataKey(String originalEncryptedDataKey) { + this.originalEncryptedDataKey = originalEncryptedDataKey; + } + + public String getOriginalExtInfo() { + return originalExtInfo; + } + + public void setOriginalExtInfo(String originalExtInfo) { + this.originalExtInfo = originalExtInfo; + } + + public String getUpdatedMd5() { + return updatedMd5; + } + + public void setUpdatedMd5(String updatedMd5) { + this.updatedMd5 = updatedMd5; + } + + public String getUpdatedContent() { + return updatedContent; + } + + public void setUpdatedContent(String updatedContent) { + this.updatedContent = updatedContent; + } + + public String getUpdatedEncryptedDataKey() { + return updatedEncryptedDataKey; + } + + public void setUpdatedEncryptedDataKey(String updatedEncryptedDataKey) { + this.updatedEncryptedDataKey = updatedEncryptedDataKey; + } + + public String getUpdateExtInfo() { + return updateExtInfo; + } + + public void setUpdateExtInfo(String updateExtInfo) { + this.updateExtInfo = updateExtInfo; + } + + public Timestamp getCreatedTime() { + return createdTime; + } + + public void setCreatedTime(Timestamp createdTime) { + this.createdTime = createdTime; + } + + public Timestamp getLastModifiedTime() { + return lastModifiedTime; + } + + public void setLastModifiedTime(Timestamp lastModifiedTime) { + this.lastModifiedTime = lastModifiedTime; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoAggr.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoAggr.java deleted file mode 100644 index 3e2e8cfb960..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoAggr.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.config.server.model; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; -import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; - -import java.io.Serializable; - -/** - * ConfigInfoAggr. - * - * @author leiwen.zh - */ -public class ConfigInfoAggr implements Serializable { - - private static final long serialVersionUID = -3845825581059306364L; - - @JsonSerialize(using = ToStringSerializer.class) - private long id; - - private String dataId; - - private String group; - - private String datumId; - - private String tenant; - - private String appName; - - private String content; - - public ConfigInfoAggr() { - - } - - public long getId() { - return id; - } - - public void setId(long id) { - this.id = id; - } - - public String getDataId() { - return dataId; - } - - public void setDataId(String dataId) { - this.dataId = dataId; - } - - public String getGroup() { - return group; - } - - public void setGroup(String group) { - this.group = group; - } - - public String getDatumId() { - return datumId; - } - - public void setDatumId(String datumId) { - this.datumId = datumId; - } - - public String getContent() { - return content; - } - - public void setContent(String content) { - this.content = content; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - ConfigInfoAggr other = (ConfigInfoAggr) obj; - if (content == null) { - if (other.content != null) { - return false; - } - } else if (!content.equals(other.content)) { - return false; - } - if (dataId == null) { - if (other.dataId != null) { - return false; - } - } else if (!dataId.equals(other.dataId)) { - return false; - } - if (datumId == null) { - if (other.datumId != null) { - return false; - } - } else if (!datumId.equals(other.datumId)) { - return false; - } - if (group == null) { - if (other.group != null) { - return false; - } - } else if (!group.equals(other.group)) { - return false; - } - return true; - } - - @Override - public String toString() { - return "ConfigInfoAggr [dataId=" + dataId + ", group=" + group + ", datumId=" + datumId + ", content=" + content - + "]"; - } - - public String getAppName() { - return appName; - } - - public void setAppName(String appName) { - this.appName = appName; - } - - public String getTenant() { - return tenant; - } - - public void setTenant(String tenant) { - this.tenant = tenant; - } - -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoGrayWrapper.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoGrayWrapper.java new file mode 100644 index 00000000000..71ef60989a5 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoGrayWrapper.java @@ -0,0 +1,80 @@ +/* + * 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.config.server.model; + +/** + * ConfigInfoGrayWrapper. + * + * @author rong + */ +public class ConfigInfoGrayWrapper extends ConfigInfo { + + private static final long serialVersionUID = 4511997591465712505L; + + private long lastModified; + + private String grayName; + + private String grayRule; + + private String srcUser; + + public ConfigInfoGrayWrapper() { + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + + public String getGrayRule() { + return grayRule; + } + + public void setGrayRule(String grayRule) { + this.grayRule = grayRule; + } + + public String getSrcUser() { + return srcUser; + } + + public void setSrcUser(String srcUser) { + this.srcUser = srcUser; + } + + @Override + public int hashCode() { + return super.hashCode(); + } + + @Override + public boolean equals(Object obj) { + return super.equals(obj); + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoStateWrapper.java b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoStateWrapper.java index 8bc146cb63f..3fd5e6a7a82 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoStateWrapper.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/ConfigInfoStateWrapper.java @@ -38,6 +38,8 @@ public class ConfigInfoStateWrapper implements Serializable { private String md5; + private String grayName; + public long getId() { return id; } @@ -78,6 +80,14 @@ public void setTenant(String tenant) { this.tenant = tenant; } + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/NacosConfigCacheFactory.java b/config/src/main/java/com/alibaba/nacos/config/server/model/NacosConfigCacheFactory.java new file mode 100644 index 00000000000..a8fd8168169 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/NacosConfigCacheFactory.java @@ -0,0 +1,40 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +/** + * The type Nacos config cache factory. + * + * @author Sunrisea + */ +public class NacosConfigCacheFactory implements ConfigCacheFactory { + + @Override + public ConfigCache createConfigCache() { + return new ConfigCache(); + } + + @Override + public ConfigCacheGray createConfigCacheGray() { + return new ConfigCacheGray(); + } + + @Override + public String getName() { + return "nacos"; + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/Event.java b/config/src/main/java/com/alibaba/nacos/config/server/model/NacosConfigCachePostProcessor.java similarity index 57% rename from istio/src/main/java/com/alibaba/nacos/istio/common/Event.java rename to config/src/main/java/com/alibaba/nacos/config/server/model/NacosConfigCachePostProcessor.java index a681d4d96ec..7a7b0f35e67 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/common/Event.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/NacosConfigCachePostProcessor.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2018 Alibaba Group Holding Ltd. + * Copyright 1999-2024 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. @@ -14,26 +14,21 @@ * limitations under the License. */ -package com.alibaba.nacos.istio.common; +package com.alibaba.nacos.config.server.model; /** - * @author special.fy + * The type Nacos config cache md 5 post processor. + * + * @author Sunrisea */ -public class Event { - - public static final Event SERVICE_UPDATE_EVENT = new Event(EventType.Service); - - private EventType type; - - public Event(EventType type) { - this.type = type; +public class NacosConfigCachePostProcessor implements ConfigCachePostProcessor { + + @Override + public String getName() { + return "nacos"; } - - public EventType getType() { - return type; - } - - public void setType(EventType type) { - this.type = type; + + @Override + public void postProcess(ConfigCache configCache, String content) { } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDataChangeEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDataChangeEvent.java index 7ee4cb192fe..e3f6eb2ebeb 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDataChangeEvent.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDataChangeEvent.java @@ -17,7 +17,6 @@ package com.alibaba.nacos.config.server.model.event; import com.alibaba.nacos.common.notify.Event; -import com.alibaba.nacos.common.utils.StringUtils; /** * ConfigDataChangeEvent. @@ -26,66 +25,29 @@ */ public class ConfigDataChangeEvent extends Event { - public final boolean isBeta; + public String dataId; - public final boolean isBatch; + public String group; - public final String dataId; + public String tenant; - public final String group; - - public final String tenant; - - public final String tag; + public String grayName; public final long lastModifiedTs; - public ConfigDataChangeEvent(String dataId, String group, long gmtModified) { - this(false, dataId, group, gmtModified); - } - - public ConfigDataChangeEvent(boolean isBeta, String dataId, String group, String tenant, long gmtModified) { + public ConfigDataChangeEvent(String dataId, String group, String tenant, long gmtModified) { if (null == dataId || null == group) { throw new IllegalArgumentException("dataId is null or group is null"); } - this.isBeta = isBeta; this.dataId = dataId; this.group = group; this.tenant = tenant; - this.tag = null; - this.isBatch = false; this.lastModifiedTs = gmtModified; } - public ConfigDataChangeEvent(boolean isBeta, String dataId, String group, long gmtModified) { - this(isBeta, dataId, group, StringUtils.EMPTY, gmtModified); - } - - public ConfigDataChangeEvent(boolean isBeta, String dataId, String group, String tenant, String tag, - long gmtModified) { - if (null == dataId || null == group) { - throw new IllegalArgumentException("dataId is null or group is null"); - } - this.isBeta = isBeta; - this.dataId = dataId; - this.group = group; - this.tenant = tenant; - this.tag = tag; - this.isBatch = false; - this.lastModifiedTs = gmtModified; - } - - public ConfigDataChangeEvent(String dataId, String group, String tenant, boolean isBatch, long gmtModified) { - if (null == dataId || null == group) { - throw new IllegalArgumentException(); - } - this.isBeta = false; - this.dataId = dataId; - this.group = group; - this.tenant = tenant; - this.tag = null; - this.isBatch = isBatch; - this.lastModifiedTs = gmtModified; + public ConfigDataChangeEvent(String dataId, String group, String tenant, String grayName, long gmtModified) { + this(dataId, group, tenant, gmtModified); + this.grayName = grayName; } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDumpEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDumpEvent.java index c1ec3b358e3..f9e408fa43f 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDumpEvent.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/event/ConfigDumpEvent.java @@ -45,6 +45,10 @@ public class ConfigDumpEvent extends Event { private String tag; + private String grayName; + + private String grayRule; + private String content; private String betaIps; @@ -167,6 +171,22 @@ public void setEncryptedDataKey(String encryptedDataKey) { this.encryptedDataKey = encryptedDataKey; } + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + + public String getGrayRule() { + return grayRule; + } + + public void setGrayRule(String grayRule) { + this.grayRule = grayRule; + } + public static ConfigDumpEventBuilder builder() { return new ConfigDumpEventBuilder(); } @@ -189,6 +209,10 @@ public static final class ConfigDumpEventBuilder { private String tag; + private String grayName; + + private String grayRule; + private String encryptedDataKey; private String content; @@ -239,6 +263,16 @@ public ConfigDumpEventBuilder tag(String tag) { return this; } + public ConfigDumpEventBuilder grayName(String grayName) { + this.grayName = grayName; + return this; + } + + public ConfigDumpEventBuilder grayRule(String grayRule) { + this.grayRule = grayRule; + return this; + } + public ConfigDumpEventBuilder content(String content) { this.content = content; return this; @@ -294,6 +328,8 @@ public ConfigDumpEvent build() { configDumpEvent.setBatch(isBatch); configDumpEvent.setDelimiter(delimiter); configDumpEvent.setLastModifiedTs(lastModifiedTs); + configDumpEvent.setGrayName(grayName); + configDumpEvent.setGrayRule(grayRule); configDumpEvent.isBeta = this.isBeta; return configDumpEvent; } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/event/IstioConfigChangeEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/model/event/IstioConfigChangeEvent.java new file mode 100644 index 00000000000..ef82632b549 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/event/IstioConfigChangeEvent.java @@ -0,0 +1,39 @@ +/* + * 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.config.server.model.event; + +/** + * XDS config change event. + * + * @author PoisonGravity + */ +public class IstioConfigChangeEvent extends ConfigDataChangeEvent { + + private static final long serialVersionUID = -2618455009648617192L; + + public final String content; + + public final String type; + + public IstioConfigChangeEvent(String dataId, String group, String tenant, long gmtModified, String content, + String type) { + super(dataId, group, tenant, gmtModified); + this.content = content; + this.type = type; + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/event/LocalDataChangeEvent.java b/config/src/main/java/com/alibaba/nacos/config/server/model/event/LocalDataChangeEvent.java index 39ac52a71ec..cd2b935291a 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/event/LocalDataChangeEvent.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/event/LocalDataChangeEvent.java @@ -18,8 +18,6 @@ import com.alibaba.nacos.common.notify.Event; -import java.util.List; - /** * LocalDataChangeEvent. * @@ -29,49 +27,8 @@ public class LocalDataChangeEvent extends Event { public final String groupKey; - public final boolean isBeta; - - public final List betaIps; - - public final String tag; - - public final boolean isBatch; - - public final int delimiter; - public LocalDataChangeEvent(String groupKey) { this.groupKey = groupKey; - this.isBeta = false; - this.betaIps = null; - this.tag = null; - this.isBatch = false; - this.delimiter = 0; - } - - public LocalDataChangeEvent(String groupKey, boolean isBeta, List betaIps) { - this.groupKey = groupKey; - this.isBeta = isBeta; - this.betaIps = betaIps; - this.tag = null; - this.isBatch = false; - this.delimiter = 0; - } - - public LocalDataChangeEvent(String groupKey, String tag) { - this.groupKey = groupKey; - this.isBeta = false; - this.betaIps = null; - this.tag = tag; - this.isBatch = false; - this.delimiter = 0; - } - - public LocalDataChangeEvent(String groupKey, boolean isBatch, int delimiter) { - this.groupKey = groupKey; - this.isBeta = false; - this.betaIps = null; - this.tag = null; - this.isBatch = isBatch; - this.delimiter = delimiter; } + } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/form/ConfigForm.java b/config/src/main/java/com/alibaba/nacos/config/server/model/form/ConfigForm.java index cf58f4a6d89..baf9f2273a5 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/model/form/ConfigForm.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/form/ConfigForm.java @@ -19,18 +19,16 @@ import com.alibaba.nacos.api.exception.api.NacosApiException; import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.model.form.NacosForm; import org.springframework.http.HttpStatus; -import java.io.Serializable; -import java.util.Objects; - /** * ConfigForm. * * @author dongyafei - * @date 2022/7/24 + * @author xiweng.yy */ -public class ConfigForm implements Serializable { +public class ConfigForm implements NacosForm { private static final long serialVersionUID = 4124932564086863921L; @@ -50,6 +48,16 @@ public class ConfigForm implements Serializable { private String configTags; + private String encryptedDataKey; + + private String grayName; + + private String grayRuleExp; + + private String grayVersion; + + private int grayPriority; + private String desc; private String use; @@ -60,14 +68,11 @@ public class ConfigForm implements Serializable { private String schema; - private String encryptedDataKey; - public ConfigForm() { } public ConfigForm(String dataId, String group, String namespaceId, String content, String tag, String appName, - String srcUser, String configTags, String desc, String use, String effect, String type, String schema, - String encryptedDataKey) { + String srcUser, String configTags, String desc, String use, String effect, String type, String schema) { this.dataId = dataId; this.group = group; this.namespaceId = namespaceId; @@ -81,7 +86,6 @@ public ConfigForm(String dataId, String group, String namespaceId, String conten this.effect = effect; this.type = type; this.schema = schema; - this.encryptedDataKey = encryptedDataKey; } public String getDataId() { @@ -196,44 +200,39 @@ public void setEncryptedDataKey(String encryptedDataKey) { this.encryptedDataKey = encryptedDataKey; } - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - ConfigForm configForm = (ConfigForm) o; - return dataId.equals(configForm.dataId) && group.equals(configForm.group) && Objects.equals(namespaceId, configForm.namespaceId) - && content.equals(configForm.content) && Objects.equals(tag, configForm.tag) && Objects - .equals(appName, configForm.appName) && Objects.equals(srcUser, configForm.srcUser) && Objects - .equals(configTags, configForm.configTags) && Objects.equals(desc, configForm.desc) && Objects.equals( - use, configForm.use) && Objects.equals(effect, configForm.effect) && Objects.equals(type, - configForm.type) && Objects.equals(schema, configForm.schema) && Objects.equals(encryptedDataKey, - configForm.encryptedDataKey); + public String getGrayName() { + return grayName; } - @Override - public int hashCode() { - return Objects.hash(dataId, group, namespaceId, content, tag, appName, srcUser, configTags, desc, use, effect, type, - schema, encryptedDataKey); + public void setGrayName(String grayName) { + this.grayName = grayName; } - @Override - public String toString() { - return "ConfigVo{" + "dataId='" + dataId + '\'' + ", group='" + group + '\'' + ", namespaceId='" + namespaceId + '\'' - + ", content='" + content + '\'' + ", tag='" + tag + '\'' + ", appName='" + appName + '\'' - + ", srcUser='" + srcUser + '\'' + ", configTags='" + configTags + '\'' + ", desc='" + desc + '\'' - + ", use='" + use + '\'' + ", effect='" + effect + '\'' + ", type='" + type + '\'' + ", schema='" - + schema + '\'' + ", encryptedDataKey='" + encryptedDataKey + '\'' + '}'; + public String getGrayRuleExp() { + return grayRuleExp; } - /** - * Validate. - * - * @throws NacosApiException NacosApiException. - */ + public void setGrayRuleExp(String grayRuleExp) { + this.grayRuleExp = grayRuleExp; + } + + public String getGrayVersion() { + return grayVersion; + } + + public void setGrayVersion(String grayVersion) { + this.grayVersion = grayVersion; + } + + public int getGrayPriority() { + return grayPriority; + } + + public void setGrayPriority(int grayPriority) { + this.grayPriority = grayPriority; + } + + @Override public void validate() throws NacosApiException { if (StringUtils.isBlank(dataId)) { throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, @@ -241,7 +240,17 @@ public void validate() throws NacosApiException { } else if (StringUtils.isBlank(group)) { throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, "Required parameter 'group' type String is not present"); - } else if (StringUtils.isBlank(content)) { + } + } + + /** + * Validate form parameter and include validate `content` parameters. + * + * @throws NacosApiException NacosApiException + */ + public void validateWithContent() throws NacosApiException { + validate(); + if (StringUtils.isBlank(content)) { throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, "Required parameter 'content' type String is not present"); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/form/ConfigFormV3.java b/config/src/main/java/com/alibaba/nacos/config/server/model/form/ConfigFormV3.java new file mode 100644 index 00000000000..fa5a632e88d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/form/ConfigFormV3.java @@ -0,0 +1,67 @@ +/* + * 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.config.server.model.form; + +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.common.utils.StringUtils; +import org.springframework.http.HttpStatus; + +/** + * Nacos HTTP config form v3, use `groupName` replace `group`. + * + * @author xiweng.yy + */ +public class ConfigFormV3 extends ConfigForm { + + private static final long serialVersionUID = 1105715502736280287L; + + private String groupName; + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + @Override + public void validate() throws NacosApiException { + if (StringUtils.isBlank(groupName)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'groupName' type String is not present"); + } + super.setGroup(groupName); + super.validate(); + } + + /** + * Validate for blur search API, which allow user input empty groupName and dataId to search all configs. + * + * @throws NacosApiException when form parameters is invalid. + */ + public void blurSearchValidate() throws NacosApiException { + if (null == groupName) { + groupName = StringUtils.EMPTY; + super.setGroup(groupName); + } + if (null == getDataId()) { + setDataId(StringUtils.EMPTY); + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/gray/AbstractGrayRule.java b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/AbstractGrayRule.java new file mode 100644 index 00000000000..641ea74fab0 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/AbstractGrayRule.java @@ -0,0 +1,98 @@ +/* + * 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.config.server.model.gray; + +import com.alibaba.nacos.api.exception.NacosException; + +import java.util.Map; + +/** + * Gray rule. type with version determined parse logic. + * + * @author shiyiyue + */ +public abstract class AbstractGrayRule implements GrayRule { + + protected String rawGrayRuleExp; + + protected int priority; + + protected volatile boolean valid = true; + + public AbstractGrayRule() { + } + + public AbstractGrayRule(String rawGrayRuleExp, int priority) { + try { + parse(rawGrayRuleExp); + this.priority = priority; + } catch (NacosException e) { + valid = false; + } + this.rawGrayRuleExp = rawGrayRuleExp; + } + + /** + * parse gray rule. + * + * @param rawGrayRule raw gray rule. + * @throws NacosException if parse failed. + * @date 2024/3/14 + */ + protected abstract void parse(String rawGrayRule) throws NacosException; + + /** + * match gray rule. + * + * @param labels conn labels. + * @return true if match. + * @date 2024/3/14 + */ + public abstract boolean match(Map labels); + + public boolean isValid() { + return valid; + } + + /** + * get type. + * + * @return gray rule type. + * @date 2024/3/14 + */ + public abstract String getType(); + + /** + * get version. + * + * @return gray rule version. + * @date 2024/3/14 + */ + public abstract String getVersion(); + + public String getRawGrayRuleExp() { + return rawGrayRuleExp; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/gray/BetaGrayRule.java b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/BetaGrayRule.java new file mode 100644 index 00000000000..83fc51e411a --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/BetaGrayRule.java @@ -0,0 +1,97 @@ +/* + * 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.config.server.model.gray; + +import com.alibaba.nacos.api.exception.NacosException; + +import java.util.HashSet; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +/** + * beta gray rule for beta ips. + * @author shiyiyue1102 + */ +public class BetaGrayRule extends AbstractGrayRule { + + Set betaIps; + + public static final String CLIENT_IP_LABEL = "ClientIp"; + + public static final String TYPE_BETA = "beta"; + + public static final String VERSION = "1.0.0"; + + public static final int PRIORITY = Integer.MAX_VALUE; + + public BetaGrayRule() { + super(); + } + + public BetaGrayRule(String betaIps, int priority) { + super(betaIps, priority); + } + + /** + * parse beta gray rule. + * @param rawGrayRule raw gray rule. + * @throws NacosException exception. + */ + @Override + protected void parse(String rawGrayRule) throws NacosException { + Set betaIps = new HashSet<>(); + String[] ips = rawGrayRule.split(","); + for (String ip : ips) { + betaIps.add(ip); + } + this.betaIps = betaIps; + } + + @Override + + public boolean match(Map labels) { + return labels.containsKey(CLIENT_IP_LABEL) && betaIps.contains(labels.get(CLIENT_IP_LABEL)); + } + + @Override + public String getType() { + return TYPE_BETA; + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + BetaGrayRule that = (BetaGrayRule) o; + return Objects.equals(betaIps, that.betaIps); + } + + @Override + public int hashCode() { + return Objects.hash(betaIps); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/gray/ConfigGrayPersistInfo.java b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/ConfigGrayPersistInfo.java new file mode 100644 index 00000000000..fc7d1cdc50c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/ConfigGrayPersistInfo.java @@ -0,0 +1,73 @@ +/* + * 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.config.server.model.gray; + +/** + * description. + * + * @author rong + * @date 2024-03-14 10:57 + */ +public class ConfigGrayPersistInfo { + + private String type; + + private String version; + + private String expr; + + private int priority; + + public ConfigGrayPersistInfo(String type, String version, String expr, int priority) { + this.type = type; + this.version = version; + this.expr = expr; + this.priority = priority; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + + public String getExpr() { + return expr; + } + + public void setExpr(String expr) { + this.expr = expr; + } + + public int getPriority() { + return priority; + } + + public void setPriority(int priority) { + this.priority = priority; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/gray/GrayRule.java b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/GrayRule.java new file mode 100644 index 00000000000..5fa2e48007f --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/GrayRule.java @@ -0,0 +1,76 @@ +/* + * 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.config.server.model.gray; + +import java.util.Map; + +/** + * gray rule. + * + * @author rong + */ +public interface GrayRule { + + /** + * gray rule match labels or not. + * + * @date 2024/3/14 + * @param labels conn labels. + * @return true if match, false otherwise. + */ + boolean match(Map labels); + + /** + * if the gray rule is valid. + * + * @date 2024/3/14 + * @return true if valid, false otherwise. + */ + boolean isValid(); + + /** + * get gray rule type. + * + * @date 2024/3/14 + * @return the gray rule type. + */ + String getType(); + + /** + * get gray rule version. + * + * @date 2024/3/14 + * @return the gray rule version. + */ + String getVersion(); + + /** + * get gray rule priority. + * + * @date 2024/3/14 + * @return the gray rule priority. + */ + int getPriority(); + + /** + * get raw String of gray rule. + * + * @date 2024/3/14 + * @return the raw String of gray rule. + */ + String getRawGrayRuleExp(); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/gray/GrayRuleManager.java b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/GrayRuleManager.java new file mode 100644 index 00000000000..7d8fbb72225 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/GrayRuleManager.java @@ -0,0 +1,114 @@ +/* + * 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.config.server.model.gray; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.google.gson.Gson; + +import java.lang.reflect.Constructor; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * GrayRuleManager. + * + * @author zunfei.lzf + */ +public class GrayRuleManager { + + private static final Map> GRAY_RULE_MAP = new ConcurrentHashMap<>(8); + + public static final String SPLIT = "_"; + + static { + Collection grayRuleCollection = NacosServiceLoader.load(GrayRule.class); + for (GrayRule grayRule : grayRuleCollection) { + GRAY_RULE_MAP.put(grayRule.getType() + SPLIT + grayRule.getVersion(), grayRule.getClass()); + } + } + + /** + * get class by type and version. + * + * @param type type. + * @param version version. + * @return class. + * @date 2024/3/14 + */ + public static Class getClassByTypeAndVersion(String type, String version) { + return GRAY_RULE_MAP.get(type + SPLIT + version); + } + + /** + * construct gray rule. + * + * @param configGrayPersistInfo config gray persist info. + * @return gray rule. + * @date 2024/3/14 + */ + public static GrayRule constructGrayRule(ConfigGrayPersistInfo configGrayPersistInfo) { + Class classByTypeAndVersion = getClassByTypeAndVersion(configGrayPersistInfo.getType(), + configGrayPersistInfo.getVersion()); + if (classByTypeAndVersion == null) { + return null; + } + try { + Constructor declaredConstructor = classByTypeAndVersion.getDeclaredConstructor(String.class, int.class); + declaredConstructor.setAccessible(true); + return (GrayRule) declaredConstructor.newInstance(configGrayPersistInfo.getExpr(), + configGrayPersistInfo.getPriority()); + } catch (Exception e) { + throw new RuntimeException(String.format("construct gray rule failed with type[%s], version[%s].", + configGrayPersistInfo.getType(), configGrayPersistInfo.getVersion()), e); + } + } + + /** + * construct config gray persist info. + * + * @param grayRule gray rule. + * @return config gray persist info. + * @date 2024/3/14 + */ + public static ConfigGrayPersistInfo constructConfigGrayPersistInfo(GrayRule grayRule) { + return new ConfigGrayPersistInfo(grayRule.getType(), grayRule.getVersion(), grayRule.getRawGrayRuleExp(), + grayRule.getPriority()); + } + + /** + * deserialize config gray persist info. + * + * @param grayRuleRawStringFromDb gray rule raw string from db. + * @return config gray persist info. + * @date 2024/3/14 + */ + public static ConfigGrayPersistInfo deserializeConfigGrayPersistInfo(String grayRuleRawStringFromDb) { + return (new Gson()).fromJson(grayRuleRawStringFromDb, ConfigGrayPersistInfo.class); + } + + /** + * serialize config gray persist info. + * + * @param configGrayPersistInfo config gray persist info. + * @return serialized string. + * @date 2024/3/14 + */ + public static String serializeConfigGrayPersistInfo(ConfigGrayPersistInfo configGrayPersistInfo) { + return (new Gson()).toJson(configGrayPersistInfo); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/model/gray/TagGrayRule.java b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/TagGrayRule.java new file mode 100644 index 00000000000..0f3f845e6ea --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/model/gray/TagGrayRule.java @@ -0,0 +1,91 @@ +/* + * 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.config.server.model.gray; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.utils.StringUtils; + +import java.util.Map; +import java.util.Objects; + +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; + +/** + * Tag gray rule. + * + * @author shiyiyue + */ +public class TagGrayRule extends AbstractGrayRule { + + String tagValue; + + public static final String VIP_SERVER_TAG_LABEL = VIPSERVER_TAG; + + public static final String TYPE_TAG = "tag"; + + public static final String VERSION = "1.0.0"; + + public static final int PRIORITY = Integer.MAX_VALUE - 1; + + public TagGrayRule() { + super(); + } + + public TagGrayRule(String rawGrayRuleExp, int priority) { + super(rawGrayRuleExp, priority); + } + + @Override + protected void parse(String rawGrayRule) throws NacosException { + if (StringUtils.isBlank(rawGrayRule)) { + return; + } + this.tagValue = rawGrayRule; + } + + @Override + public boolean match(Map labels) { + return labels.containsKey(VIP_SERVER_TAG_LABEL) && tagValue.equals(labels.get(VIP_SERVER_TAG_LABEL)); + } + + @Override + public String getType() { + return TYPE_TAG; + } + + @Override + public String getVersion() { + return VERSION; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + TagGrayRule that = (TagGrayRule) o; + return tagValue.equals(that.tagValue); + } + + @Override + public int hashCode() { + return Objects.hash(tagValue); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigBlurSearchHttpParamExtractor.java b/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigBlurSearchHttpParamExtractor.java index e2a91c42c96..f66afea8d6d 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigBlurSearchHttpParamExtractor.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigBlurSearchHttpParamExtractor.java @@ -37,6 +37,7 @@ public class ConfigBlurSearchHttpParamExtractor extends AbstractHttpParamExtract public List extractParam(HttpServletRequest request) { String searchMode = request.getParameter("search"); ArrayList paramInfos = new ArrayList<>(); + // TODO might replace '*' to empty char '' and still do check. if (StringUtils.equals(searchMode, BLUR_SEARCH_MODE)) { return paramInfos; } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigDefaultHttpParamExtractor.java b/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigDefaultHttpParamExtractor.java index c0a243e7582..6a5b848d5ac 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigDefaultHttpParamExtractor.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/paramcheck/ConfigDefaultHttpParamExtractor.java @@ -60,7 +60,10 @@ private String getAliasDataId(HttpServletRequest request) { } private String getAliasGroup(HttpServletRequest request) { - String group = request.getParameter("group"); + String group = request.getParameter("groupName"); + if (StringUtils.isBlank(group)) { + group = request.getParameter("group"); + } return group; } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeBatchListenRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeBatchListenRequestHandler.java index 4583af292b1..4a77bed338c 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeBatchListenRequestHandler.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeBatchListenRequestHandler.java @@ -22,13 +22,14 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.paramcheck.impl.ConfigBatchListenRequestParamExtractor; import com.alibaba.nacos.core.remote.RequestHandler; -import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.utils.StringPool; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; @@ -58,17 +59,17 @@ public ConfigChangeBatchListenResponse handle(ConfigBatchListenRequest configCha String tag = configChangeListenRequest.getHeader(Constants.VIPSERVER_TAG); ParamUtils.checkParam(tag); ConfigChangeBatchListenResponse configChangeBatchListenResponse = new ConfigChangeBatchListenResponse(); - for (ConfigBatchListenRequest.ConfigListenContext listenContext : configChangeListenRequest - .getConfigListenContexts()) { - String groupKey = GroupKey2 - .getKey(listenContext.getDataId(), listenContext.getGroup(), listenContext.getTenant()); + for (ConfigBatchListenRequest.ConfigListenContext listenContext : configChangeListenRequest.getConfigListenContexts()) { + String namespaceId = NamespaceUtil.processNamespaceParameter(listenContext.getTenant()); + String groupKey = GroupKey2.getKey(listenContext.getDataId(), listenContext.getGroup(), namespaceId); groupKey = StringPool.get(groupKey); String md5 = StringPool.get(listenContext.getMd5()); if (configChangeListenRequest.isListen()) { configChangeListenContext.addListen(groupKey, md5, connectionId); - boolean isUptoDate = ConfigCacheService.isUptodate(groupKey, md5, meta.getClientIp(), tag); + boolean isUptoDate = ConfigCacheService.isUptodate(groupKey, md5, meta.getClientIp(), tag, + meta.getAppLabels()); if (!isUptoDate) { configChangeBatchListenResponse.addChangeConfig(listenContext.getDataId(), listenContext.getGroup(), listenContext.getTenant()); diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandler.java index f354cd759b6..ca6dfe015ba 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandler.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandler.java @@ -21,14 +21,22 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.RemoteConstants; import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.ConfigGrayModelMigrateService; import com.alibaba.nacos.config.server.service.dump.DumpRequest; import com.alibaba.nacos.config.server.service.dump.DumpService; import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.paramcheck.impl.ConfigRequestParamExtractor; import com.alibaba.nacos.core.remote.RequestHandler; -import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.remote.grpc.InvokeSource; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.plugin.auth.constant.SignType; import org.springframework.stereotype.Component; /** @@ -44,24 +52,59 @@ public class ConfigChangeClusterSyncRequestHandler private final DumpService dumpService; - public ConfigChangeClusterSyncRequestHandler(DumpService dumpService) { + private ConfigGrayModelMigrateService configGrayModelMigrateService; + + public ConfigChangeClusterSyncRequestHandler(DumpService dumpService, + ConfigGrayModelMigrateService configGrayModelMigrateService) { this.dumpService = dumpService; + this.configGrayModelMigrateService = configGrayModelMigrateService; } @TpsControl(pointName = "ClusterConfigChangeNotify") @Override @ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class) + @Secured(signType = SignType.CONFIG, apiType = ApiType.INNER_API) public ConfigChangeClusterSyncResponse handle(ConfigChangeClusterSyncRequest configChangeSyncRequest, RequestMeta meta) throws NacosException { + + checkCompatity(configChangeSyncRequest); + ParamUtils.checkParam(configChangeSyncRequest.getTag()); DumpRequest dumpRequest = DumpRequest.create(configChangeSyncRequest.getDataId(), configChangeSyncRequest.getGroup(), configChangeSyncRequest.getTenant(), configChangeSyncRequest.getLastModified(), meta.getClientIp()); - dumpRequest.setBeta(configChangeSyncRequest.isBeta()); - dumpRequest.setBatch(configChangeSyncRequest.isBatch()); - dumpRequest.setTag(configChangeSyncRequest.getTag()); + + dumpRequest.setGrayName(configChangeSyncRequest.getGrayName()); dumpService.dump(dumpRequest); return new ConfigChangeClusterSyncResponse(); } + /** + * if notified from old server,try to migrate and transfer gray model. + * + * @param configChangeSyncRequest request. + * @return + */ + private void checkCompatity(ConfigChangeClusterSyncRequest configChangeSyncRequest) { + if (PropertyUtil.isGrayCompatibleModel() && StringUtils.isBlank(configChangeSyncRequest.getGrayName())) { + if (configChangeSyncRequest.isBeta() || StringUtils.isNotBlank(configChangeSyncRequest.getTag())) { + + String grayName = null; + //from old server ,beta or tag persist into old model,try migrate and transfer gray model. + if (configChangeSyncRequest.isBeta()) { + configGrayModelMigrateService.checkMigrateBeta(configChangeSyncRequest.getDataId(), + configChangeSyncRequest.getGroup(), configChangeSyncRequest.getTenant()); + grayName = BetaGrayRule.TYPE_BETA; + } else { + configGrayModelMigrateService.checkMigrateTag(configChangeSyncRequest.getDataId(), + configChangeSyncRequest.getGroup(), configChangeSyncRequest.getTenant(), + configChangeSyncRequest.getTag()); + grayName = TagGrayRule.TYPE_TAG + "_" + configChangeSyncRequest.getTag(); + } + configChangeSyncRequest.setGrayName(grayName); + + } + } + } + } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandler.java index 72b0d1f9e7d..04745753fa7 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandler.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandler.java @@ -16,23 +16,21 @@ package com.alibaba.nacos.config.server.remote; +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.config.ConfigType; import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest; import com.alibaba.nacos.api.config.remote.response.ConfigPublishResponse; import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.api.remote.response.ResponseCode; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.common.utils.MapUtil; +import com.alibaba.nacos.common.utils.NamespaceUtil; +import com.alibaba.nacos.common.utils.Pair; import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.model.ConfigInfo; -import com.alibaba.nacos.config.server.model.ConfigOperateResult; -import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; -import com.alibaba.nacos.config.server.service.AggrWhitelist; -import com.alibaba.nacos.config.server.service.ConfigChangePublisher; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; -import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.model.ConfigRequestInfo; +import com.alibaba.nacos.config.server.model.form.ConfigForm; +import com.alibaba.nacos.config.server.service.ConfigOperationService; import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; @@ -41,11 +39,9 @@ import com.alibaba.nacos.core.utils.Loggers; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; +import com.alibaba.nacos.plugin.encryption.handler.EncryptionHandler; import org.springframework.stereotype.Component; -import java.util.HashMap; -import java.util.Map; - /** * request handler to publish config. * @@ -55,18 +51,10 @@ @Component public class ConfigPublishRequestHandler extends RequestHandler { - private final ConfigInfoPersistService configInfoPersistService; - - private final ConfigInfoTagPersistService configInfoTagPersistService; - - private final ConfigInfoBetaPersistService configInfoBetaPersistService; + private ConfigOperationService configOperationService; - public ConfigPublishRequestHandler(ConfigInfoPersistService configInfoPersistService, - ConfigInfoTagPersistService configInfoTagPersistService, - ConfigInfoBetaPersistService configInfoBetaPersistService) { - this.configInfoPersistService = configInfoPersistService; - this.configInfoTagPersistService = configInfoTagPersistService; - this.configInfoBetaPersistService = configInfoBetaPersistService; + public ConfigPublishRequestHandler(ConfigOperationService configOperationService) { + this.configOperationService = configOperationService; } @Override @@ -74,13 +62,14 @@ public ConfigPublishRequestHandler(ConfigInfoPersistService configInfoPersistSer @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) @ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class) public ConfigPublishResponse handle(ConfigPublishRequest request, RequestMeta meta) throws NacosException { + try { String dataId = request.getDataId(); String group = request.getGroup(); String content = request.getContent(); - final String tenant = request.getTenant(); + final String tenant = NamespaceUtil.processNamespaceParameter(request.getTenant()); + final String srcIp = meta.getClientIp(); - final String requestIpApp = request.getAdditionParam("requestIpApp"); final String tag = request.getAdditionParam("tag"); final String appName = request.getAdditionParam("appName"); final String type = request.getAdditionParam("type"); @@ -90,22 +79,48 @@ public ConfigPublishResponse handle(ConfigPublishRequest request, RequestMeta me // check tenant ParamUtils.checkParam(dataId, group, "datumId", content); ParamUtils.checkParam(tag); - Map configAdvanceInfo = new HashMap<>(10); - MapUtil.putIfValNoNull(configAdvanceInfo, "config_tags", request.getAdditionParam("config_tags")); - MapUtil.putIfValNoNull(configAdvanceInfo, "desc", request.getAdditionParam("desc")); - MapUtil.putIfValNoNull(configAdvanceInfo, "use", request.getAdditionParam("use")); - MapUtil.putIfValNoNull(configAdvanceInfo, "effect", request.getAdditionParam("effect")); - MapUtil.putIfValNoNull(configAdvanceInfo, "type", type); - MapUtil.putIfValNoNull(configAdvanceInfo, "schema", request.getAdditionParam("schema")); - ParamUtils.checkParam(configAdvanceInfo); - if (AggrWhitelist.isAggrDataId(dataId)) { - Loggers.REMOTE_DIGEST.warn("[aggr-conflict] {} attempt to publish single data, {}, {}", srcIp, dataId, group); - throw new NacosException(NacosException.NO_RIGHT, "dataId:" + dataId + " is aggr"); + ConfigForm configForm = new ConfigForm(); + configForm.setDataId(dataId); + configForm.setGroup(group); + configForm.setNamespaceId(tenant); + configForm.setContent(content); + configForm.setTag(tag); + configForm.setAppName(appName); + configForm.setSrcUser(srcUser); + configForm.setConfigTags(request.getAdditionParam("config_tags")); + configForm.setDesc(request.getAdditionParam("desc")); + configForm.setUse(request.getAdditionParam("use")); + configForm.setEffect(request.getAdditionParam("effect")); + configForm.setType(type); + configForm.setSchema(request.getAdditionParam("schema")); + + if (!ConfigType.isValidType(type)) { + configForm.setType(ConfigType.getDefaultType().getType()); } - return processConfigPublish(request, dataId, group, tenant, srcIp, - requestIpApp, tag, appName, type, srcUser, encryptedDataKey, content, configAdvanceInfo); + ConfigRequestInfo configRequestInfo = new ConfigRequestInfo(); + configRequestInfo.setSrcIp(srcIp); + configRequestInfo.setRequestIpApp(meta.getLabels().get(Constants.APPNAME)); + configRequestInfo.setBetaIps(request.getAdditionParam("betaIps")); + configRequestInfo.setCasMd5(request.getCasMd5()); + + String encryptedDataKeyFinal = null; + if (StringUtils.isNotBlank(encryptedDataKey)) { + encryptedDataKeyFinal = encryptedDataKey; + } else { + Pair pair = EncryptionHandler.encryptHandler(dataId, content); + content = pair.getSecond(); + encryptedDataKeyFinal = pair.getFirst(); + configForm.setContent(content); + } + try { + configOperationService.publishConfig(configForm, configRequestInfo, encryptedDataKeyFinal); + return ConfigPublishResponse.buildSuccessResponse(); + } catch (NacosApiException nacosApiException) { + return ConfigPublishResponse.buildFailResponse(ResponseCode.FAIL.getCode(), + nacosApiException.getErrMsg()); + } } catch (Exception e) { Loggers.REMOTE_DIGEST.error("[ConfigPublishRequestHandler] publish config error ,request ={}", request, e); @@ -115,63 +130,4 @@ public ConfigPublishResponse handle(ConfigPublishRequest request, RequestMeta me } } - private ConfigPublishResponse processConfigPublish(ConfigPublishRequest request, String dataId, String group, - String tenant, String srcIp, String requestIpApp, String tag, - String appName, String type, String srcUser, String encryptedDataKey, - String content, Map configAdvanceInfo) throws NacosException { - ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); - configInfo.setMd5(request.getCasMd5()); - configInfo.setType(type); - configInfo.setEncryptedDataKey(encryptedDataKey); - String betaIps = request.getAdditionParam("betaIps"); - ConfigOperateResult configOperateResult; - String persistEvent = ConfigTraceService.PERSISTENCE_EVENT; - - if (StringUtils.isBlank(betaIps)) { - if (StringUtils.isBlank(tag)) { - if (StringUtils.isNotBlank(request.getCasMd5())) { - configOperateResult = configInfoPersistService.insertOrUpdateCas(srcIp, srcUser, configInfo, configAdvanceInfo); - if (!configOperateResult.isSuccess()) { - return ConfigPublishResponse.buildFailResponse(ResponseCode.FAIL.getCode(), - "Cas publish fail,server md5 may have changed."); - } - } else { - configOperateResult = configInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, configAdvanceInfo); - } - ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, dataId, group, tenant, configOperateResult.getLastModified())); - } else { - if (StringUtils.isNotBlank(request.getCasMd5())) { - configOperateResult = configInfoTagPersistService.insertOrUpdateTagCas(configInfo, tag, srcIp, srcUser); - if (!configOperateResult.isSuccess()) { - return ConfigPublishResponse.buildFailResponse(ResponseCode.FAIL.getCode(), - "Cas publish tag config fail,server md5 may have changed."); - } - } else { - configOperateResult = configInfoTagPersistService.insertOrUpdateTag(configInfo, tag, srcIp, srcUser); - } - persistEvent = ConfigTraceService.PERSISTENCE_EVENT_TAG + "-" + tag; - ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, dataId, group, tenant, tag, configOperateResult.getLastModified())); - } - } else { - // beta publish - if (StringUtils.isNotBlank(request.getCasMd5())) { - configOperateResult = configInfoBetaPersistService.insertOrUpdateBetaCas(configInfo, betaIps, srcIp, srcUser); - if (!configOperateResult.isSuccess()) { - return ConfigPublishResponse.buildFailResponse(ResponseCode.FAIL.getCode(), - "Cas publish beta config fail,server md5 may have changed."); - } - } else { - configOperateResult = configInfoBetaPersistService.insertOrUpdateBeta(configInfo, betaIps, srcIp, srcUser); - } - persistEvent = ConfigTraceService.PERSISTENCE_EVENT_BETA; - ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent(true, dataId, group, tenant, configOperateResult.getLastModified())); - } - - ConfigTraceService.logPersistenceEvent(dataId, group, tenant, requestIpApp, - configOperateResult.getLastModified(), srcIp, persistEvent, ConfigTraceService.PERSISTENCE_TYPE_PUB, content); - return ConfigPublishResponse.buildSuccessResponse(); - } - } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java index 12166df3650..696748b81be 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandler.java @@ -22,14 +22,17 @@ import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.api.remote.response.ResponseCode; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.model.CacheItem; -import com.alibaba.nacos.config.server.service.ConfigCacheService; -import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.common.utils.NamespaceUtil; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.query.ConfigChainRequestExtractorService; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.LogUtil; -import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.config.server.utils.TimeUtils; import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; @@ -37,6 +40,8 @@ import com.alibaba.nacos.core.remote.RequestHandler; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component; import java.net.URLEncoder; @@ -54,7 +59,12 @@ @Component public class ConfigQueryRequestHandler extends RequestHandler { - public ConfigQueryRequestHandler() { + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigQueryRequestHandler.class); + + private final ConfigQueryChainService configQueryChainService; + + public ConfigQueryRequestHandler(ConfigQueryChainService configQueryChainService) { + this.configQueryChainService = configQueryChainService; } @Override @@ -62,122 +72,114 @@ public ConfigQueryRequestHandler() { @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) @ExtractorManager.Extractor(rpcExtractor = ConfigRequestParamExtractor.class) public ConfigQueryResponse handle(ConfigQueryRequest request, RequestMeta meta) throws NacosException { - try { - return getContext(request, meta, request.isNotify()); + String dataId = request.getDataId(); + String group = request.getGroup(); + String tenant = request.getTenant(); + tenant = NamespaceUtil.processNamespaceParameter(tenant); + String groupKey = GroupKey2.getKey(dataId, group, tenant); + boolean notify = request.isNotify(); + + String requestIpApp = meta.getLabels().get(CLIENT_APPNAME_HEADER); + String clientIp = meta.getClientIp(); + + ConfigQueryChainRequest chainRequest = ConfigChainRequestExtractorService.getExtractor() + .extract(request, meta); + ConfigQueryChainResponse chainResponse = configQueryChainService.handle(chainRequest); + + if (ResponseCode.FAIL.getCode() == chainResponse.getResultCode()) { + return ConfigQueryResponse.buildFailResponse(ResponseCode.FAIL.getCode(), chainResponse.getMessage()); + } + + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_NOT_FOUND) { + return handlerConfigNotFound(request.getDataId(), request.getGroup(), request.getTenant(), requestIpApp, + clientIp, notify); + } + + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_QUERY_CONFLICT) { + return handlerConfigConflict(clientIp, groupKey); + } + + ConfigQueryResponse response = new ConfigQueryResponse(); + + // Check if there is a matched gray rule + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_GRAY) { + if (BetaGrayRule.TYPE_BETA.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + response.setBeta(true); + } else if (TagGrayRule.TYPE_TAG.equals(chainResponse.getMatchedGray().getGrayRule().getType())) { + response.setTag(URLEncoder.encode(chainResponse.getMatchedGray().getRawGrayRule(), ENCODE_UTF8)); + } + } + + // Check if there is a special tag + if (chainResponse.getStatus() == ConfigQueryChainResponse.ConfigQueryStatus.SPECIAL_TAG_CONFIG_NOT_FOUND) { + response.setTag(request.getTag()); + } + + response.setMd5(chainResponse.getMd5()); + response.setEncryptedDataKey(chainResponse.getEncryptedDataKey()); + response.setContent(chainResponse.getContent()); + response.setLastModified(chainResponse.getLastModified()); + + String pullType = ConfigTraceService.PULL_TYPE_OK; + if (chainResponse.getContent() == null) { + pullType = ConfigTraceService.PULL_TYPE_NOTFOUND; + response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); + } else { + response.setResultCode(ResponseCode.SUCCESS.getCode()); + } + + String pullEvent = resolvePullEventType(chainResponse, request.getTag()); + LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, clientIp, response.getMd5(), + TimeUtils.getCurrentTimeStr()); + final long delayed = notify ? -1 : System.currentTimeMillis() - response.getLastModified(); + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, response.getLastModified(), pullEvent, + pullType, delayed, clientIp, notify, "grpc"); + + return response; + } catch (Exception e) { + LOGGER.error("Failed to handle grpc configuration query", e); return ConfigQueryResponse.buildFailResponse(ResponseCode.FAIL.getCode(), e.getMessage()); } } - private ConfigQueryResponse getContext(ConfigQueryRequest configQueryRequest, RequestMeta meta, boolean notify) throws Exception { - String dataId = configQueryRequest.getDataId(); - String group = configQueryRequest.getGroup(); - String tenant = configQueryRequest.getTenant(); - String clientIp = meta.getClientIp(); - String tag = configQueryRequest.getTag(); - - String groupKey = GroupKey2.getKey(configQueryRequest.getDataId(), configQueryRequest.getGroup(), configQueryRequest.getTenant()); - String autoTag = configQueryRequest.getHeader(com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG); - String requestIpApp = meta.getLabels().get(CLIENT_APPNAME_HEADER); - String acceptCharset = ENCODE_UTF8; - ParamUtils.checkParam(tag); - ParamUtils.checkParam(autoTag); - int lockResult = ConfigCacheService.tryConfigReadLock(groupKey); - String pullEvent = ConfigTraceService.PULL_EVENT; - String pullType = ConfigTraceService.PULL_TYPE_OK; - + private ConfigQueryResponse handlerConfigConflict(String clientIp, String groupKey) { ConfigQueryResponse response = new ConfigQueryResponse(); - CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey); - if (lockResult > 0 && cacheItem != null) { - try { - response = processConfigQuery(notify, dataId, group, tenant, - clientIp, tag, groupKey, autoTag, requestIpApp, acceptCharset, cacheItem); - } finally { - ConfigCacheService.releaseReadLock(groupKey); - } - } else if (lockResult == 0 || cacheItem == null) { - // CacheItem No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1. - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, - pullEvent, ConfigTraceService.PULL_TYPE_NOTFOUND, -1, clientIp, notify, "grpc"); - response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); - } else { - PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey); - response.setErrorInfo(ConfigQueryResponse.CONFIG_QUERY_CONFLICT, "requested file is being modified, please try later."); - } + PULL_LOG.info("[client-get] clientIp={}, {}, get data during dump", clientIp, groupKey); + response.setErrorInfo(ConfigQueryResponse.CONFIG_QUERY_CONFLICT, + "requested file is being modified, please try later."); + return response; } - private ConfigQueryResponse processConfigQuery(boolean notify, String dataId, - String group, String tenant, String clientIp, String tag, String groupKey, - String autoTag, String requestIpApp, String acceptCharset, CacheItem cacheItem) throws Exception { + private ConfigQueryResponse handlerConfigNotFound(String dataId, String group, String tenant, String requestIpApp, + String clientIp, boolean notify) { + //CacheItem No longer exists. It is impossible to simply calculate the push delayed. Here, simply record it as - 1. ConfigQueryResponse response = new ConfigQueryResponse(); - long lastModified; - boolean isBeta = cacheItem.isBeta() && cacheItem.getIps4Beta() != null - && cacheItem.getIps4Beta().contains(clientIp) && cacheItem.getConfigCacheBeta() != null; - String configType = cacheItem.getType(); - response.setContentType((null != configType) ? configType : "text"); + ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, ConfigTraceService.PULL_EVENT, + ConfigTraceService.PULL_TYPE_NOTFOUND, -1, clientIp, notify, "grpc"); + response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); - String content; - String md5; - String encryptedDataKey; - String pullEvent = ConfigTraceService.PULL_EVENT; + return response; - if (isBeta) { - md5 = cacheItem.getConfigCacheBeta().getMd5(acceptCharset); - lastModified = cacheItem.getConfigCacheBeta().getLastModifiedTs(); - content = ConfigDiskServiceFactory.getInstance().getBetaContent(dataId, group, tenant); - pullEvent = ConfigTraceService.PULL_EVENT_BETA; - encryptedDataKey = cacheItem.getConfigCacheBeta().getEncryptedDataKey(); - response.setBeta(true); - } else { - if (StringUtils.isBlank(tag)) { - if (isUseTag(cacheItem, autoTag)) { - md5 = cacheItem.getTagMd5(autoTag, acceptCharset); - lastModified = cacheItem.getTagLastModified(autoTag); - encryptedDataKey = cacheItem.getTagEncryptedDataKey(autoTag); - content = ConfigDiskServiceFactory.getInstance().getTagContent(dataId, group, tenant, autoTag); - pullEvent = ConfigTraceService.PULL_EVENT_TAG + "-" + autoTag; - response.setTag(URLEncoder.encode(autoTag, ENCODE_UTF8)); + } + + private String resolvePullEventType(ConfigQueryChainResponse chainResponse, String tag) { + switch (chainResponse.getStatus()) { + case CONFIG_FOUND_GRAY: + ConfigCacheGray matchedGray = chainResponse.getMatchedGray(); + if (matchedGray != null) { + return ConfigTraceService.PULL_EVENT + "-" + matchedGray.getGrayName(); } else { - md5 = cacheItem.getConfigCache().getMd5(acceptCharset); - lastModified = cacheItem.getConfigCache().getLastModifiedTs(); - encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); - content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); + return ConfigTraceService.PULL_EVENT; } - } else { - md5 = cacheItem.getTagMd5(tag, acceptCharset); - lastModified = cacheItem.getTagLastModified(tag); - encryptedDataKey = cacheItem.getTagEncryptedDataKey(tag); - content = ConfigDiskServiceFactory.getInstance().getTagContent(dataId, group, tenant, tag); - response.setTag(tag); - pullEvent = ConfigTraceService.PULL_EVENT_TAG + "-" + tag; - } + case SPECIAL_TAG_CONFIG_NOT_FOUND: + return ConfigTraceService.PULL_EVENT + "-" + TagGrayRule.TYPE_TAG + "-" + tag; + default: + return ConfigTraceService.PULL_EVENT; } - - response.setMd5(md5); - response.setEncryptedDataKey(encryptedDataKey); - response.setContent(content); - response.setLastModified(lastModified); - if (content == null) { - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, -1, - pullEvent, ConfigTraceService.PULL_TYPE_NOTFOUND, -1, clientIp, notify, "grpc"); - response.setErrorInfo(ConfigQueryResponse.CONFIG_NOT_FOUND, "config data not exist"); - } else { - response.setResultCode(ResponseCode.SUCCESS.getCode()); - LogUtil.PULL_CHECK_LOG.warn("{}|{}|{}|{}", groupKey, clientIp, md5, TimeUtils.getCurrentTimeStr()); - final long delayed = notify ? -1 : System.currentTimeMillis() - lastModified; - ConfigTraceService.logPullEvent(dataId, group, tenant, requestIpApp, lastModified, - pullEvent, ConfigTraceService.PULL_TYPE_OK, delayed, clientIp, notify, "grpc"); - } - return response; } - - private static boolean isUseTag(CacheItem cacheItem, String tag) { - return StringUtils.isNotBlank(tag) && cacheItem.getConfigCacheTags() != null && cacheItem.getConfigCacheTags() - .containsKey(tag); - } - } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandler.java index 942d779da8c..a721d7074fc 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandler.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandler.java @@ -21,18 +21,20 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; import com.alibaba.nacos.config.server.service.ConfigChangePublisher; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.paramcheck.impl.ConfigRequestParamExtractor; import com.alibaba.nacos.core.remote.RequestHandler; -import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.utils.Loggers; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.constant.SignType; @@ -51,12 +53,12 @@ public class ConfigRemoveRequestHandler extends RequestHandler betaIps, String tag) { + public void configDataChanged(String groupKey, String dataId, String group, String tenant) { Set listeners = configChangeListenContext.getListeners(groupKey); if (CollectionUtils.isEmpty(listeners)) { @@ -102,12 +99,6 @@ public void configDataChanged(String groupKey, String dataId, String group, Stri ConnectionMeta metaInfo = connection.getMetaInfo(); String clientIp = metaInfo.getClientIp(); - String clientTag = metaInfo.getTag(); - - //tag check - if (StringUtils.isNotBlank(tag) && !tag.equals(clientTag)) { - continue; - } ConfigChangeNotifyRequest notifyRequest = ConfigChangeNotifyRequest.build(dataId, group, tenant); @@ -122,15 +113,13 @@ public void configDataChanged(String groupKey, String dataId, String group, Stri @Override public void onEvent(LocalDataChangeEvent event) { String groupKey = event.groupKey; - boolean isBeta = event.isBeta; - List betaIps = event.betaIps; + String[] strings = GroupKey.parseKey(groupKey); String dataId = strings[0]; String group = strings[1]; String tenant = strings.length > 2 ? strings[2] : ""; - String tag = event.tag; - configDataChanged(groupKey, dataId, group, tenant, isBeta, betaIps, tag); + configDataChanged(groupKey, dataId, group, tenant); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/AggrWhitelist.java b/config/src/main/java/com/alibaba/nacos/config/server/service/AggrWhitelist.java deleted file mode 100644 index c80bd149555..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/AggrWhitelist.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * 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.config.server.service; - -import com.alibaba.nacos.common.utils.IoUtils; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.utils.RegexParser; - -import java.io.StringReader; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicReference; -import java.util.regex.Pattern; - -import static com.alibaba.nacos.config.server.utils.LogUtil.DEFAULT_LOG; -import static com.alibaba.nacos.config.server.utils.LogUtil.FATAL_LOG; - -/** - * AggrWhitelist. - * - * @author Nacos - */ -public class AggrWhitelist { - - public static final String AGGRIDS_METADATA = "com.alibaba.nacos.metadata.aggrIDs"; - - static final AtomicReference> AGGR_DATAID_WHITELIST = new AtomicReference<>( - new ArrayList<>()); - - /** - * Judge whether specified dataId includes aggregation white list. - * - * @param dataId dataId string value. - * @return Whether to match aggregation rules. - */ - public static boolean isAggrDataId(String dataId) { - if (null == dataId) { - throw new IllegalArgumentException("dataId is null"); - } - - for (Pattern pattern : AGGR_DATAID_WHITELIST.get()) { - if (pattern.matcher(dataId).matches()) { - return true; - } - } - return false; - } - - /** - * Load aggregation white lists based content parameter value. - * - * @param content content string value. - */ - public static void load(String content) { - if (StringUtils.isBlank(content)) { - FATAL_LOG.warn("aggr dataId whitelist is blank."); - return; - } - DEFAULT_LOG.warn("[aggr-dataIds] {}", content); - - try { - List lines = IoUtils.readLines(new StringReader(content)); - compile(lines); - } catch (Exception ioe) { - DEFAULT_LOG.error("failed to load aggr whitelist, " + ioe, ioe); - } - } - - static void compile(List whitelist) { - List list = new ArrayList<>(whitelist.size()); - - for (String line : whitelist) { - if (!StringUtils.isBlank(line)) { - String regex = RegexParser.regexFormat(line.trim()); - list.add(Pattern.compile(regex)); - } - } - AGGR_DATAID_WHITELIST.set(list); - } - - public static List getWhiteList() { - return AGGR_DATAID_WHITELIST.get(); - } -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java index beb7c6e6181..bb49cb84311 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigCacheService.java @@ -17,26 +17,29 @@ package com.alibaba.nacos.config.server.service; import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.common.utils.InternetAddressUtil; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.model.CacheItem; import com.alibaba.nacos.config.server.model.ConfigCache; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.model.ConfigCachePostProcessorDelegate; import com.alibaba.nacos.config.server.model.event.LocalDataChangeEvent; +import com.alibaba.nacos.config.server.model.gray.GrayRule; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.LogUtil; import com.alibaba.nacos.sys.env.EnvUtil; -import com.google.common.collect.Lists; import java.io.IOException; -import java.util.Collections; -import java.util.List; +import java.util.HashMap; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; +import static com.alibaba.nacos.api.common.Constants.CLIENT_IP; +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; +import static com.alibaba.nacos.config.server.constant.Constants.NULL; import static com.alibaba.nacos.config.server.constant.Constants.PERSIST_ENCODE; import static com.alibaba.nacos.config.server.utils.LogUtil.DEFAULT_LOG; import static com.alibaba.nacos.config.server.utils.LogUtil.DUMP_LOG; @@ -122,7 +125,7 @@ public static boolean dumpWithMd5(String dataId, String group, String tenant, St DUMP_LOG.info( "[dump] md5 changed, update md5 and timestamp in jvm cache ,groupKey={}, newMd5={},oldMd5={},lastModifiedTs={}", groupKey, md5, localContentMd5, lastModifiedTs); - updateMd5(groupKey, md5, lastModifiedTs, encryptedDataKey); + updateMd5(groupKey, md5, content, lastModifiedTs, encryptedDataKey); } else if (newLastModified) { DUMP_LOG.info( "[dump] md5 consistent ,timestamp changed, update timestamp only in jvm cache ,groupKey={},lastModifiedTs={}", @@ -171,147 +174,84 @@ public static boolean dump(String dataId, String group, String tenant, String co } /** - * Save config file and update md5 value in cache. + * Save gray config file and update md5 value in cache. * * @param dataId dataId string value. * @param group group string value. * @param tenant tenant string value. + * @param grayName grayName string value. + * @param grayRule grayRule string value. * @param content content string value. * @param lastModifiedTs lastModifiedTs. - * @param betaIps betaIps string value. * @return dumpChange success or not. */ - public static boolean dumpBeta(String dataId, String group, String tenant, String content, long lastModifiedTs, - String betaIps, String encryptedDataKey) { + public static boolean dumpGray(String dataId, String group, String tenant, String grayName, String grayRule, + String content, long lastModifiedTs, String encryptedDataKey) { final String groupKey = GroupKey2.getKey(dataId, group, tenant); makeSure(groupKey, null); final int lockResult = tryWriteLock(groupKey); if (lockResult < 0) { - DUMP_LOG.warn("[dump-beta-error] write lock failed. {}", groupKey); + DUMP_LOG.warn("[dump-gray-error] write lock failed. {}", groupKey); return false; } try { //check timestamp - boolean timestampOutDated = lastModifiedTs < ConfigCacheService.getBetaLastModifiedTs(groupKey); - if (timestampOutDated) { - DUMP_LOG.warn("[dump-beta-ignore] timestamp is outdated,groupKey={}", groupKey); - return true; - } + long localGrayLastModifiedTs = ConfigCacheService.getGrayLastModifiedTs(groupKey, grayName); - boolean timestampUpdated = lastModifiedTs > ConfigCacheService.getBetaLastModifiedTs(groupKey); - - String[] betaIpsArr = betaIps.split(","); - List betaIpList = Lists.newArrayList(betaIpsArr); - String md5 = MD5Utils.md5Hex(content, ENCODE_UTF8); - - //md5 check & update local disk cache. - String localContentBetaMd5 = ConfigCacheService.getContentBetaMd5(groupKey); - boolean md5Changed = !md5.equals(localContentBetaMd5); - if (md5Changed) { - DUMP_LOG.info( - "[dump-beta] md5 changed, update md5 in local disk cache. groupKey={}, newMd5={}, oldMd5={}", - groupKey, md5, localContentBetaMd5); - ConfigDiskServiceFactory.getInstance().saveBetaToDisk(dataId, group, tenant, content); - } - - //md5 , ip list timestamp check and update local jvm cache. - boolean ipListChanged = !betaIpList.equals(ConfigCacheService.getBetaIps(groupKey)); - if (md5Changed) { - DUMP_LOG.info( - "[dump-beta] md5 changed, update md5 & ip list & timestamp in jvm cache. groupKey={}, newMd5={}, oldMd5={},lastModifiedTs={}", - groupKey, md5, localContentBetaMd5, lastModifiedTs); - updateBetaMd5(groupKey, md5, betaIpList, lastModifiedTs, encryptedDataKey); - } else if (ipListChanged) { - DUMP_LOG.warn("[dump-beta] ip list changed, update ip list & timestamp in jvm cache. groupKey={}," - + " newIpList={}, oldIpList={},lastModifiedTs={}", groupKey, betaIpList, - ConfigCacheService.getBetaIps(groupKey), lastModifiedTs); - updateBetaIpList(groupKey, betaIpList, lastModifiedTs); - } else if (timestampUpdated) { - DUMP_LOG.warn( - "[dump-beta] timestamp changed, update timestamp in jvm cache. groupKey={}, newLastModifiedTs={}, oldLastModifiedTs={}", - groupKey, lastModifiedTs, ConfigCacheService.getBetaLastModifiedTs(groupKey)); - updateBetaTimeStamp(groupKey, lastModifiedTs); - } else { - DUMP_LOG.warn( - "[dump-beta-ignore] ignore to save jvm cache, md5 & ip list & timestamp no changed. groupKey={}", - groupKey); - } - return true; - } catch (IOException ioe) { - DUMP_LOG.error("[dump-beta-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe); - return false; - } finally { - releaseWriteLock(groupKey); - } - } - - /** - * Save config file and update md5 value in cache. - * - * @param dataId dataId string value. - * @param group group string value. - * @param tenant tenant string value. - * @param content content string value. - * @param lastModifiedTs lastModifiedTs. - * @param tag tag string value. - * @return dumpChange success or not. - */ - public static boolean dumpTag(String dataId, String group, String tenant, String tag, String content, - long lastModifiedTs, String encryptedDataKey4Tag) { - final String groupKey = GroupKey2.getKey(dataId, group, tenant); - - makeSure(groupKey, null); - final int lockResult = tryWriteLock(groupKey); - - if (lockResult < 0) { - DUMP_LOG.warn("[dump-tag-error] write lock failed. {}", groupKey); - return false; - } - - try { - - //check timestamp - long localTagLastModifiedTs = ConfigCacheService.getTagLastModifiedTs(groupKey, tag); - - boolean timestampOutdated = lastModifiedTs < localTagLastModifiedTs; + boolean timestampOutdated = lastModifiedTs < localGrayLastModifiedTs; if (timestampOutdated) { - DUMP_LOG.warn("[dump-tag-ignore] timestamp is outdated,groupKey={}", groupKey); + DUMP_LOG.warn("[dump-gray-ignore] timestamp is outdated,groupKey={}", groupKey); return true; } - boolean timestampChanged = lastModifiedTs > localTagLastModifiedTs; + boolean timestampChanged = lastModifiedTs > localGrayLastModifiedTs; final String md5 = MD5Utils.md5Hex(content, ENCODE_UTF8); - String localContentTagMd5 = ConfigCacheService.getContentTagMd5(groupKey, tag); - boolean md5Changed = !md5.equals(localContentTagMd5); + String localContentGrayMd5 = ConfigCacheService.getContentGrayMd5(groupKey, grayName); + boolean md5Changed = !md5.equals(localContentGrayMd5); - if (md5Changed) { - ConfigDiskServiceFactory.getInstance().saveTagToDisk(dataId, group, tenant, tag, content); + GrayRule localGrayRule = ConfigCacheService.getGrayRule(groupKey, grayName); + GrayRule grayRuleNew = GrayRuleManager.constructGrayRule( + GrayRuleManager.deserializeConfigGrayPersistInfo(grayRule)); + if (grayRuleNew == null) { + DUMP_LOG.warn("[dump-gray-exception] . " + groupKey + ", unknown gray rule for gray name" + grayName); + return false; } + boolean grayRuleChanged = !grayRuleNew.equals(localGrayRule); + if (md5Changed) { - DUMP_LOG.warn( - "[dump-tag] md5 changed, update local jvm cache, groupKey={},tag={}, newMd5={},oldMd5={},lastModifiedTs={}", - groupKey, tag, md5, localContentTagMd5, lastModifiedTs); - updateTagMd5(groupKey, tag, md5, lastModifiedTs, encryptedDataKey4Tag); + DUMP_LOG.info( + "[dump-gray] md5 changed, update local jvm cache& local disk cache, groupKey={},grayName={}, " + + "newMd5={},oldMd5={}, newGrayRule={}, oldGrayRule={},lastModifiedTs={}", groupKey, + grayName, md5, localContentGrayMd5, grayRule, localGrayRule, lastModifiedTs); + updateGrayMd5(groupKey, grayName, grayRule, md5, content, lastModifiedTs, encryptedDataKey); + ConfigDiskServiceFactory.getInstance().saveGrayToDisk(dataId, group, tenant, grayName, content); + + } else if (grayRuleChanged) { + DUMP_LOG.info("[dump-gray] gray rule changed, update local jvm cache, groupKey={},grayName={}, " + + "newMd5={},oldMd5={}, newGrayRule={}, oldGrayRule={},lastModifiedTs={}", groupKey, grayName, + md5, localContentGrayMd5, grayRule, localGrayRule, lastModifiedTs); + updateGrayRule(groupKey, grayName, grayRule, lastModifiedTs, encryptedDataKey); } else if (timestampChanged) { - DUMP_LOG.warn( - "[dump-tag] timestamp changed, update last modified in local jvm cache, groupKey={},tag={}," - + "tagLastModifiedTs={},oldTagLastModifiedTs={}", groupKey, tag, lastModifiedTs, - localTagLastModifiedTs); - updateTagTimeStamp(groupKey, tag, lastModifiedTs); + DUMP_LOG.info( + "[dump-gray] timestamp changed, update last modified in local jvm cache, groupKey={},grayName={}," + + "grayLastModifiedTs={},oldgrayLastModifiedTs={}", groupKey, grayName, lastModifiedTs, + localGrayLastModifiedTs); + updateGrayTimeStamp(groupKey, grayName, lastModifiedTs); } else { - DUMP_LOG.warn("[dump-tag-ignore] md5 & timestamp not changed. groupKey={},tag={}", groupKey, tag); + DUMP_LOG.warn("[dump-gray-ignore] md5 & timestamp not changed. groupKey={},grayName={}", groupKey, + grayName); } return true; } catch (IOException ioe) { - DUMP_LOG.error("[dump-tag-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe); + DUMP_LOG.error("[dump-gray-exception] save disk error. " + groupKey + ", " + ioe.toString(), ioe); return false; } finally { releaseWriteLock(groupKey); @@ -319,14 +259,15 @@ public static boolean dumpTag(String dataId, String group, String tenant, String } /** - * Delete config file, and delete cache. + * Delete gray config file, and delete cache. * - * @param dataId dataId string value. - * @param group group string value. - * @param tenant tenant string value. + * @param dataId dataId string value. + * @param group group string value. + * @param tenant tenant string value. + * @param grayName grayName string value. * @return remove success or not. */ - public static boolean remove(String dataId, String group, String tenant) { + public static boolean removeGray(String dataId, String group, String tenant, String grayName) { final String groupKey = GroupKey2.getKey(dataId, group, tenant); final int lockResult = tryWriteLock(groupKey); @@ -343,51 +284,24 @@ public static boolean remove(String dataId, String group, String tenant) { } try { - DUMP_LOG.info("[dump] remove local disk cache,groupKey={} ", groupKey); - ConfigDiskServiceFactory.getInstance().removeConfigInfo(dataId, group, tenant); + DUMP_LOG.info("[remove-gray-ok] remove gray in local disk cache,grayName={},groupKey={} ", grayName, + groupKey); + ConfigDiskServiceFactory.getInstance().removeConfigInfo4Gray(dataId, group, tenant, grayName); - CACHE.remove(groupKey); - DUMP_LOG.info("[dump] remove local jvm cache,groupKey={} ", groupKey); + CacheItem ci = CACHE.get(groupKey); + if (ci.getConfigCacheGray() != null) { + ci.getConfigCacheGray().remove(grayName); + if (ci.getConfigCacheGray().isEmpty()) { + ci.clearConfigGrays(); + } else { + ci.sortConfigGray(); + } + } - NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey)); - - return true; - } finally { - releaseWriteLock(groupKey); - } - } - - /** - * Delete beta config file, and delete cache. - * - * @param dataId dataId string value. - * @param group group string value. - * @param tenant tenant string value. - * @return remove success or not. - */ - public static boolean removeBeta(String dataId, String group, String tenant) { - final String groupKey = GroupKey2.getKey(dataId, group, tenant); - final int lockResult = tryWriteLock(groupKey); - - // If data is non-existent. - if (0 == lockResult) { - DUMP_LOG.info("[remove-ok] {} not exist.", groupKey); - return true; - } - - // try to lock failed - if (lockResult < 0) { - DUMP_LOG.warn("[remove-error] write lock failed. {}", groupKey); - return false; - } - - try { - DUMP_LOG.info("[remove-beta-ok] remove beta in local disk cache,groupKey={} ", groupKey); - ConfigDiskServiceFactory.getInstance().removeConfigInfo4Beta(dataId, group, tenant); - NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey, true, CACHE.get(groupKey).getIps4Beta())); - CACHE.get(groupKey).removeBeta(); - DUMP_LOG.info("[remove-beta-ok] remove beta in local jvm cache,groupKey={} ", groupKey); + DUMP_LOG.info("[remove-gray-ok] remove gray in local jvm cache,grayName={},groupKey={} ", grayName, + groupKey); + NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey)); return true; } finally { releaseWriteLock(groupKey); @@ -395,15 +309,14 @@ public static boolean removeBeta(String dataId, String group, String tenant) { } /** - * Delete tag config file, and delete cache. + * Delete config file, and delete cache. * * @param dataId dataId string value. * @param group group string value. * @param tenant tenant string value. - * @param tag tag string value. * @return remove success or not. */ - public static boolean removeTag(String dataId, String group, String tenant, String tag) { + public static boolean remove(String dataId, String group, String tenant) { final String groupKey = GroupKey2.getKey(dataId, group, tenant); final int lockResult = tryWriteLock(groupKey); @@ -420,20 +333,14 @@ public static boolean removeTag(String dataId, String group, String tenant, Stri } try { - DUMP_LOG.info("[remove-tag-ok] remove tag in local disk cache,tag={},groupKey={} ", tag, groupKey); - ConfigDiskServiceFactory.getInstance().removeConfigInfo4Tag(dataId, group, tenant, tag); + DUMP_LOG.info("[dump] remove local disk cache,groupKey={} ", groupKey); + ConfigDiskServiceFactory.getInstance().removeConfigInfo(dataId, group, tenant); - CacheItem ci = CACHE.get(groupKey); - if (ci.getConfigCacheTags() != null) { - ci.getConfigCacheTags().remove(tag); - if (ci.getConfigCacheTags().isEmpty()) { - ci.clearConfigTags(); - } - } + CACHE.remove(groupKey); + DUMP_LOG.info("[dump] remove local jvm cache,groupKey={} ", groupKey); - DUMP_LOG.info("[remove-tag-ok] remove tag in local jvm cache,tag={},groupKey={} ", tag, groupKey); + NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey)); - NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey, tag)); return true; } finally { releaseWriteLock(groupKey); @@ -443,141 +350,132 @@ public static boolean removeTag(String dataId, String group, String tenant, Stri /** * Update md5 value. * - * @param groupKey groupKey string value. - * @param md5Utf8 md5 string value. - * @param lastModifiedTs lastModifiedTs long value. + * @param groupKey the group key + * @param md5 the md 5 + * @param content the content + * @param lastModifiedTs the last modified ts + * @param encryptedDataKey the encrypted data key */ - public static void updateMd5(String groupKey, String md5Utf8, long lastModifiedTs, String encryptedDataKey) { + public static void updateMd5(String groupKey, String md5, String content, long lastModifiedTs, String encryptedDataKey) { CacheItem cache = makeSure(groupKey, encryptedDataKey); - if (cache.getConfigCache().getMd5Utf8() == null || !cache.getConfigCache().getMd5Utf8().equals(md5Utf8)) { - cache.getConfigCache().setMd5Utf8(md5Utf8); - cache.getConfigCache().setLastModifiedTs(lastModifiedTs); - cache.getConfigCache().setEncryptedDataKey(encryptedDataKey); + ConfigCache configCache = cache.getConfigCache(); + if (configCache.getMd5() == null || !configCache.getMd5().equals(md5)) { + configCache.setMd5(md5); + configCache.setLastModifiedTs(lastModifiedTs); + configCache.setEncryptedDataKey(encryptedDataKey); + ConfigCachePostProcessorDelegate.getInstance().postProcess(configCache, content); NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey)); } } /** - * Update Beta md5 value. - * - * @param groupKey groupKey string value. - * @param md5Utf8 md5UTF8 string value. - * @param ips4Beta ips4Beta List. - * @param lastModifiedTs lastModifiedTs long value. - */ - public static void updateBetaMd5(String groupKey, String md5Utf8, List ips4Beta, long lastModifiedTs, - String encryptedDataKey4Beta) { - CacheItem cache = makeSure(groupKey, null); - cache.initBetaCacheIfEmpty(); - String betaMd5Utf8 = cache.getConfigCacheBeta().getMd5(ENCODE_UTF8); - if (betaMd5Utf8 == null || !betaMd5Utf8.equals(md5Utf8) || !CollectionUtils.isListEqual(ips4Beta, - cache.ips4Beta)) { - cache.isBeta = true; - cache.ips4Beta = ips4Beta; - cache.getConfigCacheBeta().setMd5Utf8(md5Utf8); - cache.getConfigCacheBeta().setLastModifiedTs(lastModifiedTs); - cache.getConfigCacheBeta().setEncryptedDataKey(encryptedDataKey4Beta); - NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey, true, ips4Beta)); - } - } - - /** - * Update tag md5 value. + * Update gray md5 value. * - * @param groupKey groupKey string value. - * @param tag tag string value. - * @param md5Utf8 md5UTF8 string value. - * @param lastModifiedTs lastModifiedTs long value. + * @param groupKey the group key + * @param grayName the gray name + * @param grayRule the gray rule + * @param md5 the md 5 + * @param content the content + * @param lastModifiedTs the last modified ts + * @param encryptedDataKey the encrypted data key */ - public static void updateTagMd5(String groupKey, String tag, String md5Utf8, long lastModifiedTs, - String encryptedDataKey4Tag) { + public static void updateGrayMd5(String groupKey, String grayName, String grayRule, String md5, String content, + long lastModifiedTs, String encryptedDataKey) { CacheItem cache = makeSure(groupKey, null); - cache.initConfigTagsIfEmpty(tag); - ConfigCache configCache = cache.getConfigCacheTags().get(tag); - configCache.setMd5Utf8(md5Utf8); + cache.initConfigGrayIfEmpty(grayName); + ConfigCacheGray configCache = cache.getConfigCacheGray().get(grayName); + configCache.setMd5(md5); configCache.setLastModifiedTs(lastModifiedTs); - configCache.setEncryptedDataKey(encryptedDataKey4Tag); - NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey, tag)); + configCache.setEncryptedDataKey(encryptedDataKey); + configCache.resetGrayRule(grayRule); + cache.sortConfigGray(); + ConfigCachePostProcessorDelegate.getInstance().postProcess(configCache, content); + NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey)); } /** * Get and return content md5 value from cache. Empty string represents no data. */ public static String getContentMd5(String groupKey) { - return getContentMd5(groupKey, "", ""); + return getContentMd5(groupKey, null, null); } public static String getContentMd5(String groupKey, String ip, String tag) { + return getContentMd5(groupKey, ip, tag, null); + } + + public static String getContentMd5(String groupKey, String ip, String tag, Map connLabels) { CacheItem item = CACHE.get(groupKey); - if (item != null && item.isBeta && item.ips4Beta != null && item.ips4Beta.contains(ip) - && item.getConfigCacheBeta() != null) { - return item.getConfigCacheBeta().getMd5(ENCODE_UTF8); + if (item == null) { + return NULL; } - - if (item != null && StringUtils.isNotBlank(tag) && item.getConfigCacheTags() != null - && item.getConfigCacheTags().containsKey(tag)) { - return item.getConfigCacheTags().get(tag).getMd5(ENCODE_UTF8); + if (connLabels == null && StringUtils.isNotBlank(ip)) { + connLabels = new HashMap<>(4); } - - if (item != null && item.isBatch && item.delimiter >= InternetAddressUtil.ipToInt(ip) - && item.getConfigCacheBatch() != null) { - return item.getConfigCacheBatch().getMd5(ENCODE_UTF8); + if (connLabels == null && StringUtils.isNotBlank(tag)) { + connLabels = new HashMap<>(4); } - return (null != item) ? item.getConfigCache().getMd5(ENCODE_UTF8) : Constants.NULL; + if (StringUtils.isNotBlank(ip)) { + connLabels.put(CLIENT_IP, ip); + } + if (StringUtils.isNotBlank(tag)) { + connLabels.put(VIPSERVER_TAG, tag); + } + if (item.getSortConfigGrays() != null && connLabels != null && !connLabels.isEmpty()) { + for (ConfigCacheGray entry : item.getSortConfigGrays()) { + if (entry.match(connLabels)) { + return entry.getMd5(); + } + } + } + String md5 = item.getConfigCache().getMd5(); + return md5 == null ? NULL : md5; } - /** - * Get and return beta md5 value from cache. Empty string represents no data. - */ - public static String getContentBetaMd5(String groupKey) { - CacheItem item = CACHE.get(groupKey); - - if (item == null || item.getConfigCacheBeta() == null) { - return Constants.NULL; - } - return item.getConfigCacheBeta().getMd5(ENCODE_UTF8); + private static void updateGrayRule(String groupKey, String grayName, String grayRule, long lastModifiedTs, + String encryptedDataKey) { + CacheItem cache = makeSure(groupKey, null); + cache.initConfigGrayIfEmpty(grayName); + ConfigCacheGray configCache = cache.getConfigCacheGray().get(grayName); + configCache.setLastModifiedTs(lastModifiedTs); + configCache.setEncryptedDataKey(encryptedDataKey); + configCache.resetGrayRule(grayRule); + cache.sortConfigGray(); + NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey)); } /** - * Get and return tag md5 value from cache. Empty string represents no data. + * Get and return gray md5 value from cache. Empty string represents no data. * * @param groupKey groupKey string value. - * @param tag tag string value. + * @param grayName grayName string value. * @return Content Tag Md5 value. */ - public static String getContentTagMd5(String groupKey, String tag) { + public static String getContentGrayMd5(String groupKey, String grayName) { CacheItem item = CACHE.get(groupKey); - if (item == null || item.getConfigCacheTags() == null || !item.getConfigCacheTags().containsKey(tag)) { - return Constants.NULL; + if (item == null || item.getConfigCacheGray() == null || !item.getConfigCacheGray().containsKey(grayName)) { + return NULL; } - return item.getConfigCacheTags().get(tag).getMd5(ENCODE_UTF8); + return item.getConfigCacheGray().get(grayName).getMd5(); } - /** - * Get and return beta ip list. - * - * @param groupKey groupKey string value. - * @return list beta ips. - */ - public static List getBetaIps(String groupKey) { + public static long getGrayLastModifiedTs(String groupKey, String grayName) { CacheItem item = CACHE.get(groupKey); - return (null != item) ? item.getIps4Beta() : Collections.emptyList(); - } - - public static long getBetaLastModifiedTs(String groupKey) { - CacheItem item = CACHE.get(groupKey); - return (null != item && item.getConfigCacheBeta() != null) ? item.getConfigCacheBeta().getLastModifiedTs() : 0L; + if (item.getConfigCacheGray() == null || !item.getConfigCacheGray().containsKey(grayName)) { + return 0; + } + ConfigCache configCacheGray = item.getConfigCacheGray().get(grayName); + + return (null != configCacheGray) ? configCacheGray.getLastModifiedTs() : 0; } - public static long getTagLastModifiedTs(String groupKey, String tag) { + public static GrayRule getGrayRule(String groupKey, String grayName) { CacheItem item = CACHE.get(groupKey); - if (item.getConfigCacheTags() == null || !item.getConfigCacheTags().containsKey(tag)) { - return 0; + if (item == null || item.getConfigCacheGray() == null || !item.getConfigCacheGray().containsKey(grayName)) { + return null; } - ConfigCache configCacheTag = item.getConfigCacheTags().get(tag); - - return (null != configCacheTag) ? configCacheTag.getLastModifiedTs() : 0; + return item.getConfigCacheGray().get(grayName).getGrayRule(); } /** @@ -596,26 +494,29 @@ public static long getLastModifiedTs(String groupKey) { } /** - * update tag timestamp. + * update gray timestamp. * * @param groupKey groupKey. - * @param tag tag. + * @param grayName grayName. * @param lastModifiedTs lastModifiedTs. */ - public static void updateTagTimeStamp(String groupKey, String tag, long lastModifiedTs) { + private static void updateGrayTimeStamp(String groupKey, String grayName, long lastModifiedTs) { CacheItem cache = makeSure(groupKey, null); - cache.initConfigTagsIfEmpty(tag); - cache.getConfigCacheTags().get(tag).setLastModifiedTs(lastModifiedTs); - + cache.initConfigGrayIfEmpty(grayName); + cache.getConfigCacheGray().get(grayName).setLastModifiedTs(lastModifiedTs); } public static boolean isUptodate(String groupKey, String md5) { - String serverMd5 = ConfigCacheService.getContentMd5(groupKey); - return StringUtils.equals(md5, serverMd5); + return isUptodate(groupKey, md5, null, null); } public static boolean isUptodate(String groupKey, String md5, String ip, String tag) { - String serverMd5 = ConfigCacheService.getContentMd5(groupKey, ip, tag); + return isUptodate(groupKey, md5, ip, tag, null); + } + + public static boolean isUptodate(String groupKey, String md5, String ip, String tag, + Map appLabels) { + String serverMd5 = ConfigCacheService.getContentMd5(groupKey, ip, tag, appLabels); return StringUtils.equals(md5, serverMd5); } @@ -687,48 +588,18 @@ static CacheItem makeSure(final String groupKey, final String encryptedDataKey) * @param lastModifiedTs lastModifiedTs. * @param encryptedDataKey encryptedDataKey. */ - public static void updateTimeStamp(String groupKey, long lastModifiedTs, String encryptedDataKey) { + private static void updateTimeStamp(String groupKey, long lastModifiedTs, String encryptedDataKey) { CacheItem cache = makeSure(groupKey, encryptedDataKey); cache.getConfigCache().setLastModifiedTs(lastModifiedTs); } - /** - * update beta ip list. - * - * @param groupKey groupKey. - * @param ips4Beta ips4Beta. - * @param lastModifiedTs lastModifiedTs. - */ - private static void updateBetaIpList(String groupKey, List ips4Beta, long lastModifiedTs) { - CacheItem cache = makeSure(groupKey, null); - cache.initBetaCacheIfEmpty(); - cache.setBeta(true); - cache.setIps4Beta(ips4Beta); - cache.getConfigCacheBeta().setLastModifiedTs(lastModifiedTs); - NotifyCenter.publishEvent(new LocalDataChangeEvent(groupKey, true, ips4Beta)); - - } - - /** - * update beta lastModifiedTs. - * - * @param groupKey groupKey. - * @param lastModifiedTs lastModifiedTs. - */ - private static void updateBetaTimeStamp(String groupKey, long lastModifiedTs) { - CacheItem cache = makeSure(groupKey, null); - cache.initBetaCacheIfEmpty(); - cache.getConfigCacheBeta().setLastModifiedTs(lastModifiedTs); - - } - private static final int TRY_GET_LOCK_TIMES = 9; /** * try config read lock with spin of try get lock times. * * @param groupKey group key of config. - * @return + * @return 0 - No data and failed. Positive number - lock succeeded. Negative number - lock failed. */ public static int tryConfigReadLock(String groupKey) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigGrayModelMigrateService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigGrayModelMigrateService.java new file mode 100644 index 00000000000..431661b81b3 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigGrayModelMigrateService.java @@ -0,0 +1,248 @@ +/* + * 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.config.server.service; + +import com.alibaba.nacos.api.utils.NetUtils; +import com.alibaba.nacos.common.utils.ThreadUtils; +import com.alibaba.nacos.config.server.model.ConfigInfoBetaWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoTagWrapper; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.ConfigGrayPersistInfo; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.springframework.stereotype.Service; + +import javax.annotation.PostConstruct; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + +import static com.alibaba.nacos.config.server.utils.LogUtil.DEFAULT_LOG; +import static com.alibaba.nacos.config.server.utils.PropertyUtil.GRAY_MIGRATE_FLAG; + +/** + * migrate beta and tag to gray model. should only invoked from config sync notify. + * + * @author shiyiyue + */ +@Service +public class ConfigGrayModelMigrateService { + + ConfigInfoBetaPersistService configInfoBetaPersistService; + + ConfigInfoTagPersistService configInfoTagPersistService; + + ConfigInfoGrayPersistService configInfoGrayPersistService; + + public ConfigGrayModelMigrateService(ConfigInfoBetaPersistService configInfoBetaPersistService, + ConfigInfoTagPersistService configInfoTagPersistService, + ConfigInfoGrayPersistService configInfoGrayPersistService) { + this.configInfoBetaPersistService = configInfoBetaPersistService; + this.configInfoGrayPersistService = configInfoGrayPersistService; + this.configInfoTagPersistService = configInfoTagPersistService; + } + + /** + * migrate beta&tag to gray . + */ + @PostConstruct + public void migrate() throws Exception { + if (PropertyUtil.isGrayCompatibleModel()) { + doCheckMigrate(); + } + } + + /** + * migrate single config beta. + * + * @param dataId dataId. + * @param group group. + * @param tenant tenant. + */ + public void checkMigrateBeta(String dataId, String group, String tenant) { + ConfigInfoBetaWrapper configInfo4Beta = configInfoBetaPersistService.findConfigInfo4Beta(dataId, group, tenant); + if (configInfo4Beta == null) { + ConfigInfoGrayWrapper configInfoGrayWrapper = configInfoGrayPersistService.findConfigInfo4Gray(dataId, + group, tenant, BetaGrayRule.TYPE_BETA); + if (configInfoGrayWrapper == null) { + return; + } + configInfoGrayPersistService.removeConfigInfoGray(dataId, group, tenant, BetaGrayRule.TYPE_BETA, + NetUtils.localIP(), "nacos_auto_migrate"); + return; + } + ConfigInfoGrayWrapper configInfo4Gray = configInfoGrayPersistService.findConfigInfo4Gray(dataId, group, tenant, + BetaGrayRule.TYPE_BETA); + if (configInfo4Gray == null || configInfo4Gray.getLastModified() < configInfo4Beta.getLastModified()) { + DEFAULT_LOG.info("[migrate beta to gray] dataId={}, group={}, tenant={}, md5={}", + configInfo4Beta.getDataId(), configInfo4Beta.getGroup(), configInfo4Beta.getTenant(), + configInfo4Beta.getMd5()); + ConfigGrayPersistInfo localConfigGrayPersistInfo = new ConfigGrayPersistInfo(BetaGrayRule.TYPE_BETA, + BetaGrayRule.VERSION, configInfo4Beta.getBetaIps(), BetaGrayRule.PRIORITY); + configInfoGrayPersistService.insertOrUpdateGray(configInfo4Beta, BetaGrayRule.TYPE_BETA, + GrayRuleManager.serializeConfigGrayPersistInfo(localConfigGrayPersistInfo), NetUtils.localIP(), + "nacos_auto_migrate"); + } + + } + + /** + * migrate single config tag. + * + * @param dataId dataId. + * @param group group. + * @param tenant tenant. + * @param tag tag. + */ + public void checkMigrateTag(String dataId, String group, String tenant, String tag) { + ConfigInfoTagWrapper configInfo4Tag = configInfoTagPersistService.findConfigInfo4Tag(dataId, group, tenant, + tag); + if (configInfo4Tag == null) { + ConfigInfoGrayWrapper configInfo4Gray = configInfoGrayPersistService.findConfigInfo4Gray(dataId, group, + tenant, TagGrayRule.TYPE_TAG + "_" + tag); + if (configInfo4Gray == null) { + return; + } + configInfoGrayPersistService.removeConfigInfoGray(dataId, group, tenant, TagGrayRule.TYPE_TAG + "_" + tag, + NetUtils.localIP(), "nacos_auto_migrate"); + return; + } + ConfigInfoGrayWrapper configInfo4Gray = configInfoGrayPersistService.findConfigInfo4Gray(dataId, group, tenant, + TagGrayRule.TYPE_TAG + "_" + tag); + if (configInfo4Gray == null || configInfo4Gray.getLastModified() < configInfo4Tag.getLastModified()) { + DEFAULT_LOG.info("[migrate tag to gray] dataId={}, group={}, tenant={}, md5={}", + configInfo4Tag.getDataId(), configInfo4Tag.getGroup(), configInfo4Tag.getTenant(), + configInfo4Tag.getMd5()); + ConfigGrayPersistInfo localConfigGrayPersistInfo = new ConfigGrayPersistInfo(TagGrayRule.TYPE_TAG, + TagGrayRule.VERSION, configInfo4Tag.getTag(), TagGrayRule.PRIORITY); + configInfoGrayPersistService.insertOrUpdateGray(configInfo4Tag, TagGrayRule.TYPE_TAG, + GrayRuleManager.serializeConfigGrayPersistInfo(localConfigGrayPersistInfo), NetUtils.localIP(), + "nacos_auto_migrate"); + } + } + + private void doCheckMigrate() throws Exception { + + int migrateMulti = EnvUtil.getProperty("nacos.gray.migrate.executor.multi", Integer.class, Integer.valueOf(4)); + ThreadPoolExecutor executorService = new ThreadPoolExecutor(ThreadUtils.getSuitableThreadCount(migrateMulti), + ThreadUtils.getSuitableThreadCount(migrateMulti), 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(PropertyUtil.getAllDumpPageSize() * migrateMulti), + r -> new Thread(r, "gray-migrate-worker"), new ThreadPoolExecutor.CallerRunsPolicy()); + int pageSize = 100; + int rowCount = configInfoBetaPersistService.configInfoBetaCount(); + int pageCount = (int) Math.ceil(rowCount * 1.0 / pageSize); + int actualRowCount = 0; + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = configInfoBetaPersistService.findAllConfigInfoBetaForDumpAll(pageNo, + pageSize); + if (page != null) { + for (ConfigInfoBetaWrapper cf : page.getPageItems()) { + + executorService.execute(() -> { + GRAY_MIGRATE_FLAG.set(true); + ConfigInfoGrayWrapper configInfo4Gray = configInfoGrayPersistService.findConfigInfo4Gray( + cf.getDataId(), cf.getGroup(), cf.getTenant(), BetaGrayRule.TYPE_BETA); + if (configInfo4Gray == null || configInfo4Gray.getLastModified() < cf.getLastModified()) { + DEFAULT_LOG.info("[migrate beta to gray] dataId={}, group={}, tenant={}, md5={}", + cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getMd5()); + ConfigGrayPersistInfo localConfigGrayPersistInfo = new ConfigGrayPersistInfo( + BetaGrayRule.TYPE_BETA, BetaGrayRule.VERSION, cf.getBetaIps(), + BetaGrayRule.PRIORITY); + configInfoGrayPersistService.insertOrUpdateGray(cf, BetaGrayRule.TYPE_BETA, + GrayRuleManager.serializeConfigGrayPersistInfo(localConfigGrayPersistInfo), + NetUtils.localIP(), "nacos_auto_migrate"); + GRAY_MIGRATE_FLAG.set(false); + } + }); + + } + actualRowCount += page.getPageItems().size(); + DEFAULT_LOG.info("[gray-migrate-beta] submit gray task {} / {}", actualRowCount, rowCount); + + } + } + + try { + int unfinishedTaskCount = 0; + while ((unfinishedTaskCount = executorService.getQueue().size() + executorService.getActiveCount()) > 0) { + DEFAULT_LOG.info("[gray-migrate-beta] wait {} migrate tasks to be finished", unfinishedTaskCount); + Thread.sleep(1000L); + } + + } catch (Exception e) { + DEFAULT_LOG.error("[gray-migrate-beta] wait dump tasks to be finished error", e); + throw e; + } + + rowCount = configInfoTagPersistService.configInfoTagCount(); + pageCount = (int) Math.ceil(rowCount * 1.0 / pageSize); + actualRowCount = 0; + for (int pageNo = 1; pageNo <= pageCount; pageNo++) { + Page page = configInfoTagPersistService.findAllConfigInfoTagForDumpAll(pageNo, + pageSize); + if (page != null) { + for (ConfigInfoTagWrapper cf : page.getPageItems()) { + + executorService.execute(() -> { + GRAY_MIGRATE_FLAG.set(true); + ConfigInfoGrayWrapper configInfo4Gray = configInfoGrayPersistService.findConfigInfo4Gray( + cf.getDataId(), cf.getGroup(), cf.getTenant(), + TagGrayRule.TYPE_TAG + "_" + cf.getTag()); + if (configInfo4Gray == null || configInfo4Gray.getLastModified() < cf.getLastModified()) { + DEFAULT_LOG.info("[migrate tag to gray] dataId={}, group={}, tenant={}, md5={}", + cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getMd5()); + ConfigGrayPersistInfo localConfigGrayPersistInfo = new ConfigGrayPersistInfo( + TagGrayRule.TYPE_TAG, TagGrayRule.VERSION, cf.getTag(), TagGrayRule.PRIORITY); + configInfoGrayPersistService.insertOrUpdateGray(cf, + TagGrayRule.TYPE_TAG + "_" + cf.getTag(), + GrayRuleManager.serializeConfigGrayPersistInfo(localConfigGrayPersistInfo), + NetUtils.localIP(), "nacos_auto_migrate"); + GRAY_MIGRATE_FLAG.set(false); + } + }); + + } + + actualRowCount += page.getPageItems().size(); + DEFAULT_LOG.info("[gray-migrate-tag] submit gray task {} / {}", actualRowCount, rowCount); + } + } + + try { + int unfinishedTaskCount = 0; + while ((unfinishedTaskCount = executorService.getQueue().size() + executorService.getActiveCount()) > 0) { + DEFAULT_LOG.info("[gray-migrate-tag] wait {} migrate tasks to be finished", unfinishedTaskCount); + Thread.sleep(1000L); + } + + } catch (Exception e) { + DEFAULT_LOG.error("[gray-migrate-tag] wait migrate tasks to be finished error", e); + throw e; + } + //shut down migrate executor + executorService.shutdown(); + + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigOperationService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigOperationService.java index 26f07c4203f..146a4646473 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigOperationService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigOperationService.java @@ -20,18 +20,29 @@ import com.alibaba.nacos.api.exception.api.NacosApiException; import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.common.utils.MapUtil; +import com.alibaba.nacos.common.utils.NumberUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigOperateResult; import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; +import com.alibaba.nacos.config.server.model.event.IstioConfigChangeEvent; import com.alibaba.nacos.config.server.model.form.ConfigForm; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.ConfigGrayPersistInfo; +import com.alibaba.nacos.config.server.model.gray.GrayRule; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.ConfigTagUtil; import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.config.server.utils.PropertyUtil; import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.alibaba.nacos.sys.env.EnvUtil; import com.alibaba.nacos.sys.utils.InetUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,6 +51,7 @@ import java.sql.Timestamp; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -58,14 +70,18 @@ public class ConfigOperationService { private ConfigInfoBetaPersistService configInfoBetaPersistService; + private ConfigInfoGrayPersistService configInfoGrayPersistService; + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigOperationService.class); public ConfigOperationService(ConfigInfoPersistService configInfoPersistService, ConfigInfoTagPersistService configInfoTagPersistService, - ConfigInfoBetaPersistService configInfoBetaPersistService) { + ConfigInfoBetaPersistService configInfoBetaPersistService, + ConfigInfoGrayPersistService configInfoGrayPersistService) { this.configInfoPersistService = configInfoPersistService; this.configInfoTagPersistService = configInfoTagPersistService; this.configInfoBetaPersistService = configInfoBetaPersistService; + this.configInfoGrayPersistService = configInfoGrayPersistService; } /** @@ -75,14 +91,11 @@ public ConfigOperationService(ConfigInfoPersistService configInfoPersistService, */ public Boolean publishConfig(ConfigForm configForm, ConfigRequestInfo configRequestInfo, String encryptedDataKey) throws NacosException { + Map configAdvanceInfo = getConfigAdvanceInfo(configForm); ParamUtils.checkParam(configAdvanceInfo); - if (AggrWhitelist.isAggrDataId(configForm.getDataId())) { - LOGGER.warn("[aggr-conflict] {} attempt to publish single data, {}, {}", configRequestInfo.getSrcIp(), - configForm.getDataId(), configForm.getGroup()); - throw new NacosApiException(HttpStatus.FORBIDDEN.value(), ErrorCode.INVALID_DATA_ID, - "dataId:" + configForm.getDataId() + " is aggr"); - } + + configForm.setEncryptedDataKey(encryptedDataKey); ConfigInfo configInfo = new ConfigInfo(configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId(), configForm.getAppName(), configForm.getContent()); //set old md5 @@ -91,82 +104,198 @@ public Boolean publishConfig(ConfigForm configForm, ConfigRequestInfo configRequ } configInfo.setType(configForm.getType()); configInfo.setEncryptedDataKey(encryptedDataKey); - String persistEvent = ConfigTraceService.PERSISTENCE_EVENT; - ConfigOperateResult configOperateResult = processBuildConfigOperateResult( - configAdvanceInfo, configInfo, configRequestInfo, persistEvent, configForm); + ConfigOperateResult configOperateResult; + + //beta publish + if (StringUtils.isNotBlank(configRequestInfo.getBetaIps())) { + configForm.setGrayName(BetaGrayRule.TYPE_BETA); + configForm.setGrayRuleExp(configRequestInfo.getBetaIps()); + configForm.setGrayVersion(BetaGrayRule.VERSION); + persistBeta(configForm, configInfo, configRequestInfo); + configForm.setGrayPriority(Integer.MAX_VALUE); + publishConfigGray(BetaGrayRule.TYPE_BETA, configForm, configRequestInfo); + return Boolean.TRUE; + } + // tag publish + if (StringUtils.isNotBlank(configForm.getTag())) { + configForm.setGrayName(TagGrayRule.TYPE_TAG + "_" + configForm.getTag()); + configForm.setGrayRuleExp(configForm.getTag()); + configForm.setGrayVersion(TagGrayRule.VERSION); + configForm.setGrayPriority(Integer.MAX_VALUE - 1); + persistTagv1(configForm, configInfo, configRequestInfo); + publishConfigGray(TagGrayRule.TYPE_TAG, configForm, configRequestInfo); + return Boolean.TRUE; + } + + //formal publish + if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) { + configOperateResult = configInfoPersistService.insertOrUpdateCas(configRequestInfo.getSrcIp(), + configForm.getSrcUser(), configInfo, configAdvanceInfo); + if (!configOperateResult.isSuccess()) { + LOGGER.warn( + "[cas-publish-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, msg = server md5 may have changed.", + configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5()); + throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.RESOURCE_CONFLICT, + "Cas publish fail, server md5 may have changed."); + } + } else { + configOperateResult = configInfoPersistService.insertOrUpdate(configRequestInfo.getSrcIp(), + configForm.getSrcUser(), configInfo, configAdvanceInfo); + } + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent(configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId(), + configOperateResult.getLastModified())); + if (ConfigTagUtil.isIstio(configForm.getConfigTags())) { + ConfigChangePublisher.notifyConfigChange( + new IstioConfigChangeEvent(configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId(), + configOperateResult.getLastModified(), configForm.getContent(), + ConfigTagUtil.getIstioType(configForm.getConfigTags()))); + } ConfigTraceService.logPersistenceEvent(configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId(), configRequestInfo.getRequestIpApp(), configOperateResult.getLastModified(), - InetUtils.getSelfIP(), persistEvent, ConfigTraceService.PERSISTENCE_TYPE_PUB, configForm.getContent()); + InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT, ConfigTraceService.PERSISTENCE_TYPE_PUB, + configForm.getContent()); + return true; } - private ConfigOperateResult processBuildConfigOperateResult(Map configAdvanceInfo, - ConfigInfo configInfo, ConfigRequestInfo configRequestInfo, String persistEvent, ConfigForm configForm) + private void persistTagv1(ConfigForm configForm, ConfigInfo configInfo, ConfigRequestInfo configRequestInfo) throws NacosApiException { + if (!PropertyUtil.isGrayCompatibleModel()) { + return; + } + + ConfigOperateResult configOperateResult = null; + if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) { + configOperateResult = configInfoTagPersistService.insertOrUpdateTagCas(configInfo, configForm.getTag(), + configRequestInfo.getSrcIp(), configForm.getSrcUser()); + if (!configOperateResult.isSuccess()) { + LOGGER.warn( + "[cas-publish-tag-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, msg = server md5 may have changed.", + configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5()); + throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.RESOURCE_CONFLICT, + "Cas publish tag config fail, server md5 may have changed."); + } + } else { + configOperateResult = configInfoTagPersistService.insertOrUpdateTag(configInfo, configForm.getTag(), + configRequestInfo.getSrcIp(), configForm.getSrcUser()); + } + } + + private void persistBeta(ConfigForm configForm, ConfigInfo configInfo, ConfigRequestInfo configRequestInfo) + throws NacosApiException { + if (!PropertyUtil.isGrayCompatibleModel()) { + return; + } + ConfigOperateResult configOperateResult = null; + // beta publish + if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) { + configOperateResult = configInfoBetaPersistService.insertOrUpdateBetaCas(configInfo, + configRequestInfo.getBetaIps(), configRequestInfo.getSrcIp(), configForm.getSrcUser()); + if (!configOperateResult.isSuccess()) { + LOGGER.warn( + "[cas-publish-beta-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, msg = server md5 may have changed.", + configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5()); + throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.RESOURCE_CONFLICT, + "Cas publish beta config fail, server md5 may have changed."); + } + } else { + configInfoBetaPersistService.insertOrUpdateBeta(configInfo, + configRequestInfo.getBetaIps(), configRequestInfo.getSrcIp(), configForm.getSrcUser()); + } + + } + + /** + * publish gray config tag v2. + * + * @param configForm ConfigForm + * @param configRequestInfo ConfigRequestInfo + * @return boolean + * @throws NacosException NacosException. + * @date 2024/2/5 + */ + private Boolean publishConfigGray(String grayType, ConfigForm configForm, ConfigRequestInfo configRequestInfo) + throws NacosException { + + Map configAdvanceInfo = getConfigAdvanceInfo(configForm); + ParamUtils.checkParam(configAdvanceInfo); + + ConfigGrayPersistInfo localConfigGrayPersistInfo = new ConfigGrayPersistInfo(grayType, + configForm.getGrayVersion(), configForm.getGrayRuleExp(), configForm.getGrayPriority()); + GrayRule grayRuleStruct = GrayRuleManager.constructGrayRule(localConfigGrayPersistInfo); + if (grayRuleStruct == null) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.CONFIG_GRAY_VERSION_INVALID, + ErrorCode.CONFIG_GRAY_VERSION_INVALID.getMsg()); + } + + if (!grayRuleStruct.isValid()) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.CONFIG_GRAY_RULE_FORMAT_INVALID, + ErrorCode.CONFIG_GRAY_RULE_FORMAT_INVALID.getMsg()); + } + + //version count check. + if (checkGrayVersionOverMaxCount(configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId(), + configForm.getGrayName())) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.CONFIG_GRAY_OVER_MAX_VERSION_COUNT, + "gray config version is over max count :" + getMaxGrayVersionCount()); + } + + ConfigInfo configInfo = new ConfigInfo(configForm.getDataId(), configForm.getGroup(), + configForm.getNamespaceId(), configForm.getAppName(), configForm.getContent()); + configInfo.setType(configForm.getType()); + configInfo.setEncryptedDataKey(configForm.getEncryptedDataKey()); + ConfigOperateResult configOperateResult; - if (StringUtils.isBlank(configRequestInfo.getBetaIps())) { - if (StringUtils.isBlank(configForm.getTag())) { - if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) { - configOperateResult = configInfoPersistService.insertOrUpdateCas(configRequestInfo.getSrcIp(), - configForm.getSrcUser(), configInfo, configAdvanceInfo); - if (!configOperateResult.isSuccess()) { - LOGGER.warn( - "[cas-publish-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, msg = server md5 may have changed.", - configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5()); - throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), - ErrorCode.RESOURCE_CONFLICT, "Cas publish fail, server md5 may have changed."); - } - } else { - configOperateResult = configInfoPersistService.insertOrUpdate(configRequestInfo.getSrcIp(), - configForm.getSrcUser(), configInfo, configAdvanceInfo); - } - ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, configForm.getDataId(), configForm.getGroup(), - configForm.getNamespaceId(), configOperateResult.getLastModified())); - } else { - if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) { - configOperateResult = configInfoTagPersistService.insertOrUpdateTagCas(configInfo, - configForm.getTag(), configRequestInfo.getSrcIp(), configForm.getSrcUser()); - if (!configOperateResult.isSuccess()) { - LOGGER.warn( - "[cas-publish-tag-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, msg = server md5 may have changed.", - configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5()); - throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), - ErrorCode.RESOURCE_CONFLICT, - "Cas publish tag config fail, server md5 may have changed."); - } - } else { - configOperateResult = configInfoTagPersistService.insertOrUpdateTag(configInfo, configForm.getTag(), - configRequestInfo.getSrcIp(), configForm.getSrcUser()); - } - persistEvent = ConfigTraceService.PERSISTENCE_EVENT_TAG + "-" + configForm.getTag(); - ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, configForm.getDataId(), configForm.getGroup(), - configForm.getNamespaceId(), configForm.getTag(), - configOperateResult.getLastModified())); + + if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) { + configOperateResult = configInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, + configForm.getGrayName(), + GrayRuleManager.serializeConfigGrayPersistInfo(localConfigGrayPersistInfo), + configRequestInfo.getSrcIp(), configForm.getSrcUser()); + if (!configOperateResult.isSuccess()) { + LOGGER.warn( + "[cas-publish-gray-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, grayName = {}, msg = server md5 may have changed.", + configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5(), + configForm.getGrayName()); + throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.RESOURCE_CONFLICT, + "Cas publish gray config fail, server md5 may have changed."); } } else { - // beta publish - if (StringUtils.isNotBlank(configRequestInfo.getCasMd5())) { - configOperateResult = configInfoBetaPersistService.insertOrUpdateBetaCas(configInfo, - configRequestInfo.getBetaIps(), configRequestInfo.getSrcIp(), configForm.getSrcUser()); - if (!configOperateResult.isSuccess()) { - LOGGER.warn( - "[cas-publish-beta-config-fail] srcIp = {}, dataId= {}, casMd5 = {}, msg = server md5 may have changed.", - configRequestInfo.getSrcIp(), configForm.getDataId(), configRequestInfo.getCasMd5()); - throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.RESOURCE_CONFLICT, - "Cas publish beta config fail, server md5 may have changed."); - } - } else { - configOperateResult = configInfoBetaPersistService.insertOrUpdateBeta(configInfo, - configRequestInfo.getBetaIps(), configRequestInfo.getSrcIp(), configForm.getSrcUser()); + configOperateResult = configInfoGrayPersistService.insertOrUpdateGray(configInfo, configForm.getGrayName(), + GrayRuleManager.serializeConfigGrayPersistInfo(localConfigGrayPersistInfo), + configRequestInfo.getSrcIp(), configForm.getSrcUser()); + } + + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent(configForm.getDataId(), configForm.getGroup(), configForm.getNamespaceId(), + configForm.getGrayName(), configOperateResult.getLastModified())); + + String eventType = ConfigTraceService.PERSISTENCE_EVENT + "-" + configForm.getGrayName(); + + ConfigTraceService.logPersistenceEvent(configForm.getDataId(), configForm.getGroup(), + configForm.getNamespaceId(), configRequestInfo.getRequestIpApp(), configOperateResult.getLastModified(), + InetUtils.getSelfIP(), eventType, ConfigTraceService.PERSISTENCE_TYPE_PUB, configForm.getContent()); + return true; + } + + private boolean checkGrayVersionOverMaxCount(String dataId, String group, String tenant, String grayName) { + List configInfoGrays = configInfoGrayPersistService.findConfigInfoGrays(dataId, group, tenant); + if (configInfoGrays == null || configInfoGrays.isEmpty()) { + return false; + } else { + if (configInfoGrays.contains(grayName)) { + return false; } - persistEvent = ConfigTraceService.PERSISTENCE_EVENT_BETA; - ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(true, configForm.getDataId(), configForm.getGroup(), - configForm.getNamespaceId(), configOperateResult.getLastModified())); + return configInfoGrays.size() >= getMaxGrayVersionCount(); } - return configOperateResult; + } + + private static final int DEFAULT_MAX_GRAY_VERSION_COUNT = 10; + + private int getMaxGrayVersionCount() { + String value = EnvUtil.getProperty("nacos.config.gray.version.max.count", ""); + return NumberUtils.isDigits(value) ? NumberUtils.toInt(value) : DEFAULT_MAX_GRAY_VERSION_COUNT; } /** @@ -175,21 +304,31 @@ private ConfigOperateResult processBuildConfigOperateResult(Map public Boolean deleteConfig(String dataId, String group, String namespaceId, String tag, String clientIp, String srcUser) { String persistEvent = ConfigTraceService.PERSISTENCE_EVENT; + String grayName = ""; if (StringUtils.isBlank(tag)) { configInfoPersistService.removeConfigInfo(dataId, group, namespaceId, clientIp, srcUser); } else { persistEvent = ConfigTraceService.PERSISTENCE_EVENT_TAG + "-" + tag; - configInfoTagPersistService.removeConfigInfoTag(dataId, group, namespaceId, tag, clientIp, srcUser); + grayName = TagGrayRule.TYPE_TAG + "_" + tag; + configInfoGrayPersistService.removeConfigInfoGray(dataId, group, namespaceId, grayName, clientIp, srcUser); + deleteConfigTagv1(dataId, group, namespaceId, tag, clientIp, srcUser); } final Timestamp time = TimeUtils.getCurrentTime(); ConfigTraceService.logPersistenceEvent(dataId, group, namespaceId, null, time.getTime(), clientIp, persistEvent, ConfigTraceService.PERSISTENCE_TYPE_REMOVE, null); ConfigChangePublisher.notifyConfigChange( - new ConfigDataChangeEvent(false, dataId, group, namespaceId, tag, time.getTime())); + new ConfigDataChangeEvent(dataId, group, namespaceId, grayName, time.getTime())); return true; } + private void deleteConfigTagv1(String dataId, String group, String namespaceId, String tag, String clientIp, + String srcUser) { + if (PropertyUtil.isGrayCompatibleModel()) { + configInfoTagPersistService.removeConfigInfoTag(dataId, group, namespaceId, tag, clientIp, srcUser); + } + } + public Map getConfigAdvanceInfo(ConfigForm configForm) { Map configAdvanceInfo = new HashMap<>(10); MapUtil.putIfValNoNull(configAdvanceInfo, "config_tags", configForm.getConfigTags()); diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java index ebf7afabb1a..1a2d3f42bce 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/ConfigSubService.java @@ -160,12 +160,13 @@ public T call() throws Exception { List runJobs() { Collection ipList = serverMemberManager.allMembers(); List collectionResult = new ArrayList<>(ipList.size()); + // Submit query task. for (Member ip : ipList) { try { completionService.submit(new Job(ip.getAddress()) { }); - } catch (Exception e) { // Send request failed. + } catch (Throwable e) { // Send request failed. LogUtil.DEFAULT_LOG.warn("invoke to {} with exception: {} during submit job", ip, e.getMessage()); } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/HistoryService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/HistoryService.java index 801ed7d4c8c..1db81b932da 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/HistoryService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/HistoryService.java @@ -17,13 +17,19 @@ package com.alibaba.nacos.config.server.service; import com.alibaba.nacos.common.utils.Pair; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.enums.OperationType; import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfoDetail; +import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; -import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.plugin.auth.exception.AccessException; import com.alibaba.nacos.plugin.encryption.handler.EncryptionHandler; +import org.springframework.beans.BeanUtils; import org.springframework.stereotype.Service; import java.util.List; @@ -42,10 +48,14 @@ public class HistoryService { private final ConfigInfoPersistService configInfoPersistService; + private final ConfigInfoGrayPersistService configInfoGrayPersistService; + public HistoryService(HistoryConfigInfoPersistService historyConfigInfoPersistService, - ConfigInfoPersistService configInfoPersistService) { + ConfigInfoPersistService configInfoPersistService, + ConfigInfoGrayPersistService configInfoGrayPersistService) { this.historyConfigInfoPersistService = historyConfigInfoPersistService; this.configInfoPersistService = configInfoPersistService; + this.configInfoGrayPersistService = configInfoGrayPersistService; } /** @@ -69,8 +79,8 @@ public ConfigHistoryInfo getConfigHistoryInfo(String dataId, String group, Strin checkHistoryInfoPermission(configHistoryInfo, dataId, group, namespaceId); String encryptedDataKey = configHistoryInfo.getEncryptedDataKey(); - Pair pair = EncryptionHandler - .decryptHandler(dataId, encryptedDataKey, configHistoryInfo.getContent()); + Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, + configHistoryInfo.getContent()); configHistoryInfo.setContent(pair.getSecond()); return configHistoryInfo; @@ -89,8 +99,8 @@ public ConfigHistoryInfo getPreviousConfigHistoryInfo(String dataId, String grou checkHistoryInfoPermission(configHistoryInfo, dataId, group, namespaceId); String encryptedDataKey = configHistoryInfo.getEncryptedDataKey(); - Pair pair = EncryptionHandler - .decryptHandler(dataId, encryptedDataKey, configHistoryInfo.getContent()); + Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, + configHistoryInfo.getContent()); configHistoryInfo.setContent(pair.getSecond()); return configHistoryInfo; @@ -108,10 +118,101 @@ public List getConfigListByNamespace(String namespaceId) { */ private void checkHistoryInfoPermission(ConfigHistoryInfo configHistoryInfo, String dataId, String group, String namespaceId) throws AccessException { - if (!Objects.equals(configHistoryInfo.getDataId(), dataId) || !Objects - .equals(configHistoryInfo.getGroup(), group) || !Objects - .equals(configHistoryInfo.getTenant(), namespaceId)) { + if (!Objects.equals(configHistoryInfo.getDataId(), dataId) || !Objects.equals(configHistoryInfo.getGroup(), + group) || !Objects.equals(configHistoryInfo.getTenant(), namespaceId)) { throw new AccessException("Please check dataId, group or namespaceId."); } } -} + + /** + * Query the detailed config history info pair, including the original version and the updated version. + */ + public ConfigHistoryInfoDetail getConfigHistoryInfoDetail(String dataId, String group, String namespaceId, Long nid) + throws AccessException { + ConfigHistoryInfo configHistoryInfo = historyConfigInfoPersistService.detailConfigHistory(nid); + if (Objects.isNull(configHistoryInfo)) { + return null; + } + + // check if history config match the input + checkHistoryInfoPermission(configHistoryInfo, dataId, group, namespaceId); + + // transform + ConfigHistoryInfoDetail configHistoryInfoDetail = new ConfigHistoryInfoDetail(); + BeanUtils.copyProperties(configHistoryInfo, configHistoryInfoDetail); + configHistoryInfoDetail.setOpType(configHistoryInfoDetail.getOpType().trim()); + + //insert + if (OperationType.INSERT.getValue().equals(configHistoryInfoDetail.getOpType())) { + configHistoryInfoDetail.setUpdatedContent(configHistoryInfo.getContent()); + configHistoryInfoDetail.setUpdatedMd5(configHistoryInfo.getMd5()); + configHistoryInfoDetail.setUpdatedEncryptedDataKey(configHistoryInfo.getEncryptedDataKey()); + configHistoryInfoDetail.setUpdateExtInfo(configHistoryInfo.getExtInfo()); + configHistoryInfoDetail.setOriginalExtInfo(StringUtils.EMPTY); + configHistoryInfoDetail.setOriginalContent(StringUtils.EMPTY); + configHistoryInfoDetail.setOriginalMd5(StringUtils.EMPTY); + configHistoryInfoDetail.setOriginalEncryptedDataKey(StringUtils.EMPTY); + } + + //update + if (OperationType.UPDATE.getValue().equals(configHistoryInfoDetail.getOpType())) { + + configHistoryInfoDetail.setOriginalExtInfo(configHistoryInfo.getExtInfo()); + configHistoryInfoDetail.setOriginalContent(configHistoryInfo.getContent()); + configHistoryInfoDetail.setOriginalMd5(configHistoryInfo.getMd5()); + configHistoryInfoDetail.setOriginalEncryptedDataKey(configHistoryInfo.getEncryptedDataKey()); + + ConfigHistoryInfo nextHistoryInfo = historyConfigInfoPersistService.getNextHistoryInfo(dataId, group, + namespaceId, configHistoryInfoDetail.getPublishType(), configHistoryInfoDetail.getGrayName(), nid); + + ConfigInfo currentConfigInfo = null; + if (Objects.isNull(nextHistoryInfo)) { + //double check for concurrent + currentConfigInfo = StringUtils.isEmpty(configHistoryInfoDetail.getGrayName()) + ? configInfoPersistService.findConfigInfo(dataId, group, namespaceId) + : configInfoGrayPersistService.findConfigInfo4Gray(dataId, group, namespaceId, + configHistoryInfoDetail.getGrayName()); + nextHistoryInfo = historyConfigInfoPersistService.getNextHistoryInfo(dataId, group, namespaceId, + configHistoryInfoDetail.getPublishType(), configHistoryInfoDetail.getGrayName(), nid); + + } + + if (nextHistoryInfo != null) { + configHistoryInfoDetail.setUpdateExtInfo(nextHistoryInfo.getExtInfo()); + configHistoryInfoDetail.setUpdatedContent(nextHistoryInfo.getContent()); + configHistoryInfoDetail.setUpdatedMd5(nextHistoryInfo.getMd5()); + configHistoryInfoDetail.setUpdatedEncryptedDataKey(nextHistoryInfo.getEncryptedDataKey()); + } else { + configHistoryInfoDetail.setUpdatedContent(currentConfigInfo.getContent()); + configHistoryInfoDetail.setUpdatedMd5(currentConfigInfo.getMd5()); + configHistoryInfoDetail.setUpdatedEncryptedDataKey(currentConfigInfo.getEncryptedDataKey()); + + } + } + + //delete + if (OperationType.DELETE.getValue().equals(configHistoryInfoDetail.getOpType())) { + configHistoryInfoDetail.setOriginalMd5(configHistoryInfo.getMd5()); + configHistoryInfoDetail.setOriginalContent(configHistoryInfo.getContent()); + configHistoryInfoDetail.setOriginalEncryptedDataKey(configHistoryInfo.getEncryptedDataKey()); + configHistoryInfoDetail.setOriginalExtInfo(configHistoryInfo.getExtInfo()); + } + + // decrypt content + if (StringUtils.isNotBlank(configHistoryInfoDetail.getOriginalContent())) { + String originalContent = EncryptionHandler.decryptHandler(dataId, + configHistoryInfoDetail.getOriginalEncryptedDataKey(), configHistoryInfoDetail.getOriginalContent()) + .getSecond(); + configHistoryInfoDetail.setOriginalContent(originalContent); + } + if (StringUtils.isNotBlank(configHistoryInfoDetail.getUpdatedContent())) { + String updatedContent = EncryptionHandler.decryptHandler(dataId, + configHistoryInfoDetail.getUpdatedEncryptedDataKey(), configHistoryInfoDetail.getUpdatedContent()) + .getSecond(); + configHistoryInfoDetail.setUpdatedContent(updatedContent); + } + + return configHistoryInfoDetail; + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/LongPollingService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/LongPollingService.java index 6635bd7725d..76769549776 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/LongPollingService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/LongPollingService.java @@ -21,7 +21,6 @@ import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.notify.listener.Subscriber; import com.alibaba.nacos.common.utils.ExceptionUtil; -import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.model.SampleResult; import com.alibaba.nacos.config.server.model.event.LocalDataChangeEvent; import com.alibaba.nacos.config.server.monitor.MetricsMonitor; @@ -243,7 +242,7 @@ public LongPollingService() { public void onEvent(Event event) { if (event instanceof LocalDataChangeEvent) { LocalDataChangeEvent evt = (LocalDataChangeEvent) event; - ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey, evt.isBeta, evt.betaIps)); + ConfigExecutor.executeLongPolling(new DataChangeTask(evt.groupKey)); } } @@ -274,11 +273,6 @@ public void run() { ClientLongPolling clientSub = iter.next(); if (clientSub.clientMd5Map.containsKey(groupKey)) { - // If published tag is not in the tag list, then it skipped. - if (StringUtils.isNotBlank(tag) && !tag.equals(clientSub.tag)) { - continue; - } - getRetainIps().put(clientSub.ip, System.currentTimeMillis()); iter.remove(); // Delete subscribers' relationships. LogUtil.CLIENT_LOG.info("{}|{}|{}|{}|{}|{}|{}", (System.currentTimeMillis() - changeTime), @@ -294,26 +288,14 @@ public void run() { } } - DataChangeTask(String groupKey, boolean isBeta, List betaIps) { - this(groupKey, isBeta, betaIps, null); - } - - DataChangeTask(String groupKey, boolean isBeta, List betaIps, String tag) { + DataChangeTask(String groupKey) { this.groupKey = groupKey; - this.isBeta = isBeta; - this.betaIps = betaIps; - this.tag = tag; } final String groupKey; final long changeTime = System.currentTimeMillis(); - final boolean isBeta; - - final List betaIps; - - final String tag; } class StatTask implements Runnable { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorker.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorker.java index a50d1112709..b917ddffd00 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorker.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorker.java @@ -76,14 +76,14 @@ public void run() { long deleteCursorId = 0L; while (true) { List configDeleted = historyConfigInfoPersistService.findDeletedConfig(startTime, - deleteCursorId, pageSize); + deleteCursorId, pageSize, Constants.FORMAL); for (ConfigInfoStateWrapper configInfo : configDeleted) { if (configInfoPersistService.findConfigInfoState(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()) == null) { ConfigCacheService.remove(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); - LogUtil.DEFAULT_LOG.info("[dump-delete-ok] {}", - GroupKey2.getKey(configInfo.getDataId(), configInfo.getGroup())); + LogUtil.DEFAULT_LOG.info("[dump-delete-ok], groupKey: {}, tenant: {}", + new Object[] {GroupKey2.getKey(configInfo.getDataId(), configInfo.getGroup())}, configInfo.getTenant()); } } if (configDeleted.size() < pageSize) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpChangeGrayConfigWorker.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpChangeGrayConfigWorker.java new file mode 100644 index 00000000000..5aa3b76c600 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpChangeGrayConfigWorker.java @@ -0,0 +1,149 @@ +/* + * 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.config.server.service.dump; + +import com.alibaba.nacos.common.utils.MD5Utils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; +import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; +import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.config.server.utils.ConfigExecutor; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.PropertyUtil; + +import java.sql.Timestamp; +import java.util.List; +import java.util.concurrent.TimeUnit; + +/** + * Dump gray config worker. + * + * @author shiyiyue + */ +public class DumpChangeGrayConfigWorker implements Runnable { + + Timestamp startTime; + + ConfigInfoGrayPersistService configInfoGrayPersistService; + + private final HistoryConfigInfoPersistService historyConfigInfoPersistService; + + int pageSize = 100; + + public DumpChangeGrayConfigWorker(ConfigInfoGrayPersistService configInfoGrayPersistService, Timestamp startTime, + HistoryConfigInfoPersistService historyConfigInfoPersistService) { + this.configInfoGrayPersistService = configInfoGrayPersistService; + this.startTime = startTime; + this.historyConfigInfoPersistService = historyConfigInfoPersistService; + } + + @Override + public void run() { + try { + if (!PropertyUtil.isDumpChangeOn()) { + LogUtil.DEFAULT_LOG.info("DumpGrayChange task is not open"); + return; + } + Timestamp currentTime = new Timestamp(System.currentTimeMillis()); + LogUtil.DEFAULT_LOG.info("DumpGrayChange start ,from time {},current time {}", startTime, currentTime); + + LogUtil.DEFAULT_LOG.info("Start to check delete configs from time {}", startTime); + long startDeletedConfigTime = System.currentTimeMillis(); + long deleteCursorId = 0L; + while (true) { + List configDeleted = historyConfigInfoPersistService.findDeletedConfig(startTime, + deleteCursorId, pageSize, Constants.GRAY); + for (ConfigInfoStateWrapper configInfo : configDeleted) { + String grayName = configInfo.getGrayName(); + if (StringUtils.isBlank(grayName)) { + continue; + } + + ConfigInfoStateWrapper configInfoStateWrapper = configInfoGrayPersistService.findConfigInfo4GrayState(configInfo.getDataId(), + configInfo.getGroup(), configInfo.getTenant(), grayName); + if (configInfoStateWrapper == null) { + ConfigCacheService.removeGray(configInfo.getDataId(), configInfo.getGroup(), + configInfo.getTenant(), grayName); + LogUtil.DEFAULT_LOG.info("[dump-gray-delete-ok], groupKey: {}, tenant: {}, grayName: {}", + GroupKey2.getKey(configInfo.getDataId(), configInfo.getGroup()), configInfo.getTenant(), grayName); + } + } + if (configDeleted.size() < pageSize) { + break; + } + deleteCursorId = configDeleted.get(configDeleted.size() - 1).getId(); + } + LogUtil.DEFAULT_LOG.info("Check delete configs finished,cost:{}", + System.currentTimeMillis() - startDeletedConfigTime); + + LogUtil.DEFAULT_LOG.info("Check changeGrayConfig start"); + long startChangeConfigTime = System.currentTimeMillis(); + + long changeCursorId = 0L; + while (true) { + LogUtil.DEFAULT_LOG.info("Check changed gray configs from time {},lastMaxId={}", startTime, + changeCursorId); + List changeConfigs = configInfoGrayPersistService.findChangeConfig(startTime, + changeCursorId, pageSize); + for (ConfigInfoGrayWrapper cf : changeConfigs) { + final String groupKey = GroupKey2.getKey(cf.getDataId(), cf.getGroup(), cf.getTenant()); + //check md5 & localtimestamp update local disk cache. + boolean newLastModified = cf.getLastModified() > ConfigCacheService.getLastModifiedTs(groupKey); + String localContentMd5 = ConfigCacheService.getContentMd5(groupKey); + boolean md5Update = !localContentMd5.equals(cf.getMd5()); + if (newLastModified || md5Update) { + LogUtil.DEFAULT_LOG.info("[dump-change-gray] find change config {}, {}, md5={}", + new Object[] {groupKey, cf.getLastModified(), cf.getMd5()}); + + LogUtil.DUMP_LOG.info("[dump-change-gray] find change config {}, {}, md5={}", + new Object[] {groupKey, cf.getLastModified(), cf.getMd5()}); + ConfigCacheService.dumpGray(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getGrayName(), + cf.getGrayRule(), cf.getContent(), cf.getLastModified(), cf.getEncryptedDataKey()); + final String content = cf.getContent(); + final String md5 = MD5Utils.md5Hex(content, Constants.ENCODE_GBK); + final String md5Utf8 = MD5Utils.md5Hex(content, Constants.ENCODE_UTF8); + + LogUtil.DEFAULT_LOG.info("[dump-change-gray-ok] {}, {}, length={}, md5={},md5UTF8={}", + new Object[] {groupKey, cf.getLastModified(), content.length(), md5, md5Utf8}); + } + } + if (changeConfigs.size() < pageSize) { + break; + } + changeCursorId = changeConfigs.get(changeConfigs.size() - 1).getId(); + } + + long endChangeConfigTime = System.currentTimeMillis(); + LogUtil.DEFAULT_LOG.info( + "Check changed gray configs finished,cost:{}, next task running will from start time {}", + endChangeConfigTime - startChangeConfigTime, currentTime); + startTime = currentTime; + } catch (Throwable e) { + LogUtil.DEFAULT_LOG.error("Check changed gray configs error", e); + } finally { + ConfigExecutor.scheduleConfigChangeTask(this, PropertyUtil.getDumpChangeWorkerInterval(), + TimeUnit.MILLISECONDS); + LogUtil.DEFAULT_LOG.info("Next dump gray change will scheduled after {} milliseconds", + PropertyUtil.getDumpChangeWorkerInterval()); + + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpConfigHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpConfigHandler.java index 153dec6e2bc..e10b1b51dff 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpConfigHandler.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpConfigHandler.java @@ -20,7 +20,6 @@ import com.alibaba.nacos.common.notify.Event; import com.alibaba.nacos.common.notify.listener.Subscriber; import com.alibaba.nacos.config.server.model.event.ConfigDumpEvent; -import com.alibaba.nacos.config.server.service.AggrWhitelist; import com.alibaba.nacos.config.server.service.ClientIpWhiteList; import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.config.server.service.SwitchService; @@ -44,78 +43,59 @@ public static boolean configDump(ConfigDumpEvent event) { final String group = event.getGroup(); final String namespaceId = event.getNamespaceId(); final String content = event.getContent(); - final String type = event.getType(); final long lastModified = event.getLastModifiedTs(); - //beta - if (event.isBeta()) { - boolean result = false; - if (event.isRemove()) { - result = ConfigCacheService.removeBeta(dataId, group, namespaceId); - if (result) { - ConfigTraceService.logDumpBetaEvent(dataId, group, namespaceId, null, lastModified, - event.getHandleIp(), ConfigTraceService.DUMP_TYPE_REMOVE_OK, - System.currentTimeMillis() - lastModified, 0); - } - return result; - } else { - result = ConfigCacheService.dumpBeta(dataId, group, namespaceId, content, lastModified, - event.getBetaIps(), event.getEncryptedDataKey()); - if (result) { - ConfigTraceService.logDumpBetaEvent(dataId, group, namespaceId, null, lastModified, - event.getHandleIp(), ConfigTraceService.DUMP_TYPE_OK, - System.currentTimeMillis() - lastModified, content.length()); - } - } - return result; - } - //tag - if (StringUtils.isNotBlank(event.getTag())) { + + //gray + if (StringUtils.isNotBlank(event.getGrayName())) { // - boolean result; + boolean result = false; if (!event.isRemove()) { - result = ConfigCacheService.dumpTag(dataId, group, namespaceId, event.getTag(), content, lastModified, - event.getEncryptedDataKey()); + result = ConfigCacheService.dumpGray(dataId, group, namespaceId, event.getGrayName(), + event.getGrayRule(), content, lastModified, event.getEncryptedDataKey()); if (result) { - ConfigTraceService.logDumpTagEvent(dataId, group, namespaceId, event.getTag(), null, lastModified, - event.getHandleIp(), ConfigTraceService.DUMP_TYPE_OK, + ConfigTraceService.logDumpGrayNameEvent(dataId, group, namespaceId, event.getGrayName(), null, + lastModified, event.getHandleIp(), ConfigTraceService.DUMP_TYPE_OK, System.currentTimeMillis() - lastModified, content.length()); } } else { - result = ConfigCacheService.removeTag(dataId, group, namespaceId, event.getTag()); + result = ConfigCacheService.removeGray(dataId, group, namespaceId, event.getGrayName()); if (result) { - ConfigTraceService.logDumpTagEvent(dataId, group, namespaceId, event.getTag(), null, lastModified, - event.getHandleIp(), ConfigTraceService.DUMP_TYPE_REMOVE_OK, + ConfigTraceService.logDumpGrayNameEvent(dataId, group, namespaceId, event.getGrayName(), null, + lastModified, event.getHandleIp(), ConfigTraceService.DUMP_TYPE_REMOVE_OK, System.currentTimeMillis() - lastModified, 0); } } + return result; } - //default - if (dataId.equals(AggrWhitelist.AGGRIDS_METADATA)) { - AggrWhitelist.load(content); - } + if (dataId.equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) { ClientIpWhiteList.load(content); } + if (dataId.equals(SwitchService.SWITCH_META_DATA_ID)) { SwitchService.load(content); } + boolean result; if (!event.isRemove()) { result = ConfigCacheService.dump(dataId, group, namespaceId, content, lastModified, event.getType(), event.getEncryptedDataKey()); + if (result) { ConfigTraceService.logDumpEvent(dataId, group, namespaceId, null, lastModified, event.getHandleIp(), ConfigTraceService.DUMP_TYPE_OK, System.currentTimeMillis() - lastModified, content.length()); } } else { result = ConfigCacheService.remove(dataId, group, namespaceId); + if (result) { ConfigTraceService.logDumpEvent(dataId, group, namespaceId, null, lastModified, event.getHandleIp(), ConfigTraceService.DUMP_TYPE_REMOVE_OK, System.currentTimeMillis() - lastModified, 0); } } return result; + } @Override diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpRequest.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpRequest.java index 54ad18c9454..8d432bd6fb9 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpRequest.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpRequest.java @@ -19,6 +19,7 @@ /** * dump request. + * * @author shiyiyue */ public class DumpRequest { @@ -29,11 +30,7 @@ public class DumpRequest { String tenant; - private boolean isBeta; - - private boolean isBatch; - - private String tag; + String grayName; private long lastModifiedTs; @@ -63,30 +60,6 @@ public void setTenant(String tenant) { this.tenant = tenant; } - public boolean isBeta() { - return isBeta; - } - - public void setBeta(boolean beta) { - isBeta = beta; - } - - public boolean isBatch() { - return isBatch; - } - - public void setBatch(boolean batch) { - isBatch = batch; - } - - public String getTag() { - return tag; - } - - public void setTag(String tag) { - this.tag = tag; - } - public long getLastModifiedTs() { return lastModifiedTs; } @@ -99,17 +72,26 @@ public String getSourceIp() { return sourceIp; } + public String getGrayName() { + return grayName; + } + + public void setGrayName(String grayName) { + this.grayName = grayName; + } + public void setSourceIp(String sourceIp) { this.sourceIp = sourceIp; } /** * create dump request. - * @param dataId dataId. - * @param group group. - * @param tenant tenant. + * + * @param dataId dataId. + * @param group group. + * @param tenant tenant. * @param lastModifiedTs lastModifiedTs. - * @param sourceIp sourceIp. + * @param sourceIp sourceIp. * @return */ public static DumpRequest create(String dataId, String group, String tenant, long lastModifiedTs, String sourceIp) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java index 9b450c2db71..37c4d5778e4 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/DumpService.java @@ -23,22 +23,16 @@ import com.alibaba.nacos.common.notify.listener.Subscriber; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.manager.TaskManager; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; -import com.alibaba.nacos.config.server.service.dump.processor.DumpAllBetaProcessor; +import com.alibaba.nacos.config.server.service.dump.processor.DumpAllGrayProcessor; import com.alibaba.nacos.config.server.service.dump.processor.DumpAllProcessor; -import com.alibaba.nacos.config.server.service.dump.processor.DumpAllTagProcessor; import com.alibaba.nacos.config.server.service.dump.processor.DumpProcessor; -import com.alibaba.nacos.config.server.service.dump.task.DumpAllBetaTask; -import com.alibaba.nacos.config.server.service.dump.task.DumpAllTagTask; +import com.alibaba.nacos.config.server.service.dump.task.DumpAllGrayTask; import com.alibaba.nacos.config.server.service.dump.task.DumpAllTask; import com.alibaba.nacos.config.server.service.dump.task.DumpTask; -import com.alibaba.nacos.config.server.service.merge.MergeDatumService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.config.server.utils.ConfigExecutor; import com.alibaba.nacos.config.server.utils.GroupKey2; @@ -53,7 +47,6 @@ import org.slf4j.LoggerFactory; import java.sql.Timestamp; -import java.util.List; import java.util.Random; import java.util.concurrent.TimeUnit; @@ -73,9 +66,7 @@ public abstract class DumpService { protected DumpAllProcessor dumpAllProcessor; - protected DumpAllBetaProcessor dumpAllBetaProcessor; - - protected DumpAllTagProcessor dumpAllTagProcessor; + protected DumpAllGrayProcessor dumpAllGrayProcessor; protected ConfigInfoPersistService configInfoPersistService; @@ -83,13 +74,7 @@ public abstract class DumpService { protected HistoryConfigInfoPersistService historyConfigInfoPersistService; - protected ConfigInfoAggrPersistService configInfoAggrPersistService; - - protected ConfigInfoBetaPersistService configInfoBetaPersistService; - - protected ConfigInfoTagPersistService configInfoTagPersistService; - - protected MergeDatumService mergeDatumService; + protected ConfigInfoGrayPersistService configInfoGrayPersistService; protected final ServerMemberManager memberManager; @@ -111,12 +96,6 @@ public abstract class DumpService { int total = 0; - private static final String BETA_TABLE_NAME = "config_info_beta"; - - private static final String TAG_TABLE_NAME = "config_info_tag"; - - private int retentionDays = 30; - /** * Here you inject the dependent objects constructively, ensuring that some of the dependent functionality is * initialized ahead of time. @@ -126,23 +105,15 @@ public abstract class DumpService { public DumpService(ConfigInfoPersistService configInfoPersistService, NamespacePersistService namespacePersistService, HistoryConfigInfoPersistService historyConfigInfoPersistService, - ConfigInfoAggrPersistService configInfoAggrPersistService, - ConfigInfoBetaPersistService configInfoBetaPersistService, - ConfigInfoTagPersistService configInfoTagPersistService, MergeDatumService mergeDatumService, - ServerMemberManager memberManager) { + ConfigInfoGrayPersistService configInfoGrayPersistService, ServerMemberManager memberManager) { this.configInfoPersistService = configInfoPersistService; + this.configInfoGrayPersistService = configInfoGrayPersistService; this.namespacePersistService = namespacePersistService; this.historyConfigInfoPersistService = historyConfigInfoPersistService; - this.configInfoAggrPersistService = configInfoAggrPersistService; - this.configInfoBetaPersistService = configInfoBetaPersistService; - this.configInfoTagPersistService = configInfoTagPersistService; - this.mergeDatumService = mergeDatumService; this.memberManager = memberManager; - this.processor = new DumpProcessor(this.configInfoPersistService, this.configInfoBetaPersistService, - this.configInfoTagPersistService); + this.processor = new DumpProcessor(this.configInfoPersistService, this.configInfoGrayPersistService); this.dumpAllProcessor = new DumpAllProcessor(this.configInfoPersistService); - this.dumpAllBetaProcessor = new DumpAllBetaProcessor(this.configInfoBetaPersistService); - this.dumpAllTagProcessor = new DumpAllTagProcessor(this.configInfoTagPersistService); + this.dumpAllGrayProcessor = new DumpAllGrayProcessor(this.configInfoGrayPersistService); this.dumpTaskMgr = new TaskManager("com.alibaba.nacos.server.DumpTaskManager"); this.dumpTaskMgr.setDefaultTaskProcessor(processor); @@ -150,9 +121,6 @@ public DumpService(ConfigInfoPersistService configInfoPersistService, this.dumpAllTaskMgr.setDefaultTaskProcessor(dumpAllProcessor); this.dumpAllTaskMgr.addProcessor(DumpAllTask.TASK_ID, dumpAllProcessor); - this.dumpAllTaskMgr.addProcessor(DumpAllBetaTask.TASK_ID, dumpAllBetaProcessor); - this.dumpAllTaskMgr.addProcessor(DumpAllTagTask.TASK_ID, dumpAllTagProcessor); - DynamicDataSource.getInstance().getDataSource(); NotifyCenter.registerSubscriber(new Subscriber() { @@ -173,12 +141,9 @@ void handleConfigDataChange(Event event) { // Generate ConfigDataChangeEvent concurrently if (event instanceof ConfigDataChangeEvent) { ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event; - DumpRequest dumpRequest = DumpRequest.create(evt.dataId, evt.group, evt.tenant, evt.lastModifiedTs, - NetUtils.localIp()); - dumpRequest.setBeta(evt.isBeta); - dumpRequest.setBatch(evt.isBatch); - dumpRequest.setTag(evt.tag); + NetUtils.localIP()); + dumpRequest.setGrayName(evt.grayName); DumpService.this.dump(dumpRequest); } } @@ -230,24 +195,13 @@ public void run() { } /** - * dump all beta processor runner. + * dump all gray processor runner. */ - class DumpAllBetaProcessorRunner implements Runnable { + class DumpAllGrayProcessorRunner implements Runnable { @Override public void run() { - dumpAllTaskMgr.addTask(DumpAllBetaTask.TASK_ID, new DumpAllBetaTask()); - } - } - - /** - * dump all tag processor runner. - */ - class DumpAllTagProcessorRunner implements Runnable { - - @Override - public void run() { - dumpAllTaskMgr.addTask(DumpAllTagTask.TASK_ID, new DumpAllTagTask()); + dumpAllTaskMgr.addTask(DumpAllGrayTask.TASK_ID, new DumpAllGrayTask()); } } @@ -262,30 +216,10 @@ protected void dumpOperate() throws NacosException { try { dumpAllConfigInfoOnStartup(dumpAllProcessor); - // update Beta cache - LogUtil.DEFAULT_LOG.info("start clear all config-info-beta."); - ConfigDiskServiceFactory.getInstance().clearAllBeta(); - if (namespacePersistService.isExistTable(BETA_TABLE_NAME)) { - dumpAllBetaProcessor.process(new DumpAllBetaTask()); - } - // update Tag cache - LogUtil.DEFAULT_LOG.info("start clear all config-info-tag."); - ConfigDiskServiceFactory.getInstance().clearAllTag(); - if (namespacePersistService.isExistTable(TAG_TABLE_NAME)) { - dumpAllTagProcessor.process(new DumpAllTagTask()); - } + LogUtil.DEFAULT_LOG.info("start clear all config-info-gray."); + ConfigDiskServiceFactory.getInstance().clearAllGray(); + dumpAllGrayProcessor.process(new DumpAllGrayTask()); - // add to dump aggr - List configList = configInfoAggrPersistService.findAllAggrGroup(); - if (configList != null && !configList.isEmpty()) { - total = configList.size(); - List> splitList = mergeDatumService.splitList(configList, - INIT_THREAD_COUNT); - for (List list : splitList) { - mergeDatumService.executeConfigsMerge(list); - } - LOGGER.info("server start, schedule merge end."); - } } catch (Exception e) { LogUtil.FATAL_LOG.error( "Nacos Server did not start because dumpservice bean construction failure :\n" + e); @@ -301,17 +235,17 @@ protected void dumpOperate() throws NacosException { ConfigExecutor.scheduleConfigTask(new DumpAllProcessorRunner(), initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES); - - ConfigExecutor.scheduleConfigTask(new DumpAllBetaProcessorRunner(), initialDelay, + ConfigExecutor.scheduleConfigTask(new DumpAllGrayProcessorRunner(), initialDelay, DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES); - ConfigExecutor.scheduleConfigTask(new DumpAllTagProcessorRunner(), initialDelay, - DUMP_ALL_INTERVAL_IN_MINUTE, TimeUnit.MINUTES); ConfigExecutor.scheduleConfigChangeTask( new DumpChangeConfigWorker(this.configInfoPersistService, this.historyConfigInfoPersistService, currentTime), random.nextInt((int) PropertyUtil.getDumpChangeWorkerInterval()), TimeUnit.MILLISECONDS); - + ConfigExecutor.scheduleConfigChangeTask( + new DumpChangeGrayConfigWorker(this.configInfoGrayPersistService, currentTime, + this.historyConfigInfoPersistService), + random.nextInt((int) PropertyUtil.getDumpChangeWorkerInterval()), TimeUnit.MILLISECONDS); } HistoryConfigCleaner cleaner = HistoryConfigCleanerManager.getHistoryConfigCleaner( @@ -342,15 +276,9 @@ private void dumpAllConfigInfoOnStartup(DumpAllProcessor dumpAllProcessor) { * @param dumpRequest dumpRequest. */ public void dump(DumpRequest dumpRequest) { - if (dumpRequest.isBeta()) { - dumpBeta(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(), - dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp()); - } else if (dumpRequest.isBatch()) { - dumpBatch(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(), - dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp()); - } else if (StringUtils.isNotBlank(dumpRequest.getTag())) { - dumpTag(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(), dumpRequest.getTag(), - dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp()); + if (StringUtils.isNotBlank(dumpRequest.getGrayName())) { + dumpGray(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(), + dumpRequest.getGrayName(), dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp()); } else { dumpFormal(dumpRequest.getDataId(), dumpRequest.getGroup(), dumpRequest.getTenant(), dumpRequest.getLastModifiedTs(), dumpRequest.getSourceIp()); @@ -369,59 +297,27 @@ public void dump(DumpRequest dumpRequest) { private void dumpFormal(String dataId, String group, String tenant, long lastModified, String handleIp) { String groupKey = GroupKey2.getKey(dataId, group, tenant); String taskKey = groupKey; - dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, false, false, false, null, lastModified, handleIp)); + dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, null, lastModified, handleIp)); DUMP_LOG.info("[dump] add formal task. groupKey={}", groupKey); } /** - * dump beta. - * - * @param dataId dataId. - * @param group group. - * @param tenant tenant. - * @param lastModified lastModified. - * @param handleIp handleIp. - */ - private void dumpBeta(String dataId, String group, String tenant, long lastModified, String handleIp) { - String groupKey = GroupKey2.getKey(dataId, group, tenant); - String taskKey = groupKey + "+beta"; - dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, true, false, false, null, lastModified, handleIp)); - DUMP_LOG.info("[dump] add beta task. groupKey={}", groupKey); - - } - - /** - * dump batch. - * - * @param dataId dataId. - * @param group group. - * @param tenant tenant. - * @param lastModified lastModified. - * @param handleIp handleIp. - */ - private void dumpBatch(String dataId, String group, String tenant, long lastModified, String handleIp) { - String groupKey = GroupKey2.getKey(dataId, group, tenant); - String taskKey = groupKey + "+batch"; - dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, false, true, false, null, lastModified, handleIp)); - DUMP_LOG.info("[dump] add batch task. groupKey={}", dataId + "+" + group); - } - - /** - * dump tag. + * dump gray. * * @param dataId dataId. * @param group group. * @param tenant tenant. - * @param tag tag. + * @param grayName grayName. * @param lastModified lastModified. * @param handleIp handleIp. */ - private void dumpTag(String dataId, String group, String tenant, String tag, long lastModified, String handleIp) { + private void dumpGray(String dataId, String group, String tenant, String grayName, long lastModified, + String handleIp) { String groupKey = GroupKey2.getKey(dataId, group, tenant); - String taskKey = groupKey + "+tag+" + tag; - dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, false, false, true, tag, lastModified, handleIp)); - DUMP_LOG.info("[dump] add tag task. groupKey={},tag={}", groupKey, tag); + String taskKey = groupKey + "+gray+" + grayName; + dumpTaskMgr.addTask(taskKey, new DumpTask(groupKey, grayName, lastModified, handleIp)); + DUMP_LOG.info("[dump] add gray task. groupKey={},grayName={}", groupKey, grayName); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/EmbeddedDumpService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/EmbeddedDumpService.java index 4895cbdf13c..5f9b814e75b 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/EmbeddedDumpService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/EmbeddedDumpService.java @@ -20,20 +20,17 @@ import com.alibaba.nacos.common.utils.Observer; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.common.utils.ThreadUtils; -import com.alibaba.nacos.config.server.service.merge.MergeDatumService; -import com.alibaba.nacos.persistence.configuration.condition.ConditionOnEmbeddedStorage; -import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.consistency.ProtocolMetaData; import com.alibaba.nacos.consistency.cp.CPProtocol; import com.alibaba.nacos.consistency.cp.MetadataKey; import com.alibaba.nacos.core.cluster.ServerMemberManager; import com.alibaba.nacos.core.distributed.ProtocolManager; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; import com.alibaba.nacos.core.utils.GlobalExecutor; +import com.alibaba.nacos.persistence.configuration.condition.ConditionOnEmbeddedStorage; import com.alibaba.nacos.persistence.constants.PersistenceConstant; import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder; import com.alibaba.nacos.sys.env.EnvUtil; @@ -76,13 +73,9 @@ public class EmbeddedDumpService extends DumpService { public EmbeddedDumpService(ConfigInfoPersistService configInfoPersistService, NamespacePersistService namespacePersistService, HistoryConfigInfoPersistService historyConfigInfoPersistService, - ConfigInfoAggrPersistService configInfoAggrPersistService, - ConfigInfoBetaPersistService configInfoBetaPersistService, - ConfigInfoTagPersistService configInfoTagPersistService, MergeDatumService mergeDatumService, + ConfigInfoGrayPersistService configInfoGrayPersistService, ServerMemberManager memberManager, ProtocolManager protocolManager) { - super(configInfoPersistService, namespacePersistService, historyConfigInfoPersistService, - configInfoAggrPersistService, configInfoBetaPersistService, configInfoTagPersistService, - mergeDatumService, memberManager); + super(configInfoPersistService, namespacePersistService, historyConfigInfoPersistService, configInfoGrayPersistService, memberManager); this.protocolManager = protocolManager; } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/ExternalDumpService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/ExternalDumpService.java index 3b2b20fba1d..3a6b3e94e1c 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/ExternalDumpService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/ExternalDumpService.java @@ -16,15 +16,12 @@ package com.alibaba.nacos.config.server.service.dump; -import com.alibaba.nacos.config.server.service.merge.MergeDatumService; -import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; -import com.alibaba.nacos.persistence.configuration.condition.ConditionOnExternalStorage; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; +import com.alibaba.nacos.persistence.configuration.condition.ConditionOnExternalStorage; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.DependsOn; import org.springframework.stereotype.Component; @@ -38,7 +35,7 @@ */ @Conditional(ConditionOnExternalStorage.class) @Component -@DependsOn({"rpcConfigChangeNotifier"}) +@DependsOn({"rpcConfigChangeNotifier", "configGrayModelMigrateService"}) public class ExternalDumpService extends DumpService { /** @@ -50,13 +47,9 @@ public class ExternalDumpService extends DumpService { public ExternalDumpService(ConfigInfoPersistService configInfoPersistService, NamespacePersistService namespacePersistService, HistoryConfigInfoPersistService historyConfigInfoPersistService, - ConfigInfoAggrPersistService configInfoAggrPersistService, - ConfigInfoBetaPersistService configInfoBetaPersistService, - ConfigInfoTagPersistService configInfoTagPersistService, MergeDatumService mergeDatumService, + ConfigInfoGrayPersistService configInfoGrayPersistService, ServerMemberManager memberManager) { - super(configInfoPersistService, namespacePersistService, historyConfigInfoPersistService, - configInfoAggrPersistService, configInfoBetaPersistService, configInfoTagPersistService, - mergeDatumService, memberManager); + super(configInfoPersistService, namespacePersistService, historyConfigInfoPersistService, configInfoGrayPersistService, memberManager); } @PostConstruct diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigDiskService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigDiskService.java index 4983704872e..97169574013 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigDiskService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigDiskService.java @@ -35,57 +35,49 @@ public interface ConfigDiskService { * @throws IOException io exception. */ void saveToDisk(String dataId, String group, String tenant, String content) throws IOException; - - /** - * Save beta information to disk. - * - * @param dataId dataId. - * @param group group. - * @param tenant tenant. - * @param content content. - * @throws IOException io exception. - */ - void saveBetaToDisk(String dataId, String group, String tenant, String content) throws IOException; /** - * Save tag information to disk. + * Save gray information to disk. * * @param dataId dataId. * @param group group. * @param tenant tenant. - * @param tag tag. + * @param grayName grayName. * @param content content. * @throws IOException io exception. */ - void saveTagToDisk(String dataId, String group, String tenant, String tag, String content) throws IOException; + void saveGrayToDisk(String dataId, String group, String tenant, String grayName, String content) throws IOException; /** - * Deletes configuration files on disk. + * Deletes gray configuration files on disk. * * @param dataId dataId. * @param group group. * @param tenant tenant. + * @param grayName grayName. */ - void removeConfigInfo(String dataId, String group, String tenant); + void removeConfigInfo4Gray(String dataId, String group, String tenant, String grayName); /** - * Deletes beta configuration files on disk. + * Returns the content of the gray cache file in server. * * @param dataId dataId. * @param group group. * @param tenant tenant. + * @param grayName grayName. + * @return gray content, null if not exist. + * @throws IOException io exception. */ - void removeConfigInfo4Beta(String dataId, String group, String tenant); + String getGrayContent(String dataId, String group, String tenant, String grayName) throws IOException; /** - * Deletes tag configuration files on disk. + * Deletes configuration files on disk. * * @param dataId dataId. * @param group group. * @param tenant tenant. - * @param tag tag. */ - void removeConfigInfo4Tag(String dataId, String group, String tenant, String tag); + void removeConfigInfo(String dataId, String group, String tenant); /** * Returns the content of the cache file in server. @@ -98,42 +90,14 @@ public interface ConfigDiskService { */ String getContent(String dataId, String group, String tenant) throws IOException; - /** - * Returns the beta content of cache file in server. - * - * @param dataId dataId. - * @param group group. - * @param tenant tenant. - * @return content, null if not exist. - * @throws IOException io exception. - */ - String getBetaContent(String dataId, String group, String tenant) throws IOException; - - /** - * Returns the path of the tag cache file in server. - * - * @param dataId dataId. - * @param group group. - * @param tenant tenant. - * @param tag tag. - * @return tag content, null if not exist. - * @throws IOException io exception. - */ - String getTagContent(String dataId, String group, String tenant, String tag) throws IOException; - /** * Clear all config file. */ void clearAll(); /** - * Clear all beta config file. - */ - void clearAllBeta(); - - /** - * Clear all tag config file. + * Clear all gray config file. */ - void clearAllTag(); + void clearAllGray(); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskService.java index 09fa0052a7c..45dc2982e2c 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskService.java @@ -45,17 +45,9 @@ public class ConfigRawDiskService implements ConfigDiskService { private static final String TENANT_BASE_DIR = File.separator + "data" + File.separator + "tenant-config-data"; - private static final String BETA_DIR = File.separator + "data" + File.separator + "beta-data"; + private static final String GRAY_DIR = File.separator + "data" + File.separator + "gray-data"; - private static final String TENANT_BETA_DIR = File.separator + "data" + File.separator + "tenant-beta-data"; - - private static final String TAG_DIR = File.separator + "data" + File.separator + "tag-data"; - - private static final String TENANT_TAG_DIR = File.separator + "data" + File.separator + "tenant-tag-data"; - - private static final String BATCH_DIR = File.separator + "data" + File.separator + "batch-data"; - - private static final String TENANT_BATCH_DIR = File.separator + "data" + File.separator + "tenant-batch-data"; + private static final String TENANT_GRAY_DIR = File.separator + "data" + File.separator + "tenant-gray-data"; /** * Save configuration information to disk. @@ -91,36 +83,11 @@ static File targetFile(String dataId, String group, String tenant) { } /** - * Returns the path of cache file in server. - */ - private static File targetBetaFile(String dataId, String group, String tenant) { - try { - ParamUtils.checkParam(dataId, group, tenant); - } catch (Exception e) { - throw new NacosRuntimeException(NacosException.CLIENT_INVALID_PARAM, "parameter is invalid."); - } - // fix https://github.com/alibaba/nacos/issues/10067 - dataId = PathEncoderManager.getInstance().encode(dataId); - group = PathEncoderManager.getInstance().encode(group); - tenant = PathEncoderManager.getInstance().encode(tenant); - File file = null; - if (StringUtils.isBlank(tenant)) { - file = new File(EnvUtil.getNacosHome(), BETA_DIR); - } else { - file = new File(EnvUtil.getNacosHome(), TENANT_BETA_DIR); - file = new File(file, tenant); - } - file = new File(file, group); - file = new File(file, dataId); - return file; - } - - /** - * Returns the path of the tag cache file in server. + * Returns the path of the gray cache file in server. */ - private static File targetTagFile(String dataId, String group, String tenant, String tag) { + private static File targetGrayFile(String dataId, String group, String tenant, String grayName) { try { - ParamUtils.checkParam(tag); + ParamUtils.checkParam(grayName); ParamUtils.checkParam(dataId, group, tenant); } catch (Exception e) { throw new NacosRuntimeException(NacosException.CLIENT_INVALID_PARAM, "parameter is invalid."); @@ -129,34 +96,34 @@ private static File targetTagFile(String dataId, String group, String tenant, St dataId = PathEncoderManager.getInstance().encode(dataId); group = PathEncoderManager.getInstance().encode(group); tenant = PathEncoderManager.getInstance().encode(tenant); + File file = null; if (StringUtils.isBlank(tenant)) { - file = new File(EnvUtil.getNacosHome(), TAG_DIR); + file = new File(EnvUtil.getNacosHome(), GRAY_DIR); } else { - file = new File(EnvUtil.getNacosHome(), TENANT_TAG_DIR); + file = new File(EnvUtil.getNacosHome(), TENANT_GRAY_DIR); file = new File(file, tenant); } file = new File(file, group); file = new File(file, dataId); - file = new File(file, tag); + file = new File(file, grayName); return file; } /** - * Save beta information to disk. + * Returns the path of the gray content cache file in server. */ - public void saveBetaToDisk(String dataId, String group, String tenant, String content) throws IOException { - File targetFile = targetBetaFile(dataId, group, tenant); - FileUtils.writeStringToFile(targetFile, content, ENCODE_UTF8); + private static File targetGrayContentFile(String dataId, String group, String tenant, String grayName) { + return targetGrayFile(dataId, group, tenant, grayName); } /** - * Save tag information to disk. + * Save gray information to disk. */ - public void saveTagToDisk(String dataId, String group, String tenant, String tag, String content) + public void saveGrayToDisk(String dataId, String group, String tenant, String grayName, String content) throws IOException { - File targetFile = targetTagFile(dataId, group, tenant, tag); - FileUtils.writeStringToFile(targetFile, content, ENCODE_UTF8); + File targetGrayContentFile = targetGrayContentFile(dataId, group, tenant, grayName); + FileUtils.writeStringToFile(targetGrayContentFile, content, ENCODE_UTF8); } /** @@ -167,17 +134,10 @@ public void removeConfigInfo(String dataId, String group, String tenant) { } /** - * Deletes beta configuration files on disk. - */ - public void removeConfigInfo4Beta(String dataId, String group, String tenant) { - FileUtils.deleteQuietly(targetBetaFile(dataId, group, tenant)); - } - - /** - * Deletes tag configuration files on disk. + * Deletes gray configuration files on disk. */ - public void removeConfigInfo4Tag(String dataId, String group, String tenant, String tag) { - FileUtils.deleteQuietly(targetTagFile(dataId, group, tenant, tag)); + public void removeConfigInfo4Gray(String dataId, String group, String tenant, String grayName) { + FileUtils.deleteQuietly(targetGrayContentFile(dataId, group, tenant, grayName)); } private static String file2String(File file) throws IOException { @@ -188,19 +148,10 @@ private static String file2String(File file) throws IOException { } /** - * Returns the path of cache file in server. + * Returns the content of the gray cache file in server. */ - public String getBetaContent(String dataId, String group, String tenant) throws IOException { - File file = targetBetaFile(dataId, group, tenant); - return file2String(file); - } - - /** - * Returns the path of the tag cache file in server. - */ - public String getTagContent(String dataId, String group, String tenant, String tag) throws IOException { - File file = targetTagFile(dataId, group, tenant, tag); - return file2String(file); + public String getGrayContent(String dataId, String group, String tenant, String grayName) throws IOException { + return file2String(targetGrayContentFile(dataId, group, tenant, grayName)); } public String getContent(String dataId, String group, String tenant) throws IOException { @@ -239,39 +190,21 @@ public void clearAll() { } /** - * Clear all beta config file. - */ - public void clearAllBeta() { - File file = new File(EnvUtil.getNacosHome(), BETA_DIR); - if (!file.exists() || FileUtils.deleteQuietly(file)) { - LogUtil.DEFAULT_LOG.info("clear all config-info-beta success."); - } else { - LogUtil.DEFAULT_LOG.warn("clear all config-info-beta failed."); - } - File fileTenant = new File(EnvUtil.getNacosHome(), TENANT_BETA_DIR); - if (!fileTenant.exists() || FileUtils.deleteQuietly(fileTenant)) { - LogUtil.DEFAULT_LOG.info("clear all config-info-beta-tenant success."); - } else { - LogUtil.DEFAULT_LOG.warn("clear all config-info-beta-tenant failed."); - } - } - - /** - * Clear all tag config file. + * Clear all gray config file. */ - public void clearAllTag() { - File file = new File(EnvUtil.getNacosHome(), TAG_DIR); + public void clearAllGray() { + File file = new File(EnvUtil.getNacosHome(), GRAY_DIR); if (!file.exists() || FileUtils.deleteQuietly(file)) { - LogUtil.DEFAULT_LOG.info("clear all config-info-tag success."); + LogUtil.DEFAULT_LOG.info("clear all config-info-gray success."); } else { - LogUtil.DEFAULT_LOG.warn("clear all config-info-tag failed."); + LogUtil.DEFAULT_LOG.warn("clear all config-info-gray failed."); } - File fileTenant = new File(EnvUtil.getNacosHome(), TENANT_TAG_DIR); + File fileTenant = new File(EnvUtil.getNacosHome(), TENANT_GRAY_DIR); if (!fileTenant.exists() || FileUtils.deleteQuietly(fileTenant)) { - LogUtil.DEFAULT_LOG.info("clear all config-info-tag-tenant success."); + LogUtil.DEFAULT_LOG.info("clear all config-info-gray-tenant success."); } else { - LogUtil.DEFAULT_LOG.warn("clear all config-info-tag-tenant failed."); + LogUtil.DEFAULT_LOG.warn("clear all config-info-gray-tenant failed."); } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRocksDbDiskService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRocksDbDiskService.java index 0eb85f98be6..0da0cc40284 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRocksDbDiskService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRocksDbDiskService.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.config.server.service.dump.disk; +import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.utils.LogUtil; import com.alibaba.nacos.sys.env.EnvUtil; @@ -32,6 +33,7 @@ import java.util.Map; import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; +import static com.alibaba.nacos.config.server.constant.Constants.NULL; /** * config rocks db disk service. @@ -45,11 +47,7 @@ public class ConfigRocksDbDiskService implements ConfigDiskService { private static final String BASE_DIR = ROCKSDB_DATA + "config-data"; - private static final String BETA_DIR = ROCKSDB_DATA + "beta-data"; - - private static final String TAG_DIR = ROCKSDB_DATA + "tag-data"; - - private static final String BATCH_DIR = ROCKSDB_DATA + "batch-data"; + private static final String GRAY_DIR = ROCKSDB_DATA + "gray-data"; private static final long DEFAULT_WRITE_BUFFER_MB = 32; @@ -79,13 +77,19 @@ private void deleteDirIfExist(String dir) { public ConfigRocksDbDiskService() { createDirIfNotExist(BASE_DIR); - createDirIfNotExist(BETA_DIR); - createDirIfNotExist(TAG_DIR); - createDirIfNotExist(BATCH_DIR); + createDirIfNotExist(GRAY_DIR); + } private byte[] getKeyByte(String dataId, String group, String tenant, String tag) throws IOException { String[] keys = new String[] {dataId, group, tenant, tag}; + return getKeyByte(keys); + } + + private byte[] getKeyByte(String... keys) throws IOException { + if (keys == null || keys.length == 0) { + return NULL.getBytes(ENCODE_UTF8); + } StringBuilder stringBuilder = new StringBuilder(); for (String key : keys) { if (StringUtils.isBlank(key)) { @@ -134,26 +138,30 @@ public void saveToDiskInner(String type, String dataId, String group, String ten } /** - * Save configuration information to disk. + * save config to disk. */ - public void saveToDisk(String dataId, String group, String tenant, String content) throws IOException { - saveToDiskInner(BASE_DIR, dataId, group, tenant, content); + public void saveGrayToDiskInner(String type, String dataId, String group, String tenant, String grayName, + String content) throws IOException { + try { + initAndGetDB(type).put(getKeyByte(dataId, group, tenant, grayName), content.getBytes(ENCODE_UTF8)); + } catch (RocksDBException e) { + throw new IOException(e); + } } /** - * Save beta information to disk. + * Save configuration information to disk. */ - public void saveBetaToDisk(String dataId, String group, String tenant, String content) throws IOException { - saveToDiskInner(BETA_DIR, dataId, group, tenant, content); - + public void saveToDisk(String dataId, String group, String tenant, String content) throws IOException { + saveToDiskInner(BASE_DIR, dataId, group, tenant, content); } /** * Save tag information to disk. */ - public void saveTagToDisk(String dataId, String group, String tenant, String tag, String content) + public void saveGrayToDisk(String dataId, String group, String tenant, String grayName, String content) throws IOException { - saveToDiskInner(TAG_DIR, dataId, group, tenant, tag, content); + saveGrayToDiskInner(GRAY_DIR, dataId, group, tenant, grayName, content); } @@ -165,17 +173,10 @@ public void removeConfigInfo(String dataId, String group, String tenant) { } /** - * Deletes beta configuration files on disk. + * Deletes gray configuration files on disk. */ - public void removeConfigInfo4Beta(String dataId, String group, String tenant) { - removeContentInner(BETA_DIR, dataId, group, tenant, null); - } - - /** - * Deletes tag configuration files on disk. - */ - public void removeConfigInfo4Tag(String dataId, String group, String tenant, String tag) { - removeContentInner(TAG_DIR, dataId, group, tenant, tag); + public void removeConfigInfo4Gray(String dataId, String group, String tenant, String grayName) { + removeGrayInner(GRAY_DIR, dataId, group, tenant, grayName); } @@ -186,7 +187,7 @@ private String byte2String(byte[] bytes) throws IOException { return new String(bytes, ENCODE_UTF8); } - RocksDB initAndGetDB(String dir) throws RocksDBException { + RocksDB initAndGetDB(String dir) throws IOException, RocksDBException { if (rocksDbMap.containsKey(dir)) { return rocksDbMap.get(dir); } else { @@ -220,11 +221,11 @@ private String getContentInner(String type, String dataId, String group, String } } - private String getTagContentInner(String type, String dataId, String group, String tenant, String tag) + private String getGrayInner(String type, String dataId, String group, String tenant, String grayName) throws IOException { byte[] bytes = null; try { - bytes = initAndGetDB(type).get(getKeyByte(dataId, group, tenant, tag)); + bytes = initAndGetDB(type).get(getKeyByte(dataId, group, tenant, grayName)); return byte2String(bytes); } catch (RocksDBException e) { throw new IOException(e); @@ -240,24 +241,30 @@ private void removeContentInner(String type, String dataId, String group, String } } - /** - * Returns the path of cache file in server. - */ - public String getBetaContent(String dataId, String group, String tenant) throws IOException { - return getContentInner(BETA_DIR, dataId, group, tenant); + private void removeGrayInner(String type, String dataId, String group, String tenant, String grayName) { + try { + initAndGetDB(type).delete(getKeyByte(dataId, group, tenant, grayName)); + } catch (Exception e) { + LogUtil.DEFAULT_LOG.warn("Remove dir=[{}] config fail,dataId={},group={},tenant={},error={}", type, dataId, + group, tenant, e.getCause()); + } } /** - * Returns the path of the tag cache file in server. + * Returns the path of the gray content cache file in server. */ - public String getTagContent(String dataId, String group, String tenant, String tag) throws IOException { - return getTagContentInner(TAG_DIR, dataId, group, tenant, tag); + public String getGrayContent(String dataId, String group, String tenant, String grayName) throws IOException { + return getGrayInner(GRAY_DIR, dataId, group, tenant, grayName); } public String getContent(String dataId, String group, String tenant) throws IOException { return getContentInner(BASE_DIR, dataId, group, tenant); } + public String getLocalConfigMd5(String dataId, String group, String tenant, String encode) throws IOException { + return MD5Utils.md5Hex(getContentInner(BASE_DIR, dataId, group, tenant), encode); + } + Options createOptions(String dir) { DBOptions dbOptions = new DBOptions(); dbOptions.setMaxBackgroundJobs(Runtime.getRuntime().availableProcessors()); @@ -326,35 +333,19 @@ public void clearAll() { } /** - * Clear all beta config file. - */ - public void clearAllBeta() { - try { - if (rocksDbMap.containsKey(BETA_DIR)) { - rocksDbMap.get(BETA_DIR).close(); - RocksDB.destroyDB(EnvUtil.getNacosHome() + BETA_DIR, new Options()); - } - deleteDirIfExist(BETA_DIR); - LogUtil.DEFAULT_LOG.info("clear all config-info-beta success."); - } catch (RocksDBException e) { - LogUtil.DEFAULT_LOG.warn("clear all config-info-beta failed.", e); - } - } - - /** - * Clear all tag config file. + * Clear all gray config file. */ - public void clearAllTag() { + public void clearAllGray() { try { - if (rocksDbMap.containsKey(TAG_DIR)) { - rocksDbMap.get(TAG_DIR).close(); - RocksDB.destroyDB(EnvUtil.getNacosHome() + TAG_DIR, new Options()); + if (rocksDbMap.containsKey(GRAY_DIR)) { + rocksDbMap.get(GRAY_DIR).close(); + RocksDB.destroyDB(EnvUtil.getNacosHome() + GRAY_DIR, new Options()); } - deleteDirIfExist(TAG_DIR); - LogUtil.DEFAULT_LOG.info("clear all config-info-tag success."); + deleteDirIfExist(GRAY_DIR); + LogUtil.DEFAULT_LOG.info("clear all config-info-gray success."); } catch (RocksDBException e) { - LogUtil.DEFAULT_LOG.warn("clear all config-info-tag failed.", e); + LogUtil.DEFAULT_LOG.warn("clear all config-info-gray failed.", e); } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllBetaProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllBetaProcessor.java deleted file mode 100644 index 47b7aab0e5b..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllBetaProcessor.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * 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.config.server.service.dump.processor; - -import com.alibaba.nacos.common.task.NacosTask; -import com.alibaba.nacos.common.task.NacosTaskProcessor; -import com.alibaba.nacos.config.server.model.ConfigInfoBetaWrapper; -import com.alibaba.nacos.config.server.service.ConfigCacheService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; -import com.alibaba.nacos.config.server.utils.GroupKey2; -import com.alibaba.nacos.config.server.utils.LogUtil; -import com.alibaba.nacos.persistence.model.Page; - -import static com.alibaba.nacos.config.server.utils.LogUtil.DEFAULT_LOG; - -/** - * Dump all beta processor. - * - * @author Nacos - * @author Wei.Wang - * @date 2020/7/5 12:18 PM - */ -public class DumpAllBetaProcessor implements NacosTaskProcessor { - - public DumpAllBetaProcessor(ConfigInfoBetaPersistService configInfoBetaPersistService) { - this.configInfoBetaPersistService = configInfoBetaPersistService; - } - - @Override - public boolean process(NacosTask task) { - int rowCount = configInfoBetaPersistService.configInfoBetaCount(); - int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); - - int actualRowCount = 0; - for (int pageNo = 1; pageNo <= pageCount; pageNo++) { - Page page = configInfoBetaPersistService.findAllConfigInfoBetaForDumpAll(pageNo, - PAGE_SIZE); - if (page != null) { - for (ConfigInfoBetaWrapper cf : page.getPageItems()) { - boolean result = ConfigCacheService.dumpBeta(cf.getDataId(), cf.getGroup(), cf.getTenant(), - cf.getContent(), cf.getLastModified(), cf.getBetaIps(), cf.getEncryptedDataKey()); - LogUtil.DUMP_LOG.info("[dump-all-beta-ok] result={}, {}, {}, length={}, md5={}", result, - GroupKey2.getKey(cf.getDataId(), cf.getGroup()), cf.getLastModified(), - cf.getContent().length(), cf.getMd5()); - } - - actualRowCount += page.getPageItems().size(); - DEFAULT_LOG.info("[all-dump-beta] {} / {}", actualRowCount, rowCount); - } - } - return true; - } - - static final int PAGE_SIZE = 1000; - - final ConfigInfoBetaPersistService configInfoBetaPersistService; -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllTagProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllGrayProcessor.java similarity index 61% rename from config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllTagProcessor.java rename to config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllGrayProcessor.java index 2e4e4e7dfc0..43dd5cf18af 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllTagProcessor.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllGrayProcessor.java @@ -18,53 +18,54 @@ import com.alibaba.nacos.common.task.NacosTask; import com.alibaba.nacos.common.task.NacosTaskProcessor; -import com.alibaba.nacos.config.server.model.ConfigInfoTagWrapper; -import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; import com.alibaba.nacos.config.server.service.ConfigCacheService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.persistence.model.Page; import static com.alibaba.nacos.config.server.utils.LogUtil.DEFAULT_LOG; +import static com.alibaba.nacos.config.server.utils.PropertyUtil.getAllDumpPageSize; /** - * Dump all tag processor. + * Dump all gray processor. * * @author Nacos - * @date 2020/7/5 12:18 PM + * @datete 2024/02/20 */ -public class DumpAllTagProcessor implements NacosTaskProcessor { +public class DumpAllGrayProcessor implements NacosTaskProcessor { - public DumpAllTagProcessor(ConfigInfoTagPersistService configInfoTagPersistService) { - this.configInfoTagPersistService = configInfoTagPersistService; + public DumpAllGrayProcessor(ConfigInfoGrayPersistService configInfoGrayPersistService) { + this.configInfoGrayPersistService = configInfoGrayPersistService; } @Override public boolean process(NacosTask task) { - int rowCount = configInfoTagPersistService.configInfoTagCount(); + int rowCount = configInfoGrayPersistService.configInfoGrayCount(); int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); int actualRowCount = 0; for (int pageNo = 1; pageNo <= pageCount; pageNo++) { - Page page = configInfoTagPersistService.findAllConfigInfoTagForDumpAll(pageNo, PAGE_SIZE); + Page page = configInfoGrayPersistService.findAllConfigInfoGrayForDumpAll(pageNo, PAGE_SIZE); if (page != null) { - for (ConfigInfoTagWrapper cf : page.getPageItems()) { + for (ConfigInfoGrayWrapper cf : page.getPageItems()) { boolean result = ConfigCacheService - .dumpTag(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getTag(), cf.getContent(), + .dumpGray(cf.getDataId(), cf.getGroup(), cf.getTenant(), cf.getGrayName(), cf.getGrayRule(), cf.getContent(), cf.getLastModified(), cf.getEncryptedDataKey()); - LogUtil.DUMP_LOG.info("[dump-all-Tag-ok] result={}, {}, {}, length={}, md5={}", result, + LogUtil.DUMP_LOG.info("[dump-all-gray-ok] result={}, {}, {}, length={}, md5={}, grayName={}", result, GroupKey2.getKey(cf.getDataId(), cf.getGroup()), cf.getLastModified(), - cf.getContent().length(), cf.getMd5()); + cf.getContent().length(), cf.getMd5(), cf.getGrayName()); } actualRowCount += page.getPageItems().size(); - DEFAULT_LOG.info("[all-dump-tag] {} / {}", actualRowCount, rowCount); + DEFAULT_LOG.info("[all-dump-gray] {} / {}", actualRowCount, rowCount); } } return true; } - static final int PAGE_SIZE = 1000; + static final int PAGE_SIZE = getAllDumpPageSize(); - final ConfigInfoTagPersistService configInfoTagPersistService; + final ConfigInfoGrayPersistService configInfoGrayPersistService; } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessor.java index 57c86dfe540..2c07dd8a2d5 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessor.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessor.java @@ -20,7 +20,6 @@ import com.alibaba.nacos.common.task.NacosTaskProcessor; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; -import com.alibaba.nacos.config.server.service.AggrWhitelist; import com.alibaba.nacos.config.server.service.ClientIpWhiteList; import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.config.server.service.SwitchService; @@ -54,105 +53,111 @@ public DumpAllProcessor(ConfigInfoPersistService configInfoPersistService) { @Override public boolean process(NacosTask task) { if (!(task instanceof DumpAllTask)) { - DEFAULT_LOG.error("[all-dump-error] ,invalid task type,DumpAllProcessor should process DumpAllTask type."); + DEFAULT_LOG.error( + "[all-dump-error] ,invalid task type {},DumpAllProcessor should process DumpAllTask type.", + task.getClass().getSimpleName()); return false; } DumpAllTask dumpAllTask = (DumpAllTask) task; + long currentMaxId = configInfoPersistService.findConfigMaxId(); long lastMaxId = 0; - ThreadPoolExecutor executorService = createExecutorService(dumpAllTask); + ThreadPoolExecutor executorService = null; + if (dumpAllTask.isStartUp()) { + executorService = new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), + Runtime.getRuntime().availableProcessors(), 60L, TimeUnit.SECONDS, + new LinkedBlockingQueue<>(PropertyUtil.getAllDumpPageSize() * 2), + r -> new Thread(r, "dump all executor"), new ThreadPoolExecutor.CallerRunsPolicy()); + } else { + executorService = new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), + r -> new Thread(r, "dump all executor"), new ThreadPoolExecutor.CallerRunsPolicy()); + } DEFAULT_LOG.info("start dump all config-info..."); + while (lastMaxId < currentMaxId) { + long start = System.currentTimeMillis(); + Page page = configInfoPersistService.findAllConfigInfoFragment(lastMaxId, PropertyUtil.getAllDumpPageSize(), dumpAllTask.isStartUp()); long dbTimeStamp = System.currentTimeMillis(); if (page == null || page.getPageItems() == null || page.getPageItems().isEmpty()) { break; } + for (ConfigInfoWrapper cf : page.getPageItems()) { lastMaxId = Math.max(cf.getId(), lastMaxId); - processConfigInfo(cf, dumpAllTask, executorService); + //if not start up, page query will not return content, check md5 and lastModified first ,if changed ,get single content info to dump. + if (!dumpAllTask.isStartUp()) { + final String groupKey = GroupKey2.getKey(cf.getDataId(), cf.getGroup(), cf.getTenant()); + boolean newLastModified = cf.getLastModified() > ConfigCacheService.getLastModifiedTs(groupKey); + //check md5 & update local disk cache. + String localContentMd5 = ConfigCacheService.getContentMd5(groupKey); + boolean md5Update = !localContentMd5.equals(cf.getMd5()); + if (newLastModified || md5Update) { + LogUtil.DUMP_LOG.info("[dump-all] find change config {}, {}, md5={}", groupKey, + cf.getLastModified(), cf.getMd5()); + cf = configInfoPersistService.findConfigInfo(cf.getDataId(), cf.getGroup(), cf.getTenant()); + } else { + continue; + } + } + + if (cf == null) { + continue; + } + + if (cf.getDataId().equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) { + ClientIpWhiteList.load(cf.getContent()); + } + + if (cf.getDataId().equals(SwitchService.SWITCH_META_DATA_ID)) { + SwitchService.load(cf.getContent()); + } + + final String content = cf.getContent(); + final String dataId = cf.getDataId(); + final String group = cf.getGroup(); + final String tenant = cf.getTenant(); + final long lastModified = cf.getLastModified(); + final String type = cf.getType(); + final String encryptedDataKey = cf.getEncryptedDataKey(); + + executorService.execute(() -> { + final String md5Utf8 = MD5Utils.md5Hex(content, ENCODE_UTF8); + boolean result = ConfigCacheService.dumpWithMd5(dataId, group, tenant, content, md5Utf8, + lastModified, type, encryptedDataKey); + if (result) { + LogUtil.DUMP_LOG.info("[dump-all-ok] {}, {}, length={},md5UTF8={}", + GroupKey2.getKey(dataId, group), lastModified, content.length(), md5Utf8); + } else { + LogUtil.DUMP_LOG.info("[dump-all-error] {}", GroupKey2.getKey(dataId, group)); + } + + }); + } + long diskStamp = System.currentTimeMillis(); DEFAULT_LOG.info("[all-dump] submit all task for {} / {}, dbTime={},diskTime={}", lastMaxId, currentMaxId, (dbTimeStamp - start), (diskStamp - dbTimeStamp)); } - // wait all tasks to be finished and then shutdown executor - waitForTasksToFinish(executorService); - DEFAULT_LOG.info("success to dump all config-info。"); - return true; - } - - private ThreadPoolExecutor createExecutorService(DumpAllTask dumpAllTask) { - if (dumpAllTask.isStartUp()) { - return new ThreadPoolExecutor(Runtime.getRuntime().availableProcessors(), - Runtime.getRuntime().availableProcessors(), 60L, TimeUnit.SECONDS, - new LinkedBlockingQueue<>(PropertyUtil.getAllDumpPageSize() * 2), - r -> new Thread(r, "dump all executor"), new ThreadPoolExecutor.CallerRunsPolicy()); - } else { - return new ThreadPoolExecutor(1, 1, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(), - r -> new Thread(r, "dump all executor"), new ThreadPoolExecutor.CallerRunsPolicy()); - } - } - - private void processConfigInfo(ConfigInfoWrapper cf, DumpAllTask dumpAllTask, ThreadPoolExecutor executorService) { - if (!dumpAllTask.isStartUp()) { - final String groupKey = GroupKey2.getKey(cf.getDataId(), cf.getGroup(), cf.getTenant()); - boolean newLastModified = cf.getLastModified() > ConfigCacheService.getLastModifiedTs(groupKey); - String localContentMd5 = ConfigCacheService.getContentMd5(groupKey); - boolean md5Update = !localContentMd5.equals(cf.getMd5()); - if (newLastModified || md5Update) { - LogUtil.DUMP_LOG.info("[dump-all] find change config {}, {}, md5={}", groupKey, cf.getLastModified(), cf.getMd5()); - cf = configInfoPersistService.findConfigInfo(cf.getDataId(), cf.getGroup(), cf.getTenant()); - } else { - return; - } - } - if (cf == null) { - return; - } - if (cf.getDataId().equals(AggrWhitelist.AGGRIDS_METADATA)) { - AggrWhitelist.load(cf.getContent()); - } - if (cf.getDataId().equals(ClientIpWhiteList.CLIENT_IP_WHITELIST_METADATA)) { - ClientIpWhiteList.load(cf.getContent()); - } - if (cf.getDataId().equals(SwitchService.SWITCH_META_DATA_ID)) { - SwitchService.load(cf.getContent()); - } - final String content = cf.getContent(); - final String dataId = cf.getDataId(); - final String group = cf.getGroup(); - final String tenant = cf.getTenant(); - final long lastModified = cf.getLastModified(); - final String type = cf.getType(); - final String encryptedDataKey = cf.getEncryptedDataKey(); - executorService.execute(() -> { - final String md5Utf8 = MD5Utils.md5Hex(content, ENCODE_UTF8); - boolean result = ConfigCacheService.dumpWithMd5(dataId, group, tenant, content, md5Utf8, lastModified, type, encryptedDataKey); - if (result) { - LogUtil.DUMP_LOG.info("[dump-all-ok] {}, {}, length={},md5UTF8={}", - GroupKey2.getKey(dataId, group), lastModified, content.length(), md5Utf8); - } else { - LogUtil.DUMP_LOG.info("[dump-all-error] {}", GroupKey2.getKey(dataId, group)); - } - }); - } - - private void waitForTasksToFinish(ThreadPoolExecutor executorService) { + //wait all task are finished and then shutdown executor. try { - int unfinishedTaskCount; + int unfinishedTaskCount = 0; while ((unfinishedTaskCount = executorService.getQueue().size() + executorService.getActiveCount()) > 0) { DEFAULT_LOG.info("[all-dump] wait {} dump tasks to be finished", unfinishedTaskCount); Thread.sleep(1000L); } executorService.shutdown(); + } catch (Exception e) { - DEFAULT_LOG.error("[all-dump] wait dump tasks to be finished error", e); + DEFAULT_LOG.error("[all-dump] wait dump tasks to be finished error", e); } + DEFAULT_LOG.info("success to dump all config-info。"); + return true; } final ConfigInfoPersistService configInfoPersistService; diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpProcessor.java index 8c5b2267feb..600ec730fd6 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpProcessor.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/processor/DumpProcessor.java @@ -19,15 +19,13 @@ import com.alibaba.nacos.common.task.NacosTask; import com.alibaba.nacos.common.task.NacosTaskProcessor; import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.model.ConfigInfoBetaWrapper; -import com.alibaba.nacos.config.server.model.ConfigInfoTagWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; import com.alibaba.nacos.config.server.model.event.ConfigDumpEvent; import com.alibaba.nacos.config.server.service.dump.DumpConfigHandler; import com.alibaba.nacos.config.server.service.dump.task.DumpTask; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.LogUtil; @@ -43,16 +41,12 @@ public class DumpProcessor implements NacosTaskProcessor { final ConfigInfoPersistService configInfoPersistService; - final ConfigInfoBetaPersistService configInfoBetaPersistService; - - final ConfigInfoTagPersistService configInfoTagPersistService; + final ConfigInfoGrayPersistService configInfoGrayPersistService; public DumpProcessor(ConfigInfoPersistService configInfoPersistService, - ConfigInfoBetaPersistService configInfoBetaPersistService, - ConfigInfoTagPersistService configInfoTagPersistService) { + ConfigInfoGrayPersistService configInfoGrayPersistService) { this.configInfoPersistService = configInfoPersistService; - this.configInfoBetaPersistService = configInfoBetaPersistService; - this.configInfoTagPersistService = configInfoTagPersistService; + this.configInfoGrayPersistService = configInfoGrayPersistService; } @Override @@ -64,37 +58,26 @@ public boolean process(NacosTask task) { String tenant = pair[2]; long lastModifiedOut = dumpTask.getLastModified(); String handleIp = dumpTask.getHandleIp(); - boolean isBeta = dumpTask.isBeta(); - String tag = dumpTask.getTag(); + String grayName = dumpTask.getGrayName(); + ConfigDumpEvent.ConfigDumpEventBuilder build = ConfigDumpEvent.builder().namespaceId(tenant).dataId(dataId) - .group(group).isBeta(isBeta).tag(tag).handleIp(handleIp); + .group(group).grayName(grayName).handleIp(handleIp); String type = "formal"; - if (isBeta) { - type = "beta"; - } else if (StringUtils.isNotBlank(tag)) { - type = "tag-" + tag; + if (StringUtils.isNotBlank(grayName)) { + type = grayName; } LogUtil.DUMP_LOG.info("[dump] process {} task. groupKey={}", type, dumpTask.getGroupKey()); - if (isBeta) { - // if publish beta, then dump config, update beta cache - ConfigInfoBetaWrapper cf = configInfoBetaPersistService.findConfigInfo4Beta(dataId, group, tenant); - build.remove(Objects.isNull(cf)); - build.betaIps(Objects.isNull(cf) ? null : cf.getBetaIps()); - build.content(Objects.isNull(cf) ? null : cf.getContent()); - build.type(Objects.isNull(cf) ? null : cf.getType()); - build.encryptedDataKey(Objects.isNull(cf) ? null : cf.getEncryptedDataKey()); - build.lastModifiedTs(Objects.isNull(cf) ? lastModifiedOut : cf.getLastModified()); - return DumpConfigHandler.configDump(build.build()); - } - - if (StringUtils.isNotBlank(tag)) { - ConfigInfoTagWrapper cf = configInfoTagPersistService.findConfigInfo4Tag(dataId, group, tenant, tag); + if (StringUtils.isNotBlank(grayName)) { + ConfigInfoGrayWrapper cf = configInfoGrayPersistService.findConfigInfo4Gray(dataId, group, tenant, + grayName); build.remove(Objects.isNull(cf)); build.content(Objects.isNull(cf) ? null : cf.getContent()); build.type(Objects.isNull(cf) ? null : cf.getType()); build.encryptedDataKey(Objects.isNull(cf) ? null : cf.getEncryptedDataKey()); build.lastModifiedTs(Objects.isNull(cf) ? lastModifiedOut : cf.getLastModified()); + build.grayName(grayName); + build.grayRule(Objects.isNull(cf) ? null : cf.getGrayRule()); return DumpConfigHandler.configDump(build.build()); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/task/DumpAllGrayTask.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/task/DumpAllGrayTask.java new file mode 100644 index 00000000000..dfdc4ee8c3d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/task/DumpAllGrayTask.java @@ -0,0 +1,34 @@ +/* + * 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.config.server.service.dump.task; + +import com.alibaba.nacos.common.task.AbstractDelayTask; + +/** + * Dump all gray task. + * + * @author Nacos + * @date 2024/3/5 + */ +public class DumpAllGrayTask extends AbstractDelayTask { + + @Override + public void merge(AbstractDelayTask task) { + } + + public static final String TASK_ID = "dumpAllGrayConfigTask"; +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/task/DumpTask.java b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/task/DumpTask.java index 9de5ea4544f..ca83b45eea3 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/dump/task/DumpTask.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/dump/task/DumpTask.java @@ -25,19 +25,11 @@ */ public class DumpTask extends AbstractDelayTask { - public DumpTask(String groupKey, boolean isBeta, boolean isBatch, boolean isTag, String tag, long lastModified, - String handleIp) { + public DumpTask(String groupKey, String grayName, long lastModified, String handleIp) { this.groupKey = groupKey; this.lastModified = lastModified; this.handleIp = handleIp; - this.isBeta = isBeta; - if (isTag) { - this.tag = tag; - } else { - this.tag = null; - } - this.isBatch = isBatch; - + this.grayName = grayName; //retry interval: 1s setTaskInterval(1000L); } @@ -52,11 +44,7 @@ public void merge(AbstractDelayTask task) { final String handleIp; - final boolean isBeta; - - final String tag; - - final boolean isBatch; + final String grayName; public String getGroupKey() { return groupKey; @@ -70,16 +58,8 @@ public String getHandleIp() { return handleIp; } - public boolean isBeta() { - return isBeta; - } - - public String getTag() { - return tag; - } - - public boolean isBatch() { - return isBatch; + public String getGrayName() { + return grayName; } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDataTask.java b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDataTask.java deleted file mode 100755 index 8ea2d2402d4..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDataTask.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * 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.config.server.service.merge; - -import com.alibaba.nacos.common.task.AbstractDelayTask; - -/** - * Represents the task of aggregating data. - * - * @author jiuRen - */ -class MergeDataTask extends AbstractDelayTask { - - MergeDataTask(String dataId, String groupId, String tenant, String clientIp) { - this(dataId, groupId, tenant, null, clientIp); - } - - MergeDataTask(String dataId, String groupId, String tenant, String tag, String clientIp) { - this.dataId = dataId; - this.groupId = groupId; - this.tenant = tenant; - this.tag = tag; - this.clientIp = clientIp; - - // aggregation delay - setTaskInterval(DELAY); - setLastProcessTime(System.currentTimeMillis()); - } - - @Override - public void merge(AbstractDelayTask task) { - } - - public String getId() { - return toString(); - } - - @Override - public String toString() { - return "MergeTask[" + dataId + ", " + groupId + ", " + tenant + ", " + clientIp + "]"; - } - - public String getClientIp() { - return clientIp; - } - - static final long DELAY = 0L; - - final String dataId; - - final String groupId; - - final String tenant; - - final String tag; - - private final String clientIp; -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDatumService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDatumService.java deleted file mode 100644 index acee8877f75..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeDatumService.java +++ /dev/null @@ -1,195 +0,0 @@ -/* - * 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.config.server.service.merge; - -import com.alibaba.nacos.common.utils.MD5Utils; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.constant.Constants; -import com.alibaba.nacos.config.server.manager.TaskManager; -import com.alibaba.nacos.config.server.model.ConfigInfo; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; -import com.alibaba.nacos.config.server.service.ConfigCacheService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; -import com.alibaba.nacos.config.server.utils.ContentUtils; -import com.alibaba.nacos.config.server.utils.GroupKey; -import com.alibaba.nacos.core.distributed.ProtocolManager; -import com.alibaba.nacos.persistence.configuration.DatasourceConfiguration; -import com.alibaba.nacos.persistence.constants.PersistenceConstant; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.sys.env.EnvUtil; -import com.alibaba.nacos.sys.utils.ApplicationUtils; -import com.alibaba.nacos.sys.utils.InetUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.atomic.AtomicInteger; - -/** - * Data aggregation service. - * - *

Full aggregation at startup and single aggregation triggered by data modification. - * - * @author jiuRen - */ -@Service -public class MergeDatumService { - - private static final Logger LOGGER = LoggerFactory.getLogger(MergeDatumService.class); - - final TaskManager mergeTasks; - - static final int INIT_THREAD_COUNT = 40; - - static final AtomicInteger FINISHED = new AtomicInteger(); - - static int total = 0; - - private ConfigInfoPersistService configInfoPersistService; - - private ConfigInfoAggrPersistService configInfoAggrPersistService; - - @Autowired - public MergeDatumService(ConfigInfoPersistService configInfoPersistService, - ConfigInfoAggrPersistService configInfoAggrPersistService, - ConfigInfoTagPersistService configInfoTagPersistService) { - this.configInfoPersistService = configInfoPersistService; - this.configInfoAggrPersistService = configInfoAggrPersistService; - mergeTasks = new TaskManager("com.alibaba.nacos.MergeDatum"); - mergeTasks.setDefaultTaskProcessor( - new MergeTaskProcessor(configInfoPersistService, configInfoAggrPersistService, - configInfoTagPersistService, this)); - } - - /** - * splitList. - * - * @param list list to split. - * @param count count expect to be split. - * @return - */ - public List> splitList(List list, int count) { - List> result = new ArrayList<>(count); - for (int i = 0; i < count; i++) { - result.add(new ArrayList<>()); - } - for (int i = 0; i < list.size(); i++) { - ConfigInfoChanged config = list.get(i); - result.get(i % count).add(config); - } - return result; - } - - /** - * Called after data changes to add aggregation tasks. - */ - public void addMergeTask(String dataId, String groupId, String tenant, String clientIp) { - if (!canExecute()) { - return; - } - MergeDataTask task = new MergeDataTask(dataId, groupId, tenant, clientIp); - mergeTasks.addTask(task.getId(), task); - } - - private boolean canExecute() { - if (!DatasourceConfiguration.isEmbeddedStorage()) { - return true; - } - if (EnvUtil.getStandaloneMode()) { - return true; - } - ProtocolManager protocolManager = ApplicationUtils.getBean(ProtocolManager.class); - return protocolManager.getCpProtocol().isLeader(PersistenceConstant.CONFIG_MODEL_RAFT_GROUP); - } - - void executeMergeConfigTask(List configInfoList, int pageSize) { - for (ConfigInfoChanged configInfo : configInfoList) { - String dataId = configInfo.getDataId(); - String group = configInfo.getGroup(); - String tenant = configInfo.getTenant(); - try { - List datumList = new ArrayList<>(); - int rowCount = configInfoAggrPersistService.aggrConfigInfoCount(dataId, group, tenant); - int pageCount = (int) Math.ceil(rowCount * 1.0 / pageSize); - for (int pageNo = 1; pageNo <= pageCount; pageNo++) { - Page page = configInfoAggrPersistService.findConfigInfoAggrByPage(dataId, group, - tenant, pageNo, pageSize); - if (page != null) { - datumList.addAll(page.getPageItems()); - LOGGER.info("[merge-query] {}, {}, size/total={}/{}", dataId, group, datumList.size(), - rowCount); - } - } - - // merge - if (datumList.size() > 0) { - ConfigInfo cf = MergeTaskProcessor.merge(dataId, group, tenant, datumList); - String aggrContent = cf.getContent(); - String localContentMd5 = ConfigCacheService.getContentMd5(GroupKey.getKey(dataId, group)); - String aggrConetentMd5 = MD5Utils.md5Hex(aggrContent, Constants.ENCODE); - - if (!StringUtils.equals(localContentMd5, aggrConetentMd5)) { - configInfoPersistService.insertOrUpdate(null, null, cf, null); - LOGGER.info("[merge-ok] {}, {}, size={}, length={}, md5={}, content={}", dataId, group, - datumList.size(), cf.getContent().length(), cf.getMd5(), - ContentUtils.truncateContent(cf.getContent())); - } - } else { - // remove config info - configInfoPersistService.removeConfigInfo(dataId, group, tenant, InetUtils.getSelfIP(), null); - LOGGER.warn("[merge-delete] delete config info because no datum. dataId=" + dataId + ", groupId=" - + group); - } - - } catch (Throwable e) { - LOGGER.info("[merge-error] " + dataId + ", " + group + ", " + e.toString(), e); - } - FINISHED.incrementAndGet(); - if (FINISHED.get() % 100 == 0) { - LOGGER.info("[all-merge-dump] {} / {}", FINISHED.get(), total); - } - } - LOGGER.info("[all-merge-dump] {} / {}", FINISHED.get(), total); - } - - public void executeConfigsMerge(List configInfoList) { - new MergeAllDataWorker(configInfoList).start(); - } - - public class MergeAllDataWorker extends Thread { - - static final int PAGE_SIZE = 10000; - - private List configInfoList; - - public MergeAllDataWorker(List configInfoList) { - super("MergeAllDataWorker"); - this.configInfoList = configInfoList; - } - - @Override - public void run() { - executeMergeConfigTask(configInfoList, PAGE_SIZE); - } - } -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessor.java deleted file mode 100755 index 89745589fdc..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessor.java +++ /dev/null @@ -1,157 +0,0 @@ -/* - * 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.config.server.service.merge; - -import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.task.NacosTask; -import com.alibaba.nacos.config.server.constant.Constants; -import com.alibaba.nacos.common.task.NacosTaskProcessor; -import com.alibaba.nacos.config.server.model.ConfigInfo; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; -import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; -import com.alibaba.nacos.config.server.utils.ContentUtils; -import com.alibaba.nacos.config.server.utils.TimeUtils; -import com.alibaba.nacos.sys.utils.InetUtils; -import com.alibaba.nacos.common.utils.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.List; - -/** - * Merge task processor. - * - * @author Nacos - */ -public class MergeTaskProcessor implements NacosTaskProcessor { - - private static final Logger LOGGER = LoggerFactory.getLogger(MergeTaskProcessor.class); - - private static final int PAGE_SIZE = 10000; - - private ConfigInfoPersistService configInfoPersistService; - - private ConfigInfoAggrPersistService configInfoAggrPersistService; - - private ConfigInfoTagPersistService configInfoTagPersistService; - - private MergeDatumService mergeService; - - MergeTaskProcessor(ConfigInfoPersistService configInfoPersistService, - ConfigInfoAggrPersistService configInfoAggrPersistService, - ConfigInfoTagPersistService configInfoTagPersistService, MergeDatumService mergeService) { - this.configInfoPersistService = configInfoPersistService; - this.configInfoAggrPersistService = configInfoAggrPersistService; - this.configInfoTagPersistService = configInfoTagPersistService; - this.mergeService = mergeService; - } - - @Override - public boolean process(NacosTask task) { - MergeDataTask mergeTask = (MergeDataTask) task; - final String dataId = mergeTask.dataId; - final String group = mergeTask.groupId; - final String tenant = mergeTask.tenant; - final String tag = mergeTask.tag; - final String clientIp = mergeTask.getClientIp(); - try { - List datumList = new ArrayList<>(); - int rowCount = configInfoAggrPersistService.aggrConfigInfoCount(dataId, group, tenant); - int pageCount = (int) Math.ceil(rowCount * 1.0 / PAGE_SIZE); - for (int pageNo = 1; pageNo <= pageCount; pageNo++) { - Page page = configInfoAggrPersistService.findConfigInfoAggrByPage(dataId, group, tenant, - pageNo, PAGE_SIZE); - if (page != null) { - datumList.addAll(page.getPageItems()); - LOGGER.info("[merge-query] {}, {}, size/total={}/{}", dataId, group, datumList.size(), rowCount); - } - } - - final Timestamp time = TimeUtils.getCurrentTime(); - if (datumList.size() > 0) { - // merge - ConfigInfo cf = merge(dataId, group, tenant, datumList); - - configInfoPersistService.insertOrUpdate(null, null, cf, null); - - LOGGER.info("[merge-ok] {}, {}, size={}, length={}, md5={}, content={}", dataId, group, - datumList.size(), cf.getContent().length(), cf.getMd5(), - ContentUtils.truncateContent(cf.getContent())); - - ConfigTraceService.logPersistenceEvent(dataId, group, tenant, null, time.getTime(), - InetUtils.getSelfIP(), ConfigTraceService.PERSISTENCE_EVENT, - ConfigTraceService.PERSISTENCE_TYPE_MERGE, cf.getContent()); - } else { - String eventType; - - // remove - if (StringUtils.isBlank(tag)) { - eventType = ConfigTraceService.PERSISTENCE_EVENT; - - configInfoPersistService.removeConfigInfo(dataId, group, tenant, clientIp, null); - } else { - eventType = ConfigTraceService.PERSISTENCE_EVENT_TAG + "-" + tag; - - configInfoTagPersistService.removeConfigInfoTag(dataId, group, tenant, tag, clientIp, null); - } - - LOGGER.warn( - "[merge-delete] delete config info because no datum. dataId=" + dataId + ", groupId=" + group); - - ConfigTraceService.logPersistenceEvent(dataId, group, tenant, null, time.getTime(), - InetUtils.getSelfIP(), eventType, ConfigTraceService.PERSISTENCE_TYPE_REMOVE, null); - } - NotifyCenter.publishEvent(new ConfigDataChangeEvent(false, dataId, group, tenant, tag, time.getTime())); - - } catch (Exception e) { - mergeService.addMergeTask(dataId, group, tenant, mergeTask.getClientIp()); - LOGGER.info("[merge-error] " + dataId + ", " + group + ", " + e.toString(), e); - } - - return true; - } - - /** - * merge datumList {@link ConfigInfoAggr}. - * - * @param dataId data id - * @param group group - * @param tenant tenant - * @param datumList datumList - * @return {@link ConfigInfo} - */ - public static ConfigInfo merge(String dataId, String group, String tenant, List datumList) { - StringBuilder sb = new StringBuilder(); - String appName = null; - for (ConfigInfoAggr aggrInfo : datumList) { - if (aggrInfo.getAppName() != null) { - appName = aggrInfo.getAppName(); - } - sb.append(aggrInfo.getContent()); - sb.append(Constants.NACOS_LINE_SEPARATOR); - } - String content = sb.substring(0, sb.lastIndexOf(Constants.NACOS_LINE_SEPARATOR)); - return new ConfigInfo(dataId, group, tenant, appName, content); - } -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java index cb66c8f4d22..ce9abebc0a9 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyService.java @@ -25,11 +25,14 @@ import com.alibaba.nacos.common.task.AbstractDelayTask; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; import com.alibaba.nacos.config.server.monitor.MetricsMonitor; import com.alibaba.nacos.config.server.remote.ConfigClusterRpcClientProxy; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import com.alibaba.nacos.config.server.utils.ConfigExecutor; import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.config.server.utils.PropertyUtil; import com.alibaba.nacos.core.cluster.Member; import com.alibaba.nacos.core.cluster.NodeState; import com.alibaba.nacos.core.cluster.ServerMemberManager; @@ -47,6 +50,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.TimeUnit; +import static com.alibaba.nacos.core.cluster.MemberMetaDataConstants.SUPPORT_GRAY_MODEL; + /** * Async notify service. * @@ -101,12 +106,8 @@ public Class subscribeType() { void handleConfigDataChangeEvent(Event event) { if (event instanceof ConfigDataChangeEvent) { ConfigDataChangeEvent evt = (ConfigDataChangeEvent) event; - long dumpTs = evt.lastModifiedTs; - String dataId = evt.dataId; - String group = evt.group; - String tenant = evt.tenant; - String tag = evt.tag; - MetricsMonitor.incrementConfigChangeCount(tenant, group, dataId); + + MetricsMonitor.incrementConfigChangeCount(evt.tenant, evt.group, evt.dataId); Collection ipList = memberManager.allMembersWithoutSelf(); @@ -115,8 +116,11 @@ void handleConfigDataChangeEvent(Event event) { for (Member member : ipList) { // grpc report data change only - rpcQueue.add( - new NotifySingleRpcTask(dataId, group, tenant, tag, dumpTs, evt.isBeta, evt.isBatch, member)); + NotifySingleRpcTask notifySingleRpcTask = generateTask(evt, member); + if (notifySingleRpcTask != null) { + rpcQueue.add(notifySingleRpcTask); + } + } if (!rpcQueue.isEmpty()) { ConfigExecutor.executeAsyncNotify(new AsyncRpcTask(rpcQueue)); @@ -124,6 +128,30 @@ void handleConfigDataChangeEvent(Event event) { } } + private NotifySingleRpcTask generateTask(ConfigDataChangeEvent configDataChangeEvent, Member member) { + + NotifySingleRpcTask task = new NotifySingleRpcTask(configDataChangeEvent.dataId, configDataChangeEvent.group, + configDataChangeEvent.tenant, configDataChangeEvent.grayName, configDataChangeEvent.lastModifiedTs, + member); + + if (PropertyUtil.isGrayCompatibleModel() && StringUtils.isNotBlank(configDataChangeEvent.grayName)) { + + // old server should set beta or tag flag + if (!(Boolean) member.getExtendInfo().getOrDefault(SUPPORT_GRAY_MODEL, Boolean.FALSE)) { + String underLine = "_"; + task.setBeta(BetaGrayRule.TYPE_BETA.equals(configDataChangeEvent.grayName)); + if (configDataChangeEvent.grayName.startsWith(TagGrayRule.TYPE_TAG + underLine)) { + task.setTag(configDataChangeEvent.grayName.substring( + configDataChangeEvent.grayName.indexOf(TagGrayRule.TYPE_TAG + underLine) + 4)); + } + + } + } + + // compatible with gray model + return task; + } + private boolean isUnHealthy(String targetIp) { return !memberManager.stateCheck(targetIp, HEALTHY_CHECK_STATUS); } @@ -134,12 +162,12 @@ void executeAsyncRpcTask(Queue queue) { ConfigChangeClusterSyncRequest syncRequest = new ConfigChangeClusterSyncRequest(); syncRequest.setDataId(task.getDataId()); + syncRequest.setTenant(task.getTenant()); syncRequest.setGroup(task.getGroup()); - syncRequest.setBeta(task.isBeta()); syncRequest.setLastModified(task.getLastModified()); + syncRequest.setGrayName(task.getGrayName()); + syncRequest.setBeta(task.isBeta()); syncRequest.setTag(task.getTag()); - syncRequest.setBatch(task.isBatch()); - syncRequest.setTenant(task.getTenant()); Member member = task.member; String event = getNotifyEvent(task); @@ -200,27 +228,24 @@ public static class NotifySingleRpcTask extends AbstractDelayTask { private Member member; + private String grayName; + + @Deprecated private boolean isBeta; + @Deprecated private String tag; - private boolean isBatch; - - public NotifySingleRpcTask(String dataId, String group, String tenant, String tag, long lastModified, - boolean isBeta, boolean isBatch, Member member) { - this(dataId, group, tenant, lastModified); - this.member = member; - this.isBeta = isBeta; - this.tag = tag; - this.isBatch = isBatch; - } - - private NotifySingleRpcTask(String dataId, String group, String tenant, long lastModified) { + public NotifySingleRpcTask(String dataId, String group, String tenant, String grayName, long lastModified, + Member member) { this.dataId = dataId; this.group = group; this.tenant = tenant; this.lastModified = lastModified; + this.member = member; + this.grayName = grayName; setTaskInterval(3000L); + } public boolean isBeta() { @@ -239,12 +264,12 @@ public void setTag(String tag) { this.tag = tag; } - public boolean isBatch() { - return isBatch; + public String getGrayName() { + return grayName; } - public void setBatch(boolean batch) { - isBatch = batch; + public void setGrayName(String grayName) { + this.grayName = grayName; } public String getDataId() { @@ -293,8 +318,8 @@ private static String getNotifyEvent(NotifySingleRpcTask task) { event = ConfigTraceService.NOTIFY_EVENT_BETA; } else if (!StringUtils.isBlank(task.tag)) { event = ConfigTraceService.NOTIFY_EVENT_TAG + "-" + task.tag; - } else if (task.isBatch()) { - event = ConfigTraceService.NOTIFY_EVENT_BATCH; + } else if (StringUtils.isNotBlank(task.grayName)) { + event = ConfigTraceService.NOTIFY_EVENT + "-" + task.grayName; } return event; } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/HttpClientManager.java b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/HttpClientManager.java index c2af4219e29..b7ae1597332 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/notify/HttpClientManager.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/notify/HttpClientManager.java @@ -64,7 +64,7 @@ public static NacosAsyncRestTemplate getNacosAsyncRestTemplate() { } private static void shutdown() { - LOGGER.warn("[ConfigServer-HttpClientManager] Start destroying NacosRestTemplate"); + LOGGER.info("[ConfigServer-HttpClientManager] Start destroying NacosRestTemplate"); try { final String httpClientFactoryBeanName = ConfigHttpClientFactory.class.getName(); HttpClientBeanHolder.shutdownNacosSyncRest(httpClientFactoryBeanName); @@ -73,7 +73,7 @@ private static void shutdown() { LOGGER.error("[ConfigServer-HttpClientManager] An exception occurred when the HTTP client was closed : {}", ExceptionUtil.getStackTrace(ex)); } - LOGGER.warn("[ConfigServer-HttpClientManager] Destruction of the end"); + LOGGER.info("[ConfigServer-HttpClientManager] Completed destruction of NacosRestTemplate"); } /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigChainRequestExtractorService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigChainRequestExtractorService.java new file mode 100644 index 00000000000..a847625863d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigChainRequestExtractorService.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.config.server.exception.NacosConfigException; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.util.Optional; + +/** + * Service class for initializing and retrieving the configuration query request extractor. + * + * @author Nacos + */ +public class ConfigChainRequestExtractorService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigChainRequestExtractorService.class); + + private static ConfigQueryChainRequestExtractor extractor; + + static { + String curExtractor = EnvUtil.getProperty("nacos.config.query.chain.request.extractor", "nacos"); + Optional optionalBuilder = NacosServiceLoader.load(ConfigQueryChainRequestExtractor.class) + .stream() + .filter(builder -> builder.getName().equals(curExtractor)) + .findFirst(); + if (optionalBuilder.isPresent()) { + extractor = optionalBuilder.get(); + LOGGER.info("ConfigQueryRequestExtractor has been initialized successfully with extractor: {}", curExtractor); + } else { + String errorMessage = "No suitable ConfigQueryRequestExtractor found for name: " + curExtractor; + LOGGER.error(errorMessage); + throw new NacosConfigException(errorMessage); + } + } + + public static ConfigQueryChainRequestExtractor getExtractor() { + return extractor; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainRequestExtractor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainRequestExtractor.java new file mode 100644 index 00000000000..ae229cb4709 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainRequestExtractor.java @@ -0,0 +1,55 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query; + +import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; + +import javax.servlet.http.HttpServletRequest; + +/** + * Interface for extracting configuration query chain requests from different sources. + * + * @author Nacos + */ +public interface ConfigQueryChainRequestExtractor { + + /** + * Gets the name of the current implementation. + * + * @return the name of the current implementation + */ + String getName(); + + /** + * Extracts a configuration query chain request from an HTTP request. + * + * @param request the HTTP request object + * @return the extracted configuration query chain request + */ + ConfigQueryChainRequest extract(HttpServletRequest request); + + /** + * Extracts a configuration query chain request from a configuration query request object. + * + * @param request the configuration query request object + * @param requestMeta the request metadata + * @return the extracted configuration query chain request + */ + ConfigQueryChainRequest extract(ConfigQueryRequest request, RequestMeta requestMeta); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainService.java new file mode 100644 index 00000000000..57c8d5659b6 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryChainService.java @@ -0,0 +1,73 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.config.server.exception.NacosConfigException; +import com.alibaba.nacos.config.server.service.query.enums.ResponseCode; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +/** + * Service class for initializing and retrieving the configuration query chain builder. + * + * @author Nacos + */ +@Service +public class ConfigQueryChainService { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigQueryChainService.class); + + private final ConfigQueryHandlerChain chain; + + public ConfigQueryChainService() { + String curChain = EnvUtil.getProperty("nacos.config.query.chain.builder", "nacos"); + Optional optionalBuilder = NacosServiceLoader.load(ConfigQueryHandlerChainBuilder.class) + .stream() + .filter(builder -> builder.getName().equals(curChain)) + .findFirst(); + if (optionalBuilder.isPresent()) { + chain = optionalBuilder.get().build(); + LOGGER.info("ConfigQueryHandlerChain has been initialized successfully with chain: {}", curChain); + } else { + String errorMessage = "No suitable ConfigQueryHandlerChainBuilder found for name: " + curChain; + LOGGER.error(errorMessage); + throw new NacosConfigException(errorMessage); + } + } + + /** + * Handles the configuration query request. + * + * @param request the configuration query request object + * @return the configuration query response object + */ + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) { + try { + return chain.handle(request); + } catch (Exception e) { + LOGGER.error("[Error] Fail to handle ConfigQueryChainRequest", e); + return ConfigQueryChainResponse.buildFailResponse(ResponseCode.FAIL.getCode(), e.getMessage()); + } + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChain.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChain.java new file mode 100644 index 00000000000..720fcb811b4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChain.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query; + +import com.alibaba.nacos.config.server.service.query.handler.ConfigQueryHandler; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Objects; + +/** + * ConfigQueryHandlerChain. + * @author Nacos + */ +public class ConfigQueryHandlerChain { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigQueryHandlerChain.class); + + private ConfigQueryHandler head; + + private ConfigQueryHandler tail; + + public ConfigQueryHandlerChain() { + } + + /** + * Adds a new configuration query handler to the chain. + * + * @param handler the configuration query handler to be added + * @return the current configuration query handler chain object, supporting method chaining + */ + public ConfigQueryHandlerChain addHandler(ConfigQueryHandler handler) { + if (Objects.isNull(handler)) { + LOGGER.warn("Attempted to add a null config query handler"); + return this; + } + + if (head == null) { + head = handler; + tail = handler; + } else { + tail.setNextHandler(handler); + tail = handler; + } + + return this; + } + + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + return head.handle(request); + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChainBuilder.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChainBuilder.java new file mode 100644 index 00000000000..e9f1b0d544c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/ConfigQueryHandlerChainBuilder.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query; + +/** + * ConfigQueryHandlerChainBuilder. + * + * @author Nacos + */ +public interface ConfigQueryHandlerChainBuilder { + + /** + * Builds the configuration query handler chain. + * + * @return the configuration query handler chain + */ + ConfigQueryHandlerChain build(); + + /** + * Gets the name of the builder. + * + * @return the name of the builder + */ + String getName(); +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultChainRequestExtractor.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultChainRequestExtractor.java new file mode 100644 index 00000000000..4e3ca0c7ea4 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultChainRequestExtractor.java @@ -0,0 +1,96 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query; + +import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.utils.RequestUtil; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; + +/** + * DefaultChainRequestExtractor. + * + * @author Nacos + */ +public class DefaultChainRequestExtractor implements ConfigQueryChainRequestExtractor { + + @Override + public String getName() { + return "nacos"; + } + + @Override + public ConfigQueryChainRequest extract(HttpServletRequest request) { + final String dataId = request.getParameter("dataId"); + final String group = request.getParameter("group"); + String tenant = request.getParameter("tenant"); + if (StringUtils.isBlank(tenant)) { + tenant = StringUtils.EMPTY; + } + String tag = request.getParameter("tag"); + String autoTag = request.getHeader(VIPSERVER_TAG); + String clientIp = RequestUtil.getRemoteIp(request); + + Map appLabels = new HashMap<>(4); + appLabels.put(BetaGrayRule.CLIENT_IP_LABEL, clientIp); + if (StringUtils.isNotBlank(tag)) { + appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, tag); + } else if (StringUtils.isNotBlank(autoTag)) { + appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, autoTag); + } + + ConfigQueryChainRequest chainRequest = new ConfigQueryChainRequest(); + chainRequest.setDataId(dataId); + chainRequest.setGroup(group); + chainRequest.setTenant(tenant); + chainRequest.setTag(tag); + chainRequest.setAppLabels(appLabels); + + return chainRequest; + } + + @Override + public ConfigQueryChainRequest extract(ConfigQueryRequest request, RequestMeta requestMeta) { + ConfigQueryChainRequest chainRequest = new ConfigQueryChainRequest(); + + String tag = request.getTag(); + Map appLabels = new HashMap<>(4); + appLabels.put(BetaGrayRule.CLIENT_IP_LABEL, requestMeta.getClientIp()); + if (StringUtils.isNotBlank(tag)) { + appLabels.put(TagGrayRule.VIP_SERVER_TAG_LABEL, tag); + } else { + appLabels.putAll(requestMeta.getAppLabels()); + } + + chainRequest.setDataId(request.getDataId()); + chainRequest.setGroup(request.getGroup()); + chainRequest.setTenant(request.getTenant()); + chainRequest.setTag(request.getTag()); + chainRequest.setAppLabels(appLabels); + + return chainRequest; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultConfigQueryHandlerChainBuilder.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultConfigQueryHandlerChainBuilder.java new file mode 100644 index 00000000000..464befe65e8 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/DefaultConfigQueryHandlerChainBuilder.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query; + +import com.alibaba.nacos.config.server.service.query.handler.ConfigChainEntryHandler; +import com.alibaba.nacos.config.server.service.query.handler.FormalHandler; +import com.alibaba.nacos.config.server.service.query.handler.GrayRuleMatchHandler; +import com.alibaba.nacos.config.server.service.query.handler.SpecialTagNotFoundHandler; + +/** + * DefaultConfigQueryHandlerChainBuilder. + * + * @author Nacos + */ +public class DefaultConfigQueryHandlerChainBuilder implements ConfigQueryHandlerChainBuilder { + + @Override + public ConfigQueryHandlerChain build() { + ConfigQueryHandlerChain chain = new ConfigQueryHandlerChain(); + chain.addHandler(new ConfigChainEntryHandler()) + .addHandler(new GrayRuleMatchHandler()) + .addHandler(new SpecialTagNotFoundHandler()) + .addHandler(new FormalHandler()); + return chain; + } + + @Override + public String getName() { + return "nacos"; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/enums/ResponseCode.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/enums/ResponseCode.java new file mode 100644 index 00000000000..7dc0af9f9db --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/enums/ResponseCode.java @@ -0,0 +1,61 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.enums; + +/** + * ResponseCode. + * + * @author Nacos + */ +public enum ResponseCode { + /** + * Request success. + */ + SUCCESS(200, "Response ok"), + + /** + * Request failed. + */ + FAIL(500, "Response fail"); + + int code; + + String desc; + + ResponseCode(int code, String desc) { + this.code = code; + this.desc = desc; + } + + /** + * Getter method for property code. + * + * @return property value of code + */ + public int getCode() { + return code; + } + + /** + * Getter method for property desc. + * + * @return property value of desc + */ + public String getDesc() { + return desc; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/AbstractConfigQueryHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/AbstractConfigQueryHandler.java new file mode 100644 index 00000000000..90254ea819d --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/AbstractConfigQueryHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.handler; + +/** + * AbstractConfigQueryHandler. + * This abstract class provides a base implementation for configuration query handlers. + * It implements the {@link ConfigQueryHandler} interface and handles the chaining of handlers. + * + * @author Nacos + */ +public abstract class AbstractConfigQueryHandler implements ConfigQueryHandler { + + public ConfigQueryHandler nextHandler; + + public void setNextHandler(ConfigQueryHandler nextHandler) { + this.nextHandler = nextHandler; + } + + public ConfigQueryHandler getNextHandler() { + return this.nextHandler; + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigChainEntryHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigChainEntryHandler.java new file mode 100644 index 00000000000..a87e92a1482 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigChainEntryHandler.java @@ -0,0 +1,86 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; + +/** + * ConfigChainEntryHandler. + * The entry point handler for the responsibility chain, responsible for initializing the chain and handling configuration query requests. + * + * @author Nacos + */ +public class ConfigChainEntryHandler extends AbstractConfigQueryHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigChainEntryHandler.class); + + private static final String CHAIN_ENTRY_HANDLER = "chainEntryHandler"; + + private static final ThreadLocal CACHE_ITEM_THREAD_LOCAL = new ThreadLocal<>(); + + @Override + public String getName() { + return CHAIN_ENTRY_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + String groupKey = GroupKey2.getKey(request.getDataId(), request.getGroup(), request.getTenant()); + int lockResult = ConfigCacheService.tryConfigReadLock(groupKey); + CacheItem cacheItem = ConfigCacheService.getContentCache(groupKey); + + if (lockResult > 0 && cacheItem != null) { + try { + CACHE_ITEM_THREAD_LOCAL.set(cacheItem); + if (nextHandler != null) { + return nextHandler.handle(request); + } else { + LOGGER.warn("chainEntryHandler's next handler is null"); + return new ConfigQueryChainResponse(); + } + } finally { + CACHE_ITEM_THREAD_LOCAL.remove(); + ConfigCacheService.releaseReadLock(groupKey); + } + } else if (lockResult == 0 || cacheItem == null) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_NOT_FOUND); + return response; + } else { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_QUERY_CONFLICT); + return response; + } + } + + public static CacheItem getThreadLocalCacheItem() { + return CACHE_ITEM_THREAD_LOCAL.get(); + } + + public static void removeThreadLocalCacheItem() { + CACHE_ITEM_THREAD_LOCAL.remove(); + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigQueryHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigQueryHandler.java new file mode 100644 index 00000000000..a1a690b30f0 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/ConfigQueryHandler.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +/** + * Configuration Query Handler Interface. + * This interface defines the standard methods for handling configuration query requests. + * + * @author Nacos + */ +public interface ConfigQueryHandler { + + /** + * Gets the name of the handler. + * @return The name of the handler. + */ + String getName(); + + /** + * Handles the configuration query request. + * If the current handler cannot process the request, it should throw an IOException. + * @param request The configuration query request. + * @return The response to the configuration query. + * @throws IOException If an I/O error occurs. + */ + ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException; + + /** + * Sets the next handler in the chain. + * @param nextHandler The next handler to which the request can be passed if the current handler cannot process it. + */ + void setNextHandler(ConfigQueryHandler nextHandler); + + /** + * Gets the next handler in the chain. + * @return The next handler. + */ + ConfigQueryHandler getNextHandler(); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/FormalHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/FormalHandler.java new file mode 100644 index 00000000000..dccbe620ca0 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/FormalHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +/** + * Formal Handler. + * This class represents a formal handler in the configuration query processing chain. + * If the request has not been processed by previous handlers, it will be handled by this handler. + * @author Nacos + */ +public class FormalHandler extends AbstractConfigQueryHandler { + + private static final String FORMAL_HANDLER = "formalHandler"; + + @Override + public String getName() { + return FORMAL_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + + String dataId = request.getDataId(); + String group = request.getGroup(); + String tenant = request.getTenant(); + + CacheItem cacheItem = ConfigChainEntryHandler.getThreadLocalCacheItem(); + String md5 = cacheItem.getConfigCache().getMd5(); + long lastModified = cacheItem.getConfigCache().getLastModifiedTs(); + String encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); + String contentType = cacheItem.getType(); + String content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); + + response.setContent(content); + response.setMd5(md5); + response.setLastModified(lastModified); + response.setEncryptedDataKey(encryptedDataKey); + response.setContentType(contentType); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_FORMAL); + + return response; + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/GrayRuleMatchHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/GrayRuleMatchHandler.java new file mode 100644 index 00000000000..412fd8d78ef --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/GrayRuleMatchHandler.java @@ -0,0 +1,80 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.handler; + +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +/** + * GrayRuleMatchHandler. + * This class represents a gray rule handler in the configuration query processing chain. + * It checks if the request matches any gray rules and processes the request accordingly. + * + * @author Nacos + */ +public class GrayRuleMatchHandler extends AbstractConfigQueryHandler { + + private static final String GRAY_RULE_MATCH_HANDLER = "grayRuleMatchHandler"; + + @Override + public String getName() { + return GRAY_RULE_MATCH_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + // Check if the request matches any gray rules + CacheItem cacheItem = ConfigChainEntryHandler.getThreadLocalCacheItem(); + ConfigCacheGray matchedGray = null; + if (cacheItem.getSortConfigGrays() != null && !cacheItem.getSortConfigGrays().isEmpty()) { + for (ConfigCacheGray configCacheGray : cacheItem.getSortConfigGrays()) { + if (configCacheGray.match(request.getAppLabels())) { + matchedGray = configCacheGray; + break; + } + } + } + + if (matchedGray != null) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + + long lastModified = matchedGray.getLastModifiedTs(); + String md5 = matchedGray.getMd5(); + String encryptedDataKey = matchedGray.getEncryptedDataKey(); + String content = ConfigDiskServiceFactory.getInstance() + .getGrayContent(request.getDataId(), request.getGroup(), request.getTenant(), + matchedGray.getGrayName()); + + response.setContent(content); + response.setMd5(md5); + response.setLastModified(lastModified); + response.setEncryptedDataKey(encryptedDataKey); + response.setMatchedGray(matchedGray); + response.setContentType(cacheItem.getType()); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.CONFIG_FOUND_GRAY); + + return response; + } else { + return nextHandler.handle(request); + } + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/SpecialTagNotFoundHandler.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/SpecialTagNotFoundHandler.java new file mode 100644 index 00000000000..900e755ecf9 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/handler/SpecialTagNotFoundHandler.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.handler; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainRequest; +import com.alibaba.nacos.config.server.service.query.model.ConfigQueryChainResponse; + +import java.io.IOException; + +/** + * SpecialTagNotFound Handler. + * This class represents special tag not found handler in the configuration query processing chain. + * + * @author Nacos + */ +public class SpecialTagNotFoundHandler extends AbstractConfigQueryHandler { + + private static final String SPECIAL_TAG_NOT_FOUND_HANDLER = "specialTagNotFoundHandler"; + + @Override + public String getName() { + return SPECIAL_TAG_NOT_FOUND_HANDLER; + } + + @Override + public ConfigQueryChainResponse handle(ConfigQueryChainRequest request) throws IOException { + if (StringUtils.isNotBlank(request.getTag())) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + + String dataId = request.getDataId(); + String group = request.getGroup(); + String tenant = request.getTenant(); + + CacheItem cacheItem = ConfigChainEntryHandler.getThreadLocalCacheItem(); + String md5 = cacheItem.getConfigCache().getMd5(); + long lastModified = cacheItem.getConfigCache().getLastModifiedTs(); + String encryptedDataKey = cacheItem.getConfigCache().getEncryptedDataKey(); + String contentType = cacheItem.getType(); + String content = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); + + response.setContent(content); + response.setMd5(md5); + response.setLastModified(lastModified); + response.setEncryptedDataKey(encryptedDataKey); + response.setContentType(contentType); + response.setStatus(ConfigQueryChainResponse.ConfigQueryStatus.SPECIAL_TAG_CONFIG_NOT_FOUND); + + return response; + } else { + return nextHandler.handle(request); + } + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainRequest.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainRequest.java new file mode 100644 index 00000000000..7b20943e03c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainRequest.java @@ -0,0 +1,99 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.model; + +import java.util.Map; +import java.util.Objects; + +/** + * ConfigQueryChainRequest. + * + * @author Nacos + */ +public class ConfigQueryChainRequest { + + private String dataId; + + private String group; + + private String tenant; + + private String tag; + + private Map appLabels; + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getTenant() { + return tenant; + } + + public void setTenant(String tenant) { + this.tenant = tenant; + } + + public String getTag() { + return tag; + } + + public void setTag(String tag) { + this.tag = tag; + } + + public Map getAppLabels() { + return appLabels; + } + + public void setAppLabels(Map appLabels) { + this.appLabels = appLabels; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConfigQueryChainRequest that = (ConfigQueryChainRequest) o; + return Objects.equals(dataId, that.dataId) + && Objects.equals(group, that.group) + && Objects.equals(tenant, that.tenant) + && Objects.equals(tag, that.tag) + && Objects.equals(appLabels, that.appLabels); + } + + @Override + public int hashCode() { + return Objects.hash(dataId, group, tenant, tag, appLabels); + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainResponse.java b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainResponse.java new file mode 100644 index 00000000000..1028e35aa03 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/query/model/ConfigQueryChainResponse.java @@ -0,0 +1,190 @@ +/* + * Copyright 1999-$toady.year 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.config.server.service.query.model; + +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.service.query.enums.ResponseCode; + +import java.util.Objects; + +/** + * ConfigQueryChainResponse. + * + * @author Nacos + */ +public class ConfigQueryChainResponse { + + private String content; + + private String contentType; + + private String encryptedDataKey; + + private String md5; + + private long lastModified; + + private ConfigCacheGray matchedGray; + + private int resultCode; + + private String message; + + private ConfigQueryStatus status; + + public enum ConfigQueryStatus { + /** + * Indicates that the configuration was found and is formal. + */ + CONFIG_FOUND_FORMAL, + + /** + * Indicates that the configuration was found and is gray. + */ + CONFIG_FOUND_GRAY, + + /** + * Indicates that the configuration special tag was not found. + */ + SPECIAL_TAG_CONFIG_NOT_FOUND, + + /** + * Indicates that the configuration was not found. + */ + CONFIG_NOT_FOUND, + + /** + * Indicates a conflict in the configuration query. + */ + CONFIG_QUERY_CONFLICT, + } + + public String getContent() { + return content; + } + + public void setContent(String content) { + this.content = content; + } + + public String getContentType() { + return contentType; + } + + public void setContentType(String contentType) { + this.contentType = contentType; + } + + public String getEncryptedDataKey() { + return encryptedDataKey; + } + + public void setEncryptedDataKey(String encryptedDataKey) { + this.encryptedDataKey = encryptedDataKey; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } + + public long getLastModified() { + return lastModified; + } + + public void setLastModified(long lastModified) { + this.lastModified = lastModified; + } + + public ConfigCacheGray getMatchedGray() { + return matchedGray; + } + + public void setMatchedGray(ConfigCacheGray matchedGray) { + this.matchedGray = matchedGray; + } + + public int getResultCode() { + return resultCode; + } + + public void setResultCode(int resultCode) { + this.resultCode = resultCode; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + public ConfigQueryStatus getStatus() { + return status; + } + + public void setStatus(ConfigQueryStatus status) { + this.status = status; + } + + /** + * Build fail response. + * + * @param errorCode errorCode. + * @param message message. + * @return response. + */ + public static ConfigQueryChainResponse buildFailResponse(int errorCode, String message) { + ConfigQueryChainResponse response = new ConfigQueryChainResponse(); + response.setErrorInfo(errorCode, message); + return response; + } + + public void setErrorInfo(int errorCode, String errorMsg) { + this.resultCode = ResponseCode.FAIL.getCode(); + this.message = errorMsg; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + ConfigQueryChainResponse that = (ConfigQueryChainResponse) o; + return lastModified == that.lastModified + && Objects.equals(content, that.content) + && Objects.equals(contentType, that.contentType) + && Objects.equals(encryptedDataKey, that.encryptedDataKey) + && Objects.equals(md5, that.md5) + && Objects.equals(matchedGray, that.matchedGray) + && Objects.equals(resultCode, that.resultCode) + && Objects.equals(message, that.message) + && status == that.status; + } + + @Override + public int hashCode() { + return Objects.hash(content, contentType, encryptedDataKey, md5, lastModified, matchedGray, resultCode, message, status); + } +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoAggrPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoAggrPersistService.java deleted file mode 100644 index 6a1df0859ea..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoAggrPersistService.java +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 1999-2022 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.config.server.service.repository; - -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.persistence.repository.PaginationHelper; - -import java.util.List; -import java.util.Map; - -/** - * Database service, providing access to config_info_aggr in the database. - * - * @author lixiaoshuang - */ -public interface ConfigInfoAggrPersistService { - - Object[] EMPTY_ARRAY = new Object[] {}; - - String PATTERN_STR = "*"; - - /** - * create Pagination utils. - * - * @param Generic object - * @return {@link PaginationHelper} - */ - PaginationHelper createPaginationHelper(); - - //------------------------------------------insert---------------------------------------------// - - /** - * Add data before aggregation to the database, select -> update or insert . - * - * @param dataId data id - * @param group group - * @param tenant tenant - * @param datumId datum id - * @param appName app name - * @param content config content - * @return {@code true} if add success - */ - boolean addAggrConfigInfo(final String dataId, final String group, String tenant, final String datumId, - String appName, final String content); - - /** - * Add or update data in batches. Any exception during the transaction will force a TransactionSystemException to be - * thrown. - * - * @param dataId dataId - * @param group group - * @param tenant tenant - * @param appName app name - * @param datumMap datumMap - * @return {@code true} if publish success - */ - boolean batchPublishAggr(final String dataId, final String group, final String tenant, - final Map datumMap, final String appName); - - - //------------------------------------------select---------------------------------------------// - - /** - * Get count of aggregation config info. - * - * @param dataId data id - * @param group group - * @param tenant tenant - * @return count - */ - int aggrConfigInfoCount(String dataId, String group, String tenant); - - /** - * Query aggregation config info. - * - * @param dataId data id - * @param group group - * @param tenant tenant - * @param pageNo page number - * @param pageSize page size - * @return {@link Page} with {@link ConfigInfoAggr} generation - */ - Page findConfigInfoAggrByPage(String dataId, String group, String tenant, final int pageNo, - final int pageSize); - - /** - * Find all aggregated data sets. - * - * @return {@link ConfigInfoChanged} list - */ - List findAllAggrGroup(); - -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoBetaPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoBetaPersistService.java index 69d5cd9c892..f98b43a6065 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoBetaPersistService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoBetaPersistService.java @@ -27,9 +27,10 @@ /** * Database service, providing access to config_info_beta in the database. - * + * Deprecated since 2.5.0,only support on compatibility,replaced with ConfigInfoGray model, will be soon removed on further version. * @author lixiaoshuang */ +@Deprecated public interface ConfigInfoBetaPersistService { /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoGrayPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoGrayPersistService.java new file mode 100644 index 00000000000..791b620d3b5 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoGrayPersistService.java @@ -0,0 +1,201 @@ +/* + * Copyright 1999-2022 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.config.server.service.repository; + +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; +import com.alibaba.nacos.config.server.model.ConfigOperateResult; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.persistence.repository.PaginationHelper; + +import java.sql.Timestamp; +import java.util.List; + +/** + * Database service, providing access to config_info_gray in the database. + * + * @author rong + */ +public interface ConfigInfoGrayPersistService { + + /** + * create Pagination utils. + * + * @param Generic object + * @return {@link PaginationHelper} + */ + PaginationHelper createPaginationHelper(); + + //------------------------------------------insert---------------------------------------------// + + + /** + * get gray config info state. + * + * @param dataId dataId. + * @param group group. + * @param tenant tenant. + * @param grayName gray name. + * @return config info state. + */ + ConfigInfoStateWrapper findConfigInfo4GrayState(final String dataId, final String group, final String tenant, + String grayName); + + /** + * Add gray configuration information and publish data change events. + * + * @param configInfo config info + * @param grayName gray name + * @param grayRule gray rule + * @param srcIp remote ip + * @param srcUser user + * @return config operation result. + */ + ConfigOperateResult addConfigInfo4Gray(ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser); + + /** + * Adds configuration information with database atomic operations, minimizing SQL actions and avoiding business + * encapsulation. + * + * @param configGrayId the ID for the gray configuration + * @param configInfo the configuration information to be added + * @param grayName the name of the gray configuration + * @param grayRule the rule of the gray configuration + * @param srcIp the IP address of the source + * @param srcUser the user who performs the addition + */ + void addConfigInfoGrayAtomic(final long configGrayId, final ConfigInfo configInfo, final String grayName, final String grayRule, + final String srcIp, final String srcUser); + + /** + * insert or update gray config. + * + * @param configInfo config info + * @param grayName gray name + * @param grayRule gray rule + * @param srcIp remote ip + * @param srcUser user + * @return config operation result. + */ + ConfigOperateResult insertOrUpdateGray(final ConfigInfo configInfo, final String grayName, final String grayRule, + final String srcIp, final String srcUser); + + /** + * insert or update gray config cas. + * + * @param configInfo config info. + * @param grayName gray name + * @param grayRule gray rule + * @param srcIp remote ip. + * @param srcUser user. + * @return config operation result. + */ + ConfigOperateResult insertOrUpdateGrayCas(final ConfigInfo configInfo, final String grayName, final String grayRule, + final String srcIp, final String srcUser); + //------------------------------------------delete---------------------------------------------// + + /** + * Delete configuration; database atomic operation, minimum SQL action, no business encapsulation. + * + * @param dataId dataId + * @param group group + * @param tenant tenant + * @param grayName gray name + * @param srcIp remote ip + * @param srcUser user + */ + void removeConfigInfoGray(final String dataId, final String group, final String tenant, final String grayName, + final String srcIp, final String srcUser); + //------------------------------------------update---------------------------------------------// + + /** + * Update gray configuration information. + * + * @param configInfo config info + * @param grayName gray name + * @param grayRule gray rule + * @param srcIp remote ip + * @param srcUser user + * @return config operation result. + */ + ConfigOperateResult updateConfigInfo4Gray(ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser); + + /** + * Update gray configuration information. + * + * @param configInfo config info + * @param grayName gray name + * @param grayRule gray rule + * @param srcIp remote ip + * @param srcUser user + * @return success or not. + */ + ConfigOperateResult updateConfigInfo4GrayCas(ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser); + //------------------------------------------select---------------------------------------------// + + /** + * Query gray configuration information based on dataId and group. + * + * @param dataId data id + * @param group group + * @param tenant tenant + * @param grayName gray name + * @return ConfigInfoGrayWrapper gray model instance. + */ + ConfigInfoGrayWrapper findConfigInfo4Gray(final String dataId, final String group, final String tenant, + final String grayName); + + /** + * Returns the number of gray configuration items. + * + * @return number of configuration items. + */ + int configInfoGrayCount(); + + /** + * Query all gray config info for dump task. + * + * @param pageNo page numbser + * @param pageSize page sizxe + * @return {@link Page} with {@link ConfigInfoGrayWrapper} generation + */ + Page findAllConfigInfoGrayForDumpAll(final int pageNo, final int pageSize); + + /** + * Query all gray config info for dump task. + * + * @param startTime startTime + * @param lastMaxId lastMaxId + * @param pageSize pageSize + * @return {@link Page} with {@link ConfigInfoGrayWrapper} generation + */ + List findChangeConfig(final Timestamp startTime, long lastMaxId, final int pageSize); + + /** + * found all config grays. + * + * @param dataId dataId. + * @param group group. + * @param tenant tenant. + * @return + */ + List findConfigInfoGrays(final String dataId, final String group, final String tenant); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoPersistService.java index afb006529e0..d182900fa2a 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoPersistService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoPersistService.java @@ -169,10 +169,10 @@ void removeConfigInfo(final String dataId, final String group, final String tena * @param ids id list * @param srcIp remote ip * @param srcUser user - * @return {@link ConfigInfo} list + * @return {@link ConfigAllInfo} list * @author klw */ - List removeConfigInfoByIds(final List ids, final String srcIp, final String srcUser); + List removeConfigInfoByIds(final List ids, final String srcIp, final String srcUser); /** * Delete tag. diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoTagPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoTagPersistService.java index 06306b22662..a864977b2b8 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoTagPersistService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigInfoTagPersistService.java @@ -29,9 +29,10 @@ /** * Database service, providing access to config_info_tag in the database. - * + * Deprecated since 2.5.0,only support on compatibility,replaced with ConfigInfoGray model, will be soon removed on further version. * @author lixiaoshuang */ +@Deprecated public interface ConfigInfoTagPersistService { /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjector.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjector.java index 8b45b37905e..a22fa3a0058 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjector.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjector.java @@ -22,10 +22,10 @@ import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; import com.alibaba.nacos.config.server.model.ConfigInfo4Tag; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; import com.alibaba.nacos.config.server.model.ConfigInfoBase; import com.alibaba.nacos.config.server.model.ConfigInfoBetaWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoChanged; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoTagWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; @@ -55,6 +55,8 @@ public class ConfigRowMapperInjector { public static final ConfigInfoTagWrapperRowMapper CONFIG_INFO_TAG_WRAPPER_ROW_MAPPER = new ConfigInfoTagWrapperRowMapper(); + public static final ConfigInfoGrayWrapperRowMapper CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER = new ConfigInfoGrayWrapperRowMapper(); + public static final ConfigInfoRowMapper CONFIG_INFO_ROW_MAPPER = new ConfigInfoRowMapper(); public static final ConfigAdvanceInfoRowMapper CONFIG_ADVANCE_INFO_ROW_MAPPER = new ConfigAdvanceInfoRowMapper(); @@ -67,8 +69,6 @@ public class ConfigRowMapperInjector { public static final ConfigInfoBaseRowMapper CONFIG_INFO_BASE_ROW_MAPPER = new ConfigInfoBaseRowMapper(); - public static final ConfigInfoAggrRowMapper CONFIG_INFO_AGGR_ROW_MAPPER = new ConfigInfoAggrRowMapper(); - public static final ConfigInfoChangedRowMapper CONFIG_INFO_CHANGED_ROW_MAPPER = new ConfigInfoChangedRowMapper(); public static final ConfigHistoryRowMapper HISTORY_LIST_ROW_MAPPER = new ConfigHistoryRowMapper(); @@ -146,12 +146,6 @@ private static void injectConfigRowMapper() { ConfigRowMapperInjector.CONFIG_INFO_BASE_ROW_MAPPER.getClass().getCanonicalName(), ConfigRowMapperInjector.CONFIG_INFO_BASE_ROW_MAPPER); - // CONFIG_INFO_AGGR_ROW_MAPPER - - RowMapperManager.registerRowMapper( - ConfigRowMapperInjector.CONFIG_INFO_AGGR_ROW_MAPPER.getClass().getCanonicalName(), - ConfigRowMapperInjector.CONFIG_INFO_AGGR_ROW_MAPPER); - // CONFIG_INFO_CHANGED_ROW_MAPPER RowMapperManager.registerRowMapper( @@ -181,7 +175,7 @@ public ConfigInfoWrapper mapRow(ResultSet rs, int rowNum) throws SQLException { info.setGroup(rs.getString("group_id")); info.setTenant(rs.getString("tenant_id")); info.setAppName(rs.getString("app_name")); - + try { info.setType(rs.getString("type")); } catch (SQLException ignore) { @@ -305,6 +299,48 @@ public ConfigInfoTagWrapper mapRow(ResultSet rs, int rowNum) throws SQLException } } + public static final class ConfigInfoGrayWrapperRowMapper implements RowMapper { + + @Override + public ConfigInfoGrayWrapper mapRow(ResultSet rs, int rowNum) throws SQLException { + ConfigInfoGrayWrapper info = new ConfigInfoGrayWrapper(); + + info.setDataId(rs.getString("data_id")); + info.setGroup(rs.getString("group_id")); + info.setTenant(rs.getString("tenant_id")); + info.setGrayName(rs.getString("gray_name")); + info.setGrayRule(rs.getString("gray_rule")); + info.setAppName(rs.getString("app_name")); + + try { + info.setContent(rs.getString("content")); + } catch (SQLException ignore) { + } + try { + info.setId(rs.getLong("id")); + } catch (SQLException ignore) { + } + try { + info.setLastModified(rs.getTimestamp("gmt_modified").getTime()); + } catch (SQLException ignore) { + } + try { + info.setMd5(rs.getString("md5")); + } catch (SQLException ignore) { + } + try { + info.setEncryptedDataKey(rs.getString("encrypted_data_key")); + } catch (SQLException ignore) { + } + + try { + info.setSrcUser(rs.getString("src_user")); + } catch (SQLException ignore) { + } + return info; + } + } + public static final class ConfigInfoRowMapper implements RowMapper { @Override @@ -488,21 +524,6 @@ public ConfigInfoBase mapRow(ResultSet rs, int rowNum) throws SQLException { } } - public static final class ConfigInfoAggrRowMapper implements RowMapper { - - @Override - public ConfigInfoAggr mapRow(ResultSet rs, int rowNum) throws SQLException { - ConfigInfoAggr info = new ConfigInfoAggr(); - info.setDataId(rs.getString("data_id")); - info.setGroup(rs.getString("group_id")); - info.setDatumId(rs.getString("datum_id")); - info.setTenant(rs.getString("tenant_id")); - info.setAppName(rs.getString("app_name")); - info.setContent(rs.getString("content")); - return info; - } - } - public static final class ConfigInfoChangedRowMapper implements RowMapper { @Override @@ -528,6 +549,9 @@ public ConfigHistoryInfo mapRow(ResultSet rs, int rowNum) throws SQLException { configHistoryInfo.setSrcIp(rs.getString("src_ip")); configHistoryInfo.setSrcUser(rs.getString("src_user")); configHistoryInfo.setOpType(rs.getString("op_type")); + configHistoryInfo.setPublishType(rs.getString("publish_type")); + configHistoryInfo.setGrayName(rs.getString("gray_name")); + configHistoryInfo.setExtInfo(rs.getString("ext_info")); configHistoryInfo.setCreatedTime(rs.getTimestamp("gmt_create")); configHistoryInfo.setLastModifiedTime(rs.getTimestamp("gmt_modified")); return configHistoryInfo; @@ -549,6 +573,9 @@ public ConfigHistoryInfo mapRow(ResultSet rs, int rowNum) throws SQLException { configHistoryInfo.setSrcUser(rs.getString("src_user")); configHistoryInfo.setSrcIp(rs.getString("src_ip")); configHistoryInfo.setOpType(rs.getString("op_type")); + configHistoryInfo.setPublishType(rs.getString("publish_type")); + configHistoryInfo.setGrayName(rs.getString("gray_name")); + configHistoryInfo.setExtInfo(rs.getString("ext_info")); configHistoryInfo.setCreatedTime(rs.getTimestamp("gmt_create")); configHistoryInfo.setLastModifiedTime(rs.getTimestamp("gmt_modified")); try { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/HistoryConfigInfoPersistService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/HistoryConfigInfoPersistService.java index e0e14b71568..1573a2ba25b 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/HistoryConfigInfoPersistService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/HistoryConfigInfoPersistService.java @@ -45,15 +45,18 @@ public interface HistoryConfigInfoPersistService { /** * Update change records; database atomic operations, minimal sql actions, no business encapsulation. * - * @param id id - * @param configInfo config info - * @param srcIp ip - * @param srcUser user - * @param time time - * @param ops ops type + * @param id id + * @param configInfo config info + * @param srcIp ip + * @param srcUser user + * @param time time + * @param ops ops type + * @param publishType publish type + * @param grayName gray name + * @param extInfo extra config info */ void insertConfigHistoryAtomic(long id, ConfigInfo configInfo, String srcIp, String srcUser, final Timestamp time, - String ops); + String ops, String publishType, String grayName, String extInfo); //------------------------------------------delete---------------------------------------------// /** @@ -69,12 +72,14 @@ void insertConfigHistoryAtomic(long id, ConfigInfo configInfo, String srcIp, Str /** * Query deleted config. * - * @param startTime start time - * @param startId last max id - * @param size page size + * @param startTime start time + * @param startId last max id + * @param size page size + * @param publishType publish type * @return {@link ConfigInfoStateWrapper} list */ - List findDeletedConfig(final Timestamp startTime, final long startId, int size); + List findDeletedConfig(final Timestamp startTime, final long startId, int size, + String publishType); /** * List configuration history change record. @@ -112,4 +117,18 @@ void insertConfigHistoryAtomic(long id, ConfigInfo configInfo, String srcIp, Str */ @Deprecated int findConfigHistoryCountByTime(final Timestamp startTime); + + /** + * Get the next history config detail of the history config. + * + * @param dataId data Id + * @param group group + * @param tenant tenant + * @param publishType publish type + * @param grayName gray name + * @param startNid start nid + * @return the next history config detail of the history config + */ + ConfigHistoryInfo getNextHistoryInfo(String dataId, String group, String tenant, String publishType, String grayName, + long startNid); } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoAggrPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoAggrPersistServiceImpl.java deleted file mode 100644 index dab8c013af8..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoAggrPersistServiceImpl.java +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 1999-2022 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.config.server.service.repository.embedded; - -import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.exception.NacosConfigException; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.persistence.configuration.condition.ConditionOnEmbeddedStorage; -import com.alibaba.nacos.persistence.datasource.DataSourceService; -import com.alibaba.nacos.persistence.datasource.DynamicDataSource; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.persistence.model.event.DerbyImportEvent; -import com.alibaba.nacos.persistence.repository.PaginationHelper; -import com.alibaba.nacos.persistence.repository.embedded.EmbeddedPaginationHelperImpl; -import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder; -import com.alibaba.nacos.persistence.repository.embedded.operate.DatabaseOperate; -import com.alibaba.nacos.plugin.datasource.MapperManager; -import com.alibaba.nacos.plugin.datasource.constants.CommonConstant; -import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; -import com.alibaba.nacos.plugin.datasource.constants.TableConstant; -import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoAggrMapper; -import com.alibaba.nacos.plugin.datasource.model.MapperContext; -import com.alibaba.nacos.plugin.datasource.model.MapperResult; -import com.alibaba.nacos.sys.env.EnvUtil; -import org.springframework.context.annotation.Conditional; -import org.springframework.stereotype.Service; - -import java.sql.Timestamp; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_AGGR_ROW_MAPPER; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_CHANGED_ROW_MAPPER; - -/** - * EmbeddedConfigInfoAggrPersistServiceImpl. - * - * @author lixiaoshuang - */ -@SuppressWarnings({"PMD.MethodReturnWrapperTypeRule", "checkstyle:linelength"}) -@Conditional(value = ConditionOnEmbeddedStorage.class) -@Service("embeddedConfigInfoAggrPersistServiceImpl") -public class EmbeddedConfigInfoAggrPersistServiceImpl implements ConfigInfoAggrPersistService { - - private DataSourceService dataSourceService; - - private final DatabaseOperate databaseOperate; - - private MapperManager mapperManager; - - /** - * The constructor sets the dependency injection order. - * - * @param databaseOperate databaseOperate. - */ - public EmbeddedConfigInfoAggrPersistServiceImpl(DatabaseOperate databaseOperate) { - this.databaseOperate = databaseOperate; - this.dataSourceService = DynamicDataSource.getInstance().getDataSource(); - Boolean isDataSourceLogEnable = EnvUtil.getProperty(CommonConstant.NACOS_PLUGIN_DATASOURCE_LOG, Boolean.class, - false); - this.mapperManager = MapperManager.instance(isDataSourceLogEnable); - NotifyCenter.registerToSharePublisher(DerbyImportEvent.class); - } - - @Override - public PaginationHelper createPaginationHelper() { - return new EmbeddedPaginationHelperImpl<>(databaseOperate); - } - - @Override - public boolean addAggrConfigInfo(final String dataId, final String group, String tenant, final String datumId, - String appName, final String content) { - String appNameTmp = StringUtils.isBlank(appName) ? StringUtils.EMPTY : appName; - String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; - String contentTmp = StringUtils.isBlank(content) ? StringUtils.EMPTY : content; - final Timestamp now = new Timestamp(System.currentTimeMillis()); - - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - final String select = configInfoAggrMapper.select(Collections.singletonList("content"), - Arrays.asList("data_id", "group_id", "tenant_id", "datum_id")); - final String insert = configInfoAggrMapper.insert( - Arrays.asList("data_id", "group_id", "tenant_id", "datum_id", "app_name", "content", "gmt_modified")); - final String update = configInfoAggrMapper.update(Arrays.asList("content", "gmt_modified"), - Arrays.asList("data_id", "group_id", "tenant_id", "datum_id")); - - String dbContent = databaseOperate.queryOne(select, new Object[] {dataId, group, tenantTmp, datumId}, - String.class); - - if (Objects.isNull(dbContent)) { - final Object[] args = new Object[] {dataId, group, tenantTmp, datumId, appNameTmp, contentTmp, now}; - EmbeddedStorageContextHolder.addSqlContext(insert, args); - } else if (!dbContent.equals(content)) { - final Object[] args = new Object[] {contentTmp, now, dataId, group, tenantTmp, datumId}; - EmbeddedStorageContextHolder.addSqlContext(update, args); - } - - try { - boolean result = databaseOperate.update(EmbeddedStorageContextHolder.getCurrentSqlContext()); - if (!result) { - throw new NacosConfigException("[Merge] Configuration release failed"); - } - return true; - } finally { - EmbeddedStorageContextHolder.cleanAllContext(); - } - } - - @Override - public boolean batchPublishAggr(final String dataId, final String group, final String tenant, - final Map datumMap, final String appName) { - try { - Boolean isPublishOk = false; - for (Map.Entry entry : datumMap.entrySet()) { - addAggrConfigInfo(dataId, group, tenant, entry.getKey(), appName, entry.getValue()); - } - - isPublishOk = databaseOperate.update(EmbeddedStorageContextHolder.getCurrentSqlContext()); - - if (isPublishOk == null) { - return false; - } - return isPublishOk; - } finally { - EmbeddedStorageContextHolder.cleanAllContext(); - } - } - - @Override - public int aggrConfigInfoCount(String dataId, String group, String tenant) { - String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - String sql = configInfoAggrMapper.count(Arrays.asList("data_id", "group_id", "tenant_id")); - Integer result = databaseOperate.queryOne(sql, new Object[] {dataId, group, tenantTmp}, Integer.class); - if (result == null) { - throw new IllegalArgumentException("aggrConfigInfoCount error"); - } - return result; - } - - @Override - public Page findConfigInfoAggrByPage(String dataId, String group, String tenant, final int pageNo, - final int pageSize) { - String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - final int startRow = (pageNo - 1) * pageSize; - final String sqlCountRows = configInfoAggrMapper.select(Collections.singletonList("count(*)"), - Arrays.asList("data_id", "group_id", "tenant_id")); - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, group); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantTmp); - context.setStartRow(startRow); - context.setPageSize(pageSize); - MapperResult mapperResult = configInfoAggrMapper.findConfigInfoAggrByPageFetchRows(context); - String sqlFetchRows = mapperResult.getSql(); - Object[] sqlFetchArgs = mapperResult.getParamList().toArray(); - - PaginationHelper helper = createPaginationHelper(); - return helper.fetchPageLimit(sqlCountRows, new Object[] {dataId, group, tenantTmp}, sqlFetchRows, sqlFetchArgs, - pageNo, pageSize, CONFIG_INFO_AGGR_ROW_MAPPER); - } - - @Override - public List findAllAggrGroup() { - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - MapperResult mapperResult = configInfoAggrMapper.findAllAggrGroupByDistinct(null); - - return databaseOperate.queryMany(mapperResult.getSql(), EMPTY_ARRAY, CONFIG_INFO_CHANGED_ROW_MAPPER); - - } - -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoGrayPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoGrayPersistServiceImpl.java new file mode 100644 index 00000000000..87fb419f873 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoGrayPersistServiceImpl.java @@ -0,0 +1,428 @@ +/* + * Copyright 1999-2022 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.config.server.service.repository.embedded; + +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.utils.MD5Utils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; +import com.alibaba.nacos.config.server.model.ConfigOperateResult; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; +import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.config.server.service.sql.EmbeddedStorageContextUtils; +import com.alibaba.nacos.config.server.utils.ConfigExtInfoUtil; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; +import com.alibaba.nacos.persistence.configuration.condition.ConditionOnEmbeddedStorage; +import com.alibaba.nacos.persistence.datasource.DataSourceService; +import com.alibaba.nacos.persistence.datasource.DynamicDataSource; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.persistence.model.event.DerbyImportEvent; +import com.alibaba.nacos.persistence.repository.PaginationHelper; +import com.alibaba.nacos.persistence.repository.embedded.EmbeddedPaginationHelperImpl; +import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder; +import com.alibaba.nacos.persistence.repository.embedded.operate.DatabaseOperate; +import com.alibaba.nacos.plugin.datasource.MapperManager; +import com.alibaba.nacos.plugin.datasource.constants.CommonConstant; +import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; +import com.alibaba.nacos.plugin.datasource.constants.TableConstant; +import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoGrayMapper; +import com.alibaba.nacos.plugin.datasource.model.MapperContext; +import com.alibaba.nacos.plugin.datasource.model.MapperResult; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Conditional; +import org.springframework.dao.DataAccessException; +import org.springframework.stereotype.Service; + +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER; +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; +import static com.alibaba.nacos.config.server.utils.PropertyUtil.GRAY_MIGRATE_FLAG; + +/** + * EmbeddedConfigInfoGrayPersistServiceImpl. + * + * @author rong + */ +@SuppressWarnings({"PMD.MethodReturnWrapperTypeRule", "checkstyle:linelength"}) +@Conditional(value = ConditionOnEmbeddedStorage.class) +@Service("embeddedConfigInfoGrayPersistServiceImpl") +public class EmbeddedConfigInfoGrayPersistServiceImpl implements ConfigInfoGrayPersistService { + + private static final String RESOURCE_CONFIG_HISTORY_ID = "config-history-id"; + + private static final String RESOURCE_CONFIG_HISTORY_GRAY_ID = "config-history-gray-id"; + + private DataSourceService dataSourceService; + + private final DatabaseOperate databaseOperate; + + private MapperManager mapperManager; + + private final IdGeneratorManager idGeneratorManager; + + private final HistoryConfigInfoPersistService historyConfigInfoPersistService; + + /** + * The constructor sets the dependency injection order. + * + * @param databaseOperate databaseOperate. + */ + public EmbeddedConfigInfoGrayPersistServiceImpl(DatabaseOperate databaseOperate, + IdGeneratorManager idGeneratorManager, + @Qualifier("embeddedHistoryConfigInfoPersistServiceImpl") HistoryConfigInfoPersistService historyConfigInfoPersistService) { + this.databaseOperate = databaseOperate; + this.idGeneratorManager = idGeneratorManager; + this.historyConfigInfoPersistService = historyConfigInfoPersistService; + idGeneratorManager.register(RESOURCE_CONFIG_HISTORY_GRAY_ID, RESOURCE_CONFIG_HISTORY_ID); + this.dataSourceService = DynamicDataSource.getInstance().getDataSource(); + Boolean isDataSourceLogEnable = EnvUtil.getProperty(CommonConstant.NACOS_PLUGIN_DATASOURCE_LOG, Boolean.class, + false); + this.mapperManager = MapperManager.instance(isDataSourceLogEnable); + NotifyCenter.registerToSharePublisher(DerbyImportEvent.class); + } + + @Override + public PaginationHelper createPaginationHelper() { + return new EmbeddedPaginationHelperImpl<>(databaseOperate); + } + + @Override + public ConfigInfoStateWrapper findConfigInfo4GrayState(final String dataId, final String group, final String tenant, + String grayName) { + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + + String sql = configInfoGrayMapper.select( + Arrays.asList("id", "data_id", "group_id", "tenant_id", "gmt_modified"), + Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")); + return databaseOperate.queryOne(sql, new Object[] {dataId, group, tenantTmp, grayNameTmp}, + CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER); + } + + private ConfigOperateResult getGrayOperateResult(String dataId, String group, String tenant, String grayName) { + String tenantTmp = StringUtils.defaultEmptyIfBlank(tenant); + ConfigInfoStateWrapper configInfo4Gray = this.findConfigInfo4GrayState(dataId, group, tenantTmp, grayName); + if (configInfo4Gray == null) { + return new ConfigOperateResult(false); + } + return new ConfigOperateResult(configInfo4Gray.getId(), configInfo4Gray.getLastModified()); + + } + + @Override + public ConfigOperateResult addConfigInfo4Gray(ConfigInfo configInfo, String grayName, String grayRule, String srcIp, + String srcUser) { + String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + String grayRuleTmp = StringUtils.isBlank(grayRule) ? StringUtils.EMPTY : grayRule.trim(); + + configInfo.setTenant(tenantTmp); + + try { + long configGrayId = idGeneratorManager.nextId(RESOURCE_CONFIG_HISTORY_GRAY_ID); + long hisId = idGeneratorManager.nextId(RESOURCE_CONFIG_HISTORY_ID); + + addConfigInfoGrayAtomic(configGrayId, configInfo, grayNameTmp, grayRuleTmp, srcIp, srcUser); + Timestamp now = new Timestamp(System.currentTimeMillis()); + + if (!GRAY_MIGRATE_FLAG.get()) { + historyConfigInfoPersistService.insertConfigHistoryAtomic(hisId, configInfo, srcIp, srcUser, now, "I", + Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(grayNameTmp, grayRuleTmp, srcUser)); + } + + EmbeddedStorageContextUtils.onModifyConfigGrayInfo(configInfo, grayNameTmp, grayRuleTmp, srcIp, now); + databaseOperate.blockUpdate(); + return getGrayOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp); + } finally { + EmbeddedStorageContextHolder.cleanAllContext(); + } + } + + @Override + public void addConfigInfoGrayAtomic(long configGrayId, ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser) { + String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); + String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + String grayRuleTmp = StringUtils.isBlank(grayRule) ? StringUtils.EMPTY : grayRule.trim(); + String md5 = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); + + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + + final String sql = configInfoGrayMapper.insert( + Arrays.asList("id", "data_id", "group_id", "tenant_id", "gray_name", "gray_rule", "app_name", "content", + "md5", "src_ip", "src_user", "gmt_create", "gmt_modified")); + + Timestamp time = new Timestamp(System.currentTimeMillis()); + final Object[] args = new Object[] {configGrayId, configInfo.getDataId(), configInfo.getGroup(), tenantTmp, + grayNameTmp, grayRuleTmp, appNameTmp, configInfo.getContent(), md5, srcIp, srcUser, time, time}; + EmbeddedStorageContextHolder.addSqlContext(sql, args); + } + + @Override + public ConfigOperateResult insertOrUpdateGray(final ConfigInfo configInfo, final String grayName, + final String grayRule, final String srcIp, final String srcUser) { + if (findConfigInfo4GrayState(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), grayName) + == null) { + return addConfigInfo4Gray(configInfo, grayName, grayRule, srcIp, srcUser); + } else { + return updateConfigInfo4Gray(configInfo, grayName, grayRule, srcIp, srcUser); + } + } + + @Override + public ConfigOperateResult insertOrUpdateGrayCas(final ConfigInfo configInfo, final String grayName, + final String grayRule, final String srcIp, final String srcUser) { + if (findConfigInfo4GrayState(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), grayName) + == null) { + return addConfigInfo4Gray(configInfo, grayName, grayRule, srcIp, srcUser); + } else { + return updateConfigInfo4GrayCas(configInfo, grayName, grayRule, srcIp, srcUser); + } + } + + @Override + public void removeConfigInfoGray(final String dataId, final String group, final String tenant, + final String grayName, final String srcIp, final String srcUser) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName; + + ConfigInfoGrayWrapper oldConfigAllInfo4Gray = findConfigInfo4Gray(dataId, group, tenantTmp, grayNameTmp); + if (oldConfigAllInfo4Gray == null) { + if (LogUtil.FATAL_LOG.isErrorEnabled()) { + LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", dataId, + group, tenant); + } + } + + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + final String sql = configInfoGrayMapper.delete(Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")); + final Object[] args = new Object[] {dataId, group, tenantTmp, grayNameTmp}; + + Timestamp now = new Timestamp(System.currentTimeMillis()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo4Gray.getId(), oldConfigAllInfo4Gray, + srcIp, srcUser, now, "D", Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(oldConfigAllInfo4Gray.getGrayName(), + oldConfigAllInfo4Gray.getGrayRule(), oldConfigAllInfo4Gray.getSrcUser())); + + EmbeddedStorageContextUtils.onDeleteConfigGrayInfo(tenantTmp, group, dataId, grayNameTmp, srcIp); + EmbeddedStorageContextHolder.addSqlContext(sql, args); + try { + databaseOperate.update(EmbeddedStorageContextHolder.getCurrentSqlContext()); + } finally { + EmbeddedStorageContextHolder.cleanAllContext(); + } + } + + @Override + public ConfigOperateResult updateConfigInfo4Gray(ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser) { + String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); + String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + String grayRuleTmp = StringUtils.isBlank(grayRule) ? StringUtils.EMPTY : grayRule.trim(); + + configInfo.setTenant(tenantTmp); + + try { + ConfigInfoGrayWrapper oldConfigAllInfo4Gray = findConfigInfo4Gray(configInfo.getDataId(), + configInfo.getGroup(), tenantTmp, grayNameTmp); + if (oldConfigAllInfo4Gray == null) { + if (LogUtil.FATAL_LOG.isErrorEnabled()) { + LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", + configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + } + } + + String md5 = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + Timestamp time = new Timestamp(System.currentTimeMillis()); + + final String sql = configInfoGrayMapper.update( + Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified", "app_name", "gray_rule"), + Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")); + final Object[] args = new Object[] {configInfo.getContent(), md5, srcIp, srcUser, time, appNameTmp, + grayRuleTmp, configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp}; + if (!GRAY_MIGRATE_FLAG.get()) { + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo4Gray.getId(), + oldConfigAllInfo4Gray, srcIp, srcUser, time, "U", Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(oldConfigAllInfo4Gray.getGrayName(), + oldConfigAllInfo4Gray.getGrayRule(), oldConfigAllInfo4Gray.getSrcUser())); + } + EmbeddedStorageContextUtils.onModifyConfigGrayInfo(configInfo, grayNameTmp, grayRuleTmp, srcIp, time); + EmbeddedStorageContextHolder.addSqlContext(sql, args); + + databaseOperate.blockUpdate(); + return getGrayOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp); + + } finally { + EmbeddedStorageContextHolder.cleanAllContext(); + } + } + + @Override + public ConfigOperateResult updateConfigInfo4GrayCas(ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser) { + String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); + String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + String grayRuleTmp = StringUtils.isBlank(grayRule) ? StringUtils.EMPTY : grayRule.trim(); + + configInfo.setTenant(tenantTmp); + + try { + final ConfigInfoGrayWrapper oldConfigAllInfo4Gray = findConfigInfo4Gray(configInfo.getDataId(), + configInfo.getGroup(), tenantTmp, grayNameTmp); + if (oldConfigAllInfo4Gray == null) { + if (LogUtil.FATAL_LOG.isErrorEnabled()) { + LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", + configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + } + } + + String md5 = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + Timestamp time = new Timestamp(System.currentTimeMillis()); + + MapperContext context = new MapperContext(); + context.putUpdateParameter(FieldConstant.CONTENT, configInfo.getContent()); + context.putUpdateParameter(FieldConstant.MD5, md5); + context.putUpdateParameter(FieldConstant.SRC_IP, srcIp); + context.putUpdateParameter(FieldConstant.SRC_USER, srcUser); + context.putUpdateParameter(FieldConstant.GMT_MODIFIED, time); + context.putUpdateParameter(FieldConstant.APP_NAME, appNameTmp); + + context.putWhereParameter(FieldConstant.DATA_ID, configInfo.getDataId()); + context.putWhereParameter(FieldConstant.GROUP_ID, configInfo.getGroup()); + context.putWhereParameter(FieldConstant.TENANT_ID, tenantTmp); + context.putWhereParameter(FieldConstant.GRAY_NAME, grayNameTmp); + context.putWhereParameter(FieldConstant.GRAY_RULE, grayRuleTmp); + context.putWhereParameter(FieldConstant.MD5, configInfo.getMd5()); + + final MapperResult mapperResult = configInfoGrayMapper.updateConfigInfo4GrayCas(context); + + Timestamp now = new Timestamp(System.currentTimeMillis()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo4Gray.getId(), + oldConfigAllInfo4Gray, srcIp, srcUser, now, "U", Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(oldConfigAllInfo4Gray.getGrayName(), + oldConfigAllInfo4Gray.getGrayRule(), oldConfigAllInfo4Gray.getSrcUser())); + + EmbeddedStorageContextUtils.onModifyConfigGrayInfo(configInfo, grayNameTmp, grayRuleTmp, srcIp, time); + EmbeddedStorageContextHolder.addSqlContext(mapperResult.getSql(), mapperResult.getParamList().toArray()); + + Boolean success = databaseOperate.blockUpdate(); + if (success) { + return getGrayOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp); + } else { + return new ConfigOperateResult(false); + } + + } finally { + EmbeddedStorageContextHolder.cleanAllContext(); + } + } + + @Override + public ConfigInfoGrayWrapper findConfigInfo4Gray(final String dataId, final String group, final String tenant, + final String grayName) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + final String sql = configInfoGrayMapper.select( + Arrays.asList("id", "data_id", "group_id", "tenant_id", "gray_name", "gray_rule", "app_name", "content", + "md5", "gmt_modified", "src_user", "encrypted_data_key"), + Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")); + + return databaseOperate.queryOne(sql, new Object[] {dataId, group, tenantTmp, grayNameTmp}, + CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER); + } + + @Override + public int configInfoGrayCount() { + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + String sql = configInfoGrayMapper.count(null); + Integer result = databaseOperate.queryOne(sql, Integer.class); + if (result == null) { + throw new IllegalArgumentException("configInfoBetaCount error"); + } + return result; + } + + @Override + public Page findAllConfigInfoGrayForDumpAll(final int pageNo, final int pageSize) { + final int startRow = (pageNo - 1) * pageSize; + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + String sqlCountRows = configInfoGrayMapper.count(null); + MapperResult sqlFetchRows = configInfoGrayMapper.findAllConfigInfoGrayForDumpAllFetchRows( + new MapperContext(startRow, pageSize)); + + PaginationHelper helper = createPaginationHelper(); + return helper.fetchPageLimit(sqlCountRows, sqlFetchRows.getSql(), sqlFetchRows.getParamList().toArray(), pageNo, + pageSize, CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER); + + } + + @Override + public List findConfigInfoGrays(String dataId, String group, String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + final String sql = configInfoGrayMapper.select(Collections.singletonList("gray_name"), + Arrays.asList("data_id", "group_id", "tenant_id")); + + return databaseOperate.queryMany(sql, new Object[] {dataId, group, tenantTmp}, String.class); + } + + @Override + public List findChangeConfig(final Timestamp startTime, long lastMaxId, final int pageSize) { + try { + ConfigInfoGrayMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + + MapperContext context = new MapperContext(); + context.putWhereParameter(FieldConstant.START_TIME, startTime); + context.putWhereParameter(FieldConstant.PAGE_SIZE, pageSize); + context.putWhereParameter(FieldConstant.LAST_MAX_ID, lastMaxId); + + MapperResult mapperResult = configInfoMapper.findChangeConfig(context); + return databaseOperate.queryMany(mapperResult.getSql(), mapperResult.getParamList().toArray(), + CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER); + } catch (DataAccessException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImpl.java index d547771721d..2b379cd269d 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImpl.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImpl.java @@ -37,6 +37,8 @@ import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.config.server.service.sql.EmbeddedStorageContextUtils; +import com.alibaba.nacos.config.server.utils.ConfigExtInfoUtil; +import com.alibaba.nacos.config.server.utils.LogUtil; import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; import com.alibaba.nacos.persistence.configuration.condition.ConditionOnEmbeddedStorage; @@ -217,9 +219,12 @@ private ConfigOperateResult addConfigInfo(final String srcIp, final String srcUs addConfigTagsRelation(configId, configTags, configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); - + Timestamp now = new Timestamp(System.currentTimeMillis()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(hisId, configInfo, srcIp, srcUser, now, "I"); + historyConfigInfoPersistService.insertConfigHistoryAtomic(hisId, configInfo, srcIp, srcUser, now, "I", + Constants.FORMAL, null, + ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser)); + EmbeddedStorageContextUtils.onModifyConfigInfo(configInfo, srcIp, now); databaseOperate.blockUpdate(consumer); return getConfigInfoOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp); @@ -266,12 +271,12 @@ public long addConfigInfoAtomic(final long id, final String srcIp, final String configInfo.getEncryptedDataKey() == null ? StringUtils.EMPTY : configInfo.getEncryptedDataKey(); ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO); - + final String sql = configInfoMapper.insert( Arrays.asList("id", "data_id", "group_id", "tenant_id", "app_name", "content", "md5", "src_ip", - "src_user", "gmt_create@NOW()", "gmt_modified@NOW()", "c_desc", "c_use", "effect", - "type", "c_schema", "encrypted_data_key")); - final Object[] args = new Object[]{id, configInfo.getDataId(), configInfo.getGroup(), tenantTmp, appNameTmp, + "src_user", "gmt_create@NOW()", "gmt_modified@NOW()", "c_desc", "c_use", "effect", "type", + "c_schema", "encrypted_data_key")); + final Object[] args = new Object[] {id, configInfo.getDataId(), configInfo.getGroup(), tenantTmp, appNameTmp, configInfo.getContent(), md5Tmp, srcIp, srcUser, desc, use, effect, type, schema, encryptedDataKey}; EmbeddedStorageContextHolder.addSqlContext(sql, args); return id; @@ -400,15 +405,16 @@ public Map batchInsertOrUpdate(List configInfoLis public void removeConfigInfo(final String dataId, final String group, final String tenant, final String srcIp, final String srcUser) { final Timestamp time = new Timestamp(System.currentTimeMillis()); - ConfigInfo configInfo = findConfigInfo(dataId, group, tenant); - if (Objects.nonNull(configInfo)) { + ConfigAllInfo oldConfigAllInfo = findConfigAllInfo(dataId, group, tenant); + if (Objects.nonNull(oldConfigAllInfo)) { try { String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; removeConfigInfoAtomic(dataId, group, tenantTmp, srcIp, srcUser); - removeTagByIdAtomic(configInfo.getId()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(configInfo.getId(), configInfo, srcIp, - srcUser, time, "D"); + removeTagByIdAtomic(oldConfigAllInfo.getId()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo.getId(), oldConfigAllInfo, + srcIp, srcUser, time, "D", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(oldConfigAllInfo)); EmbeddedStorageContextUtils.onDeleteConfigInfo(tenantTmp, group, dataId, srcIp, time); @@ -423,7 +429,7 @@ public void removeConfigInfo(final String dataId, final String group, final Stri } @Override - public List removeConfigInfoByIds(final List ids, final String srcIp, final String srcUser) { + public List removeConfigInfoByIds(final List ids, final String srcIp, final String srcUser) { if (CollectionUtils.isEmpty(ids)) { return null; } @@ -431,23 +437,24 @@ public List removeConfigInfoByIds(final List ids, final String final Timestamp time = new Timestamp(System.currentTimeMillis()); try { String idsStr = StringUtils.join(ids, StringUtils.COMMA); - List configInfoList = findConfigInfosByIds(idsStr); - if (CollectionUtils.isNotEmpty(configInfoList)) { + List oldConfigAllInfoList = findAllConfigInfo4Export(null, null, null, null, ids); + if (CollectionUtils.isNotEmpty(oldConfigAllInfoList)) { removeConfigInfoByIdsAtomic(idsStr); - for (ConfigInfo configInfo : configInfoList) { - removeTagByIdAtomic(configInfo.getId()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(configInfo.getId(), configInfo, srcIp, - srcUser, time, "D"); + for (ConfigAllInfo configAllInfo : oldConfigAllInfoList) { + removeTagByIdAtomic(configAllInfo.getId()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(configAllInfo.getId(), configAllInfo, + srcIp, srcUser, time, "D", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo)); } } - EmbeddedStorageContextUtils.onBatchDeleteConfigInfo(configInfoList); + EmbeddedStorageContextUtils.onBatchDeleteConfigInfo(oldConfigAllInfoList); boolean result = databaseOperate.update(EmbeddedStorageContextHolder.getCurrentSqlContext()); if (!result) { throw new NacosConfigException("Failed to config batch deletion"); } - return configInfoList; + return oldConfigAllInfoList; } finally { EmbeddedStorageContextHolder.cleanAllContext(); } @@ -496,15 +503,22 @@ public void removeConfigInfoByIdsAtomic(final String ids) { public ConfigOperateResult updateConfigInfo(final ConfigInfo configInfo, final String srcIp, final String srcUser, final Map configAdvanceInfo) { try { - ConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), + ConfigAllInfo oldConfigAllInfo = findConfigAllInfo(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + if (oldConfigAllInfo == null) { + if (LogUtil.FATAL_LOG.isErrorEnabled()) { + LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", + configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + } + return new ConfigOperateResult(false); + } final String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); - oldConfigInfo.setTenant(tenantTmp); + oldConfigAllInfo.setTenant(tenantTmp); - String appNameTmp = oldConfigInfo.getAppName(); + String appNameTmp = oldConfigAllInfo.getAppName(); // If the appName passed by the user is not empty, the appName of the user is persisted; // otherwise, the appName of db is used. Empty string is required to clear appName if (configInfo.getAppName() == null) { @@ -516,14 +530,15 @@ public ConfigOperateResult updateConfigInfo(final ConfigInfo configInfo, final S String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); if (configTags != null) { // Delete all tags and recreate them - removeTagByIdAtomic(oldConfigInfo.getId()); - addConfigTagsRelation(oldConfigInfo.getId(), configTags, configInfo.getDataId(), configInfo.getGroup(), - configInfo.getTenant()); + removeTagByIdAtomic(oldConfigAllInfo.getId()); + addConfigTagsRelation(oldConfigAllInfo.getId(), configTags, configInfo.getDataId(), + configInfo.getGroup(), configInfo.getTenant()); } Timestamp time = new Timestamp(System.currentTimeMillis()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigInfo.getId(), oldConfigInfo, srcIp, - srcUser, time, "U"); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo.getId(), oldConfigAllInfo, srcIp, + srcUser, time, "U", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(oldConfigAllInfo)); EmbeddedStorageContextUtils.onModifyConfigInfo(configInfo, srcIp, time); databaseOperate.blockUpdate(); return getConfigInfoOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp); @@ -536,15 +551,21 @@ public ConfigOperateResult updateConfigInfo(final ConfigInfo configInfo, final S public ConfigOperateResult updateConfigInfoCas(final ConfigInfo configInfo, final String srcIp, final String srcUser, final Map configAdvanceInfo) { try { - ConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), + ConfigAllInfo oldConfigAllInfo = findConfigAllInfo(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); - + if (oldConfigAllInfo == null) { + if (LogUtil.FATAL_LOG.isErrorEnabled()) { + LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", + configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + } + return new ConfigOperateResult(false); + } final String tenantTmp = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); - oldConfigInfo.setTenant(tenantTmp); + oldConfigAllInfo.setTenant(tenantTmp); - String appNameTmp = oldConfigInfo.getAppName(); + String appNameTmp = oldConfigAllInfo.getAppName(); // If the appName passed by the user is not empty, the appName of the user is persisted; // otherwise, the appName of db is used. Empty string is required to clear appName if (configInfo.getAppName() == null) { @@ -556,14 +577,15 @@ public ConfigOperateResult updateConfigInfoCas(final ConfigInfo configInfo, fina String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); if (configTags != null) { // Delete all tags and recreate them - removeTagByIdAtomic(oldConfigInfo.getId()); - addConfigTagsRelation(oldConfigInfo.getId(), configTags, configInfo.getDataId(), configInfo.getGroup(), - configInfo.getTenant()); + removeTagByIdAtomic(oldConfigAllInfo.getId()); + addConfigTagsRelation(oldConfigAllInfo.getId(), configTags, configInfo.getDataId(), + configInfo.getGroup(), configInfo.getTenant()); } - + Timestamp time = new Timestamp(System.currentTimeMillis()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigInfo.getId(), oldConfigInfo, srcIp, - srcUser, time, "U"); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo.getId(), oldConfigAllInfo, srcIp, + srcUser, time, "U", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(oldConfigAllInfo)); EmbeddedStorageContextUtils.onModifyConfigInfo(configInfo, srcIp, time); boolean success = databaseOperate.blockUpdate(); if (success) { @@ -608,7 +630,8 @@ private ConfigOperateResult updateConfigInfoAtomicCas(final ConfigInfo configInf context.putWhereParameter(FieldConstant.MD5, configInfo.getMd5()); MapperResult mapperResult = configInfoMapper.updateConfigInfoAtomicCas(context); - EmbeddedStorageContextHolder.addSqlContext(Boolean.TRUE, mapperResult.getSql(), mapperResult.getParamList().toArray()); + EmbeddedStorageContextHolder.addSqlContext(Boolean.TRUE, mapperResult.getSql(), + mapperResult.getParamList().toArray()); return getConfigInfoOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp); } @@ -626,17 +649,17 @@ public void updateConfigInfoAtomic(final ConfigInfo configInfo, final String src final String schema = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("schema"); final String encryptedDataKey = configInfo.getEncryptedDataKey() == null ? StringUtils.EMPTY : configInfo.getEncryptedDataKey(); - + ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO); final String sql = configInfoMapper.update( - Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified@NOW()", "app_name", - "c_desc", "c_use", "effect", "type", "c_schema", "encrypted_data_key"), + Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified@NOW()", "app_name", "c_desc", + "c_use", "effect", "type", "c_schema", "encrypted_data_key"), Arrays.asList("data_id", "group_id", "tenant_id")); - - final Object[] args = new Object[]{configInfo.getContent(), md5Tmp, srcIp, srcUser, appNameTmp, desc, - use, effect, type, schema, encryptedDataKey, configInfo.getDataId(), configInfo.getGroup(), tenantTmp}; - + + final Object[] args = new Object[] {configInfo.getContent(), md5Tmp, srcIp, srcUser, appNameTmp, desc, use, + effect, type, schema, encryptedDataKey, configInfo.getDataId(), configInfo.getGroup(), tenantTmp}; + EmbeddedStorageContextHolder.addSqlContext(sql, args); } @@ -802,7 +825,8 @@ public Page findConfigInfoLike4Page(final int pageNo, final int page String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); final String content = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("content"); - final String types = Optional.ofNullable(configAdvanceInfo).map(e -> (String) e.get(ParametersField.TYPES)).orElse(null); + final String types = Optional.ofNullable(configAdvanceInfo).map(e -> (String) e.get(ParametersField.TYPES)) + .orElse(null); final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); MapperResult sqlCountRows; MapperResult sqlFetchRows; diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImpl.java index 4bfdf8ec65b..aac9549a373 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImpl.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImpl.java @@ -45,11 +45,11 @@ import org.springframework.stereotype.Service; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_DETAIL_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_LIST_ROW_MAPPER; @@ -90,19 +90,23 @@ public PaginationHelper createPaginationHelper() { @Override public void insertConfigHistoryAtomic(long configHistoryId, ConfigInfo configInfo, String srcIp, String srcUser, - final Timestamp time, String ops) { + final Timestamp time, String ops, String publishType, String grayName, String extInfo) { String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); final String md5Tmp = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); + String publishTypeTmp = StringUtils.defaultEmptyIfBlank(publishType); String encryptedDataKey = StringUtils.defaultEmptyIfBlank(configInfo.getEncryptedDataKey()); + String grayNameTemp = StringUtils.defaultEmptyIfBlank(grayName); HistoryConfigInfoMapper historyConfigInfoMapper = mapperManager.findMapper( dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); final String sql = historyConfigInfoMapper.insert( Arrays.asList("id", "data_id", "group_id", "tenant_id", "app_name", "content", "md5", "src_ip", - "src_user", "gmt_modified", "op_type", "encrypted_data_key")); + "src_user", "gmt_modified", "op_type", "publish_type", "gray_name", "ext_info", + "encrypted_data_key")); final Object[] args = new Object[] {configHistoryId, configInfo.getDataId(), configInfo.getGroup(), tenantTmp, - appNameTmp, configInfo.getContent(), md5Tmp, srcIp, srcUser, time, ops, encryptedDataKey}; + appNameTmp, configInfo.getContent(), md5Tmp, srcIp, srcUser, time, ops, publishTypeTmp, grayNameTemp, + extInfo, encryptedDataKey}; EmbeddedStorageContextHolder.addSqlContext(sql, args); } @@ -120,18 +124,33 @@ public void removeConfigHistory(final Timestamp startTime, final int limitSize) } @Override - public List findDeletedConfig(final Timestamp startTime, long lastMaxId, - final int pageSize) { + public List findDeletedConfig(final Timestamp startTime, long lastMaxId, final int pageSize, + String publishType) { HistoryConfigInfoMapper historyConfigInfoMapper = mapperManager.findMapper( dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); MapperContext context = new MapperContext(); context.putWhereParameter(FieldConstant.START_TIME, startTime); context.putWhereParameter(FieldConstant.PAGE_SIZE, pageSize); context.putWhereParameter(FieldConstant.LAST_MAX_ID, lastMaxId); + context.putWhereParameter(FieldConstant.PUBLISH_TYPE, publishType); MapperResult mapperResult = historyConfigInfoMapper.findDeletedConfig(context); - return databaseOperate.queryMany(mapperResult.getSql(), mapperResult.getParamList().toArray(), - CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER); + List configHistoryInfos = databaseOperate.queryMany(mapperResult.getSql(), + mapperResult.getParamList().toArray(), HISTORY_DETAIL_ROW_MAPPER); + + List configInfoStateWrappers = new ArrayList<>(); + for (ConfigHistoryInfo configHistoryInfo : configHistoryInfos) { + ConfigInfoStateWrapper configInfoStateWrapper = new ConfigInfoStateWrapper(); + configInfoStateWrapper.setId(configHistoryInfo.getId()); + configInfoStateWrapper.setDataId(configHistoryInfo.getDataId()); + configInfoStateWrapper.setGroup(configHistoryInfo.getGroup()); + configInfoStateWrapper.setTenant(configHistoryInfo.getTenant()); + configInfoStateWrapper.setMd5(configHistoryInfo.getMd5()); + configInfoStateWrapper.setLastModified(configHistoryInfo.getLastModifiedTime().getTime()); + configInfoStateWrapper.setGrayName(configHistoryInfo.getGrayName()); + configInfoStateWrappers.add(configInfoStateWrapper); + } + return configInfoStateWrappers; } @Override @@ -161,8 +180,8 @@ public ConfigHistoryInfo detailConfigHistory(Long nid) { dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); String sqlFetchRows = historyConfigInfoMapper.select( Arrays.asList("nid", "data_id", "group_id", "tenant_id", "app_name", "content", "md5", "src_user", - "src_ip", "op_type", "gmt_create", "gmt_modified", "encrypted_data_key"), - Collections.singletonList("nid")); + "src_ip", "op_type", "publish_type", "gray_name", "ext_info", "gmt_create", "gmt_modified", + "encrypted_data_key"), Collections.singletonList("nid")); return databaseOperate.queryOne(sqlFetchRows, new Object[] {nid}, HISTORY_DETAIL_ROW_MAPPER); } @@ -191,4 +210,22 @@ public int findConfigHistoryCountByTime(final Timestamp startTime) { } return result; } + + @Override + public ConfigHistoryInfo getNextHistoryInfo(String dataId, String group, String tenant, String publishType, + String grayName, long startNid) { + HistoryConfigInfoMapper historyConfigInfoMapper = mapperManager.findMapper( + dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); + MapperContext context = new MapperContext(); + context.putWhereParameter(FieldConstant.DATA_ID, dataId); + context.putWhereParameter(FieldConstant.GROUP_ID, group); + context.putWhereParameter(FieldConstant.TENANT_ID, tenant); + context.putWhereParameter(FieldConstant.PUBLISH_TYPE, publishType); + context.putWhereParameter(FieldConstant.GRAY_NAME, grayName); + context.putWhereParameter(FieldConstant.NID, startNid); + MapperResult sqlFetchRows = historyConfigInfoMapper.getNextHistoryInfo(context); + return databaseOperate.queryOne(sqlFetchRows.getSql(), sqlFetchRows.getParamList().toArray(), + HISTORY_DETAIL_ROW_MAPPER); + } + } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoAggrPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoAggrPersistServiceImpl.java deleted file mode 100644 index 03c7d093e1c..00000000000 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoAggrPersistServiceImpl.java +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 1999-2022 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.config.server.service.repository.extrnal; - -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.utils.LogUtil; -import com.alibaba.nacos.persistence.configuration.condition.ConditionOnExternalStorage; -import com.alibaba.nacos.persistence.datasource.DataSourceService; -import com.alibaba.nacos.persistence.datasource.DynamicDataSource; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.persistence.repository.PaginationHelper; -import com.alibaba.nacos.persistence.repository.extrnal.ExternalStoragePaginationHelperImpl; -import com.alibaba.nacos.plugin.datasource.MapperManager; -import com.alibaba.nacos.plugin.datasource.constants.CommonConstant; -import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; -import com.alibaba.nacos.plugin.datasource.constants.TableConstant; -import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoAggrMapper; -import com.alibaba.nacos.plugin.datasource.model.MapperContext; -import com.alibaba.nacos.plugin.datasource.model.MapperResult; -import com.alibaba.nacos.sys.env.EnvUtil; -import org.springframework.context.annotation.Conditional; -import org.springframework.dao.DataAccessException; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.CannotGetJdbcConnectionException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Service; -import org.springframework.transaction.TransactionException; -import org.springframework.transaction.TransactionSystemException; -import org.springframework.transaction.support.TransactionTemplate; - -import java.sql.Timestamp; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_AGGR_ROW_MAPPER; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_CHANGED_ROW_MAPPER; - -/** - * ExternalConfigInfoAggrPersistServiceImpl. - * - * @author lixiaoshuang - */ -@SuppressWarnings(value = {"PMD.MethodReturnWrapperTypeRule", "checkstyle:linelength"}) -@Conditional(value = ConditionOnExternalStorage.class) -@Service("externalConfigInfoAggrPersistServiceImpl") -public class ExternalConfigInfoAggrPersistServiceImpl implements ConfigInfoAggrPersistService { - - private DataSourceService dataSourceService; - - protected JdbcTemplate jt; - - protected TransactionTemplate tjt; - - private MapperManager mapperManager; - - public ExternalConfigInfoAggrPersistServiceImpl() { - this.dataSourceService = DynamicDataSource.getInstance().getDataSource(); - this.jt = dataSourceService.getJdbcTemplate(); - this.tjt = dataSourceService.getTransactionTemplate(); - Boolean isDataSourceLogEnable = EnvUtil.getProperty(CommonConstant.NACOS_PLUGIN_DATASOURCE_LOG, Boolean.class, - false); - this.mapperManager = MapperManager.instance(isDataSourceLogEnable); - } - - @Override - public PaginationHelper createPaginationHelper() { - return new ExternalStoragePaginationHelperImpl<>(jt); - } - - @Override - public boolean addAggrConfigInfo(final String dataId, final String group, String tenant, final String datumId, - String appName, final String content) { - String appNameTmp = StringUtils.isBlank(appName) ? StringUtils.EMPTY : appName; - String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; - final Timestamp now = new Timestamp(System.currentTimeMillis()); - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - String select = configInfoAggrMapper.select(Collections.singletonList("content"), - Arrays.asList("data_id", "group_id", "tenant_id", "datum_id")); - String insert = configInfoAggrMapper.insert( - Arrays.asList("data_id", "group_id", "tenant_id", "datum_id", "app_name", "content", "gmt_modified")); - String update = configInfoAggrMapper.update(Arrays.asList("content", "gmt_modified"), - Arrays.asList("data_id", "group_id", "tenant_id", "datum_id")); - - try { - try { - String dbContent = jt.queryForObject(select, new Object[] {dataId, group, tenantTmp, datumId}, - String.class); - - if (dbContent != null && dbContent.equals(content)) { - return true; - } else { - return jt.update(update, content, now, dataId, group, tenantTmp, datumId) > 0; - } - } catch (EmptyResultDataAccessException ex) { // no data, insert - return jt.update(insert, dataId, group, tenantTmp, datumId, appNameTmp, content, now) > 0; - } - } catch (DataAccessException e) { - LogUtil.FATAL_LOG.error("[db-error] " + e, e); - throw e; - } - } - - @Override - public boolean batchPublishAggr(final String dataId, final String group, final String tenant, - final Map datumMap, final String appName) { - try { - Boolean isPublishOk = tjt.execute(status -> { - for (Map.Entry entry : datumMap.entrySet()) { - try { - if (!addAggrConfigInfo(dataId, group, tenant, entry.getKey(), appName, entry.getValue())) { - throw new TransactionSystemException("error in batchPublishAggr"); - } - } catch (Throwable e) { - throw new TransactionSystemException("error in batchPublishAggr"); - } - } - return Boolean.TRUE; - }); - if (isPublishOk == null) { - return false; - } - return isPublishOk; - } catch (TransactionException e) { - LogUtil.FATAL_LOG.error("[db-error] " + e, e); - return false; - } - } - - @Override - public int aggrConfigInfoCount(String dataId, String group, String tenant) { - String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - String sql = configInfoAggrMapper.count(Arrays.asList("data_id", "group_id", "tenant_id")); - Integer result = jt.queryForObject(sql, Integer.class, new Object[] {dataId, group, tenantTmp}); - return result.intValue(); - } - - @Override - public Page findConfigInfoAggrByPage(String dataId, String group, String tenant, final int pageNo, - final int pageSize) { - String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - final int startRow = (pageNo - 1) * pageSize; - String sqlCountRows = configInfoAggrMapper.select(Arrays.asList("count(*)"), - Arrays.asList("data_id", "group_id", "tenant_id")); - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, group); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantTmp); - context.setStartRow(startRow); - context.setPageSize(pageSize); - - MapperResult mapperResult = configInfoAggrMapper.findConfigInfoAggrByPageFetchRows(context); - String sqlFetchRows = mapperResult.getSql(); - Object[] sqlFetchArgs = mapperResult.getParamList().toArray(); - - PaginationHelper helper = this.createPaginationHelper(); - try { - return helper.fetchPageLimit(sqlCountRows, new Object[] {dataId, group, tenantTmp}, sqlFetchRows, - sqlFetchArgs, pageNo, pageSize, CONFIG_INFO_AGGR_ROW_MAPPER); - - } catch (CannotGetJdbcConnectionException e) { - LogUtil.FATAL_LOG.error("[db-error] " + e, e); - throw e; - } - } - - @Override - public List findAllAggrGroup() { - ConfigInfoAggrMapper configInfoAggrMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO_AGGR); - MapperResult mapperResult = configInfoAggrMapper.findAllAggrGroupByDistinct(null); - - try { - return jt.query(mapperResult.getSql(), mapperResult.getParamList().toArray(), - CONFIG_INFO_CHANGED_ROW_MAPPER); - } catch (CannotGetJdbcConnectionException e) { - LogUtil.FATAL_LOG.error("[db-error] " + e, e); - throw e; - } catch (EmptyResultDataAccessException e) { - return null; - } catch (Exception e) { - LogUtil.FATAL_LOG.error("[db-other-error]" + e.getMessage(), e); - throw new RuntimeException(e); - } - } - -} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoGrayPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoGrayPersistServiceImpl.java new file mode 100644 index 00000000000..5ee4ce2dcf8 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoGrayPersistServiceImpl.java @@ -0,0 +1,410 @@ +/* + * Copyright 1999-2022 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.config.server.service.repository.extrnal; + +import com.alibaba.nacos.common.utils.MD5Utils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; +import com.alibaba.nacos.config.server.model.ConfigOperateResult; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; +import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.config.server.utils.ConfigExtInfoUtil; +import com.alibaba.nacos.config.server.utils.LogUtil; +import com.alibaba.nacos.persistence.configuration.condition.ConditionOnExternalStorage; +import com.alibaba.nacos.persistence.datasource.DataSourceService; +import com.alibaba.nacos.persistence.datasource.DynamicDataSource; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.persistence.repository.PaginationHelper; +import com.alibaba.nacos.persistence.repository.extrnal.ExternalStoragePaginationHelperImpl; +import com.alibaba.nacos.plugin.datasource.MapperManager; +import com.alibaba.nacos.plugin.datasource.constants.CommonConstant; +import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; +import com.alibaba.nacos.plugin.datasource.constants.TableConstant; +import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoGrayMapper; +import com.alibaba.nacos.plugin.datasource.model.MapperContext; +import com.alibaba.nacos.plugin.datasource.model.MapperResult; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.annotation.Conditional; +import org.springframework.dao.DataAccessException; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.TransactionStatus; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionTemplate; + +import java.sql.Timestamp; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER; +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; +import static com.alibaba.nacos.config.server.utils.PropertyUtil.GRAY_MIGRATE_FLAG; + + +/** + * ExternalConfigInfoGrayPersistServiceImpl. + * + * @author rong + */ +@SuppressWarnings(value = {"PMD.MethodReturnWrapperTypeRule", "checkstyle:linelength"}) +@Conditional(value = ConditionOnExternalStorage.class) +@Service("externalConfigInfoGrayPersistServiceImpl") +public class ExternalConfigInfoGrayPersistServiceImpl implements ConfigInfoGrayPersistService { + + private DataSourceService dataSourceService; + + protected JdbcTemplate jt; + + protected TransactionTemplate tjt; + + private MapperManager mapperManager; + + private HistoryConfigInfoPersistService historyConfigInfoPersistService; + + public ExternalConfigInfoGrayPersistServiceImpl( + @Qualifier("externalHistoryConfigInfoPersistServiceImpl") HistoryConfigInfoPersistService historyConfigInfoPersistService) { + this.historyConfigInfoPersistService = historyConfigInfoPersistService; + this.dataSourceService = DynamicDataSource.getInstance().getDataSource(); + this.jt = dataSourceService.getJdbcTemplate(); + this.tjt = dataSourceService.getTransactionTemplate(); + Boolean isDataSourceLogEnable = EnvUtil.getProperty(CommonConstant.NACOS_PLUGIN_DATASOURCE_LOG, Boolean.class, + false); + this.mapperManager = MapperManager.instance(isDataSourceLogEnable); + } + + @Override + public PaginationHelper createPaginationHelper() { + return new ExternalStoragePaginationHelperImpl<>(jt); + } + + @Override + public ConfigInfoStateWrapper findConfigInfo4GrayState(final String dataId, final String group, final String tenant, + String grayName) { + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + try { + return this.jt.queryForObject(configInfoGrayMapper.select( + Arrays.asList("id", "data_id", "group_id", "tenant_id", "gray_rule", "gmt_modified"), + Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")), + new Object[] {dataId, group, tenantTmp, grayNameTmp}, CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { + return null; + } + } + + private ConfigOperateResult getGrayOperateResult(String dataId, String group, String tenant, String grayName) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + + ConfigInfoStateWrapper configInfo4Gray = this.findConfigInfo4GrayState(dataId, group, tenantTmp, grayName); + if (configInfo4Gray == null) { + return new ConfigOperateResult(false); + } + return new ConfigOperateResult(configInfo4Gray.getId(), configInfo4Gray.getLastModified()); + + } + + @Override + public ConfigOperateResult addConfigInfo4Gray(ConfigInfo configInfo, String grayName, String grayRule, String srcIp, + String srcUser) { + return tjt.execute(status -> { + String tenantTmp = + StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant().trim(); + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + String grayRuleTmp = StringUtils.isBlank(grayRule) ? StringUtils.EMPTY : grayRule.trim(); + try { + addConfigInfoGrayAtomic(-1, configInfo, grayNameTmp, grayRuleTmp, srcIp, srcUser); + + if (!GRAY_MIGRATE_FLAG.get()) { + Timestamp now = new Timestamp(System.currentTimeMillis()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, now, "I", + Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(grayNameTmp, grayRuleTmp, srcUser)); + } + return getGrayOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp); + } catch (Exception e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + }); + } + + @Override + public void addConfigInfoGrayAtomic(long configGrayId, ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser) { + String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); + String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); + String md5 = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); + final String encryptedDataKey = + configInfo.getEncryptedDataKey() == null ? StringUtils.EMPTY : configInfo.getEncryptedDataKey(); + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + jt.update(configInfoGrayMapper.insert( + Arrays.asList("data_id", "group_id", "tenant_id", "gray_name", "gray_rule", "app_name", "content", + "encrypted_data_key", "md5", "src_ip", "src_user", "gmt_create@NOW()", "gmt_modified@NOW()")), + configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayName, grayRule, appNameTmp, + configInfo.getContent(), encryptedDataKey, md5, srcIp, srcUser); + } + + @Override + public ConfigOperateResult insertOrUpdateGray(final ConfigInfo configInfo, final String grayName, + final String grayRule, final String srcIp, final String srcUser) { + if (findConfigInfo4GrayState(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), grayName) + == null) { + return addConfigInfo4Gray(configInfo, grayName, grayRule, srcIp, srcUser); + } else { + return updateConfigInfo4Gray(configInfo, grayName, grayRule, srcIp, srcUser); + } + } + + @Override + public ConfigOperateResult insertOrUpdateGrayCas(final ConfigInfo configInfo, final String grayName, + final String grayRule, final String srcIp, final String srcUser) { + if (findConfigInfo4GrayState(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), grayName) + == null) { + return addConfigInfo4Gray(configInfo, grayName, grayRule, srcIp, srcUser); + } else { + return updateConfigInfo4GrayCas(configInfo, grayName, grayRule, srcIp, srcUser); + } + } + + @Override + public void removeConfigInfoGray(final String dataId, final String group, final String tenant, + final String grayName, final String srcIp, final String srcUser) { + tjt.execute(new TransactionCallbackWithoutResult() { + @Override + protected void doInTransactionWithoutResult(TransactionStatus status) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName; + try { + ConfigInfoGrayWrapper oldConfigAllInfo4Gray = findConfigInfo4Gray(dataId, group, tenantTmp, + grayNameTmp); + if (oldConfigAllInfo4Gray == null) { + return; + } + + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper( + dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO_GRAY); + jt.update( + configInfoGrayMapper.delete(Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")), + dataId, group, tenantTmp, grayNameTmp); + + Timestamp now = new Timestamp(System.currentTimeMillis()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo4Gray.getId(), + oldConfigAllInfo4Gray, srcIp, srcUser, now, "D", Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(oldConfigAllInfo4Gray.getGrayName(), + oldConfigAllInfo4Gray.getGrayRule(), oldConfigAllInfo4Gray.getSrcUser())); + } catch (CannotGetJdbcConnectionException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + } + }); + } + + @Override + public ConfigOperateResult updateConfigInfo4Gray(ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser) { + return tjt.execute(status -> { + String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); + String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + String grayRuleTmp = StringUtils.isBlank(grayRule) ? StringUtils.EMPTY : grayRule.trim(); + try { + ConfigInfoGrayWrapper oldConfigAllInfo4Gray = findConfigInfo4Gray(configInfo.getDataId(), + configInfo.getGroup(), tenantTmp, grayNameTmp); + if (oldConfigAllInfo4Gray == null) { + if (LogUtil.FATAL_LOG.isErrorEnabled()) { + LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", + configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + } + } + + String md5 = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper( + dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO_GRAY); + jt.update(configInfoGrayMapper.update( + Arrays.asList("content", "encrypted_data_key", "md5", "src_ip", "src_user", + "gmt_modified@NOW()", "app_name", "gray_rule"), + Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")), configInfo.getContent(), + configInfo.getEncryptedDataKey(), md5, srcIp, srcUser, appNameTmp, grayRuleTmp, + configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp); + + Timestamp now = new Timestamp(System.currentTimeMillis()); + if (!GRAY_MIGRATE_FLAG.get()) { + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo4Gray.getId(), + oldConfigAllInfo4Gray, srcIp, srcUser, now, "U", Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(oldConfigAllInfo4Gray.getGrayName(), + oldConfigAllInfo4Gray.getGrayRule(), oldConfigAllInfo4Gray.getSrcUser())); + } + return getGrayOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp); + } catch (CannotGetJdbcConnectionException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + }); + } + + @Override + public ConfigOperateResult updateConfigInfo4GrayCas(ConfigInfo configInfo, String grayName, String grayRule, + String srcIp, String srcUser) { + return tjt.execute(status -> { + String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); + String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + String grayRuleTmp = StringUtils.isBlank(grayRule) ? StringUtils.EMPTY : grayRule.trim(); + try { + String md5 = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper( + dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO_GRAY); + MapperContext context = new MapperContext(); + context.putUpdateParameter(FieldConstant.CONTENT, configInfo.getContent()); + context.putUpdateParameter(FieldConstant.MD5, md5); + context.putUpdateParameter(FieldConstant.SRC_IP, srcIp); + context.putUpdateParameter(FieldConstant.SRC_USER, srcUser); + context.putUpdateParameter(FieldConstant.APP_NAME, appNameTmp); + + context.putWhereParameter(FieldConstant.DATA_ID, configInfo.getDataId()); + context.putWhereParameter(FieldConstant.GROUP_ID, configInfo.getGroup()); + context.putWhereParameter(FieldConstant.TENANT_ID, tenantTmp); + context.putWhereParameter(FieldConstant.GRAY_NAME, grayNameTmp); + context.putWhereParameter(FieldConstant.GRAY_RULE, grayRuleTmp); + context.putWhereParameter(FieldConstant.MD5, configInfo.getMd5()); + + final MapperResult mapperResult = configInfoGrayMapper.updateConfigInfo4GrayCas(context); + boolean success = jt.update(mapperResult.getSql(), mapperResult.getParamList().toArray()) > 0; + + ConfigInfoGrayWrapper oldConfigAllInfo4Gray = findConfigInfo4Gray(configInfo.getDataId(), + configInfo.getGroup(), tenantTmp, grayNameTmp); + if (oldConfigAllInfo4Gray == null) { + if (LogUtil.FATAL_LOG.isErrorEnabled()) { + LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", + configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); + } + } + + Timestamp now = new Timestamp(System.currentTimeMillis()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo4Gray.getId(), + oldConfigAllInfo4Gray, srcIp, srcUser, now, "U", Constants.GRAY, grayNameTmp, + ConfigExtInfoUtil.getExtInfoFromGrayInfo(oldConfigAllInfo4Gray.getGrayName(), + oldConfigAllInfo4Gray.getGrayRule(), oldConfigAllInfo4Gray.getSrcUser())); + + if (success) { + return getGrayOperateResult(configInfo.getDataId(), configInfo.getGroup(), tenantTmp, grayNameTmp); + } else { + return new ConfigOperateResult(false); + } + } catch (CannotGetJdbcConnectionException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + }); + } + + @Override + public ConfigInfoGrayWrapper findConfigInfo4Gray(final String dataId, final String group, final String tenant, + final String grayName) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + String grayNameTmp = StringUtils.isBlank(grayName) ? StringUtils.EMPTY : grayName.trim(); + try { + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + return this.jt.queryForObject(configInfoGrayMapper.select( + Arrays.asList("id", "data_id", "group_id", "tenant_id", "gray_name", "gray_rule", "app_name", + "content", "md5", "encrypted_data_key", "gmt_modified", "src_user"), + Arrays.asList("data_id", "group_id", "tenant_id", "gray_name")), + new Object[] {dataId, group, tenantTmp, grayNameTmp}, CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER); + } catch (EmptyResultDataAccessException e) { // Indicates that the data does not exist, returns null. + return null; + } catch (CannotGetJdbcConnectionException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + } + + @Override + public int configInfoGrayCount() { + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + String sql = configInfoGrayMapper.count(null); + Integer result = jt.queryForObject(sql, Integer.class); + if (result == null) { + throw new IllegalArgumentException("configInfoGrayCount error"); + } + return result; + } + + @Override + public Page findAllConfigInfoGrayForDumpAll(final int pageNo, final int pageSize) { + final int startRow = (pageNo - 1) * pageSize; + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + String sqlCountRows = configInfoGrayMapper.count(null); + MapperResult sqlFetchRows = configInfoGrayMapper.findAllConfigInfoGrayForDumpAllFetchRows( + new MapperContext(startRow, pageSize)); + + PaginationHelper helper = createPaginationHelper(); + + try { + return helper.fetchPageLimit(sqlCountRows, sqlFetchRows.getSql(), sqlFetchRows.getParamList().toArray(), + pageNo, pageSize, CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER); + + } catch (CannotGetJdbcConnectionException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + } + + @Override + public List findChangeConfig(final Timestamp startTime, long lastMaxId, final int pageSize) { + try { + ConfigInfoGrayMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + + MapperContext context = new MapperContext(); + context.putWhereParameter(FieldConstant.START_TIME, startTime); + context.putWhereParameter(FieldConstant.PAGE_SIZE, pageSize); + context.putWhereParameter(FieldConstant.LAST_MAX_ID, lastMaxId); + + MapperResult mapperResult = configInfoMapper.findChangeConfig(context); + return jt.query(mapperResult.getSql(), mapperResult.getParamList().toArray(), + CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER); + } catch (DataAccessException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + } + + @Override + public List findConfigInfoGrays(final String dataId, final String group, final String tenant) { + String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; + ConfigInfoGrayMapper configInfoGrayMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO_GRAY); + String selectSql = configInfoGrayMapper.select(Collections.singletonList("gray_name"), + Arrays.asList("data_id", "group_id", "tenant_id")); + return jt.queryForList(selectSql, new Object[] {dataId, group, tenantTmp}, String.class); + } + +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImpl.java index 48193079860..95a9acd6fe7 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImpl.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImpl.java @@ -34,6 +34,7 @@ import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.config.server.service.sql.ExternalStorageUtils; +import com.alibaba.nacos.config.server.utils.ConfigExtInfoUtil; import com.alibaba.nacos.config.server.utils.LogUtil; import com.alibaba.nacos.config.server.utils.ParamUtils; import com.alibaba.nacos.persistence.configuration.condition.ConditionOnExternalStorage; @@ -150,7 +151,9 @@ public ConfigOperateResult addConfigInfo(final String srcIp, final String srcUse configInfo.getTenant()); Timestamp now = new Timestamp(System.currentTimeMillis()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, now, "I"); + historyConfigInfoPersistService.insertConfigHistoryAtomic(0, configInfo, srcIp, srcUser, now, "I", + Constants.FORMAL, null, + ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser)); ConfigInfoStateWrapper configInfoCurrent = this.findConfigInfoState(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); if (configInfoCurrent == null) { @@ -174,6 +177,7 @@ public ConfigOperateResult addConfigInfo(final String srcIp, final String srcUse * @param configAdvanceInfo advance info * @return */ + @Override public ConfigOperateResult insertOrUpdate(String srcIp, String srcUser, ConfigInfo configInfo, Map configAdvanceInfo) { try { @@ -247,7 +251,7 @@ PreparedStatement createPsForInsertConfigInfo(final String srcIp, final String s final String encryptedDataKey = configInfo.getEncryptedDataKey() == null ? StringUtils.EMPTY : configInfo.getEncryptedDataKey(); final String md5Tmp = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); - + String insertSql = configInfoMapper.insert( Arrays.asList("data_id", "group_id", "tenant_id", "app_name", "content", "md5", "src_ip", "src_user", "gmt_create@NOW()", "gmt_modified@NOW()", "c_desc", "c_use", "effect", "type", "c_schema", @@ -395,12 +399,13 @@ public void removeConfigInfo(final String dataId, final String group, final Stri @Override public Boolean doInTransaction(TransactionStatus status) { try { - ConfigInfo configInfo = findConfigInfo(dataId, group, tenant); - if (configInfo != null) { + ConfigAllInfo oldConfigAllInfo = findConfigAllInfo(dataId, group, tenant); + if (oldConfigAllInfo != null) { removeConfigInfoAtomic(dataId, group, tenant, srcIp, srcUser); - removeTagByIdAtomic(configInfo.getId()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(configInfo.getId(), configInfo, srcIp, - srcUser, time, "D"); + removeTagByIdAtomic(oldConfigAllInfo.getId()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo.getId(), + oldConfigAllInfo, srcIp, srcUser, time, "D", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(oldConfigAllInfo)); } } catch (CannotGetJdbcConnectionException e) { LogUtil.FATAL_LOG.error("[db-error] " + e, e); @@ -412,28 +417,29 @@ public Boolean doInTransaction(TransactionStatus status) { } @Override - public List removeConfigInfoByIds(final List ids, final String srcIp, final String srcUser) { + public List removeConfigInfoByIds(final List ids, final String srcIp, final String srcUser) { if (CollectionUtils.isEmpty(ids)) { return null; } ids.removeAll(Collections.singleton(null)); - return tjt.execute(new TransactionCallback>() { + return tjt.execute(new TransactionCallback>() { final Timestamp time = new Timestamp(System.currentTimeMillis()); @Override - public List doInTransaction(TransactionStatus status) { + public List doInTransaction(TransactionStatus status) { try { String idsStr = StringUtils.join(ids, StringUtils.COMMA); - List configInfoList = findConfigInfosByIds(idsStr); - if (!CollectionUtils.isEmpty(configInfoList)) { + List oldConfigAllInfoList = findAllConfigInfo4Export(null, null, null, null, ids); + if (!CollectionUtils.isEmpty(oldConfigAllInfoList)) { removeConfigInfoByIdsAtomic(idsStr); - for (ConfigInfo configInfo : configInfoList) { - removeTagByIdAtomic(configInfo.getId()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(configInfo.getId(), configInfo, - srcIp, srcUser, time, "D"); + for (ConfigAllInfo configAllInfo : oldConfigAllInfoList) { + removeTagByIdAtomic(configAllInfo.getId()); + historyConfigInfoPersistService.insertConfigHistoryAtomic(configAllInfo.getId(), + configAllInfo, srcIp, srcUser, time, "D", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo)); } } - return configInfoList; + return oldConfigAllInfoList; } catch (CannotGetJdbcConnectionException e) { LogUtil.FATAL_LOG.error("[db-error] " + e, e); throw e; @@ -497,9 +503,9 @@ public ConfigOperateResult updateConfigInfo(final ConfigInfo configInfo, final S final Map configAdvanceInfo) { return tjt.execute(status -> { try { - ConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), + ConfigAllInfo oldConfigAllInfo = findConfigAllInfo(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); - if (oldConfigInfo == null) { + if (oldConfigAllInfo == null) { if (LogUtil.FATAL_LOG.isErrorEnabled()) { LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); @@ -507,7 +513,7 @@ public ConfigOperateResult updateConfigInfo(final ConfigInfo configInfo, final S return new ConfigOperateResult(false); } - String appNameTmp = oldConfigInfo.getAppName(); + String appNameTmp = oldConfigAllInfo.getAppName(); /* If the appName passed by the user is not empty, use the persistent user's appName, otherwise use db; when emptying appName, you need to pass an empty string @@ -519,14 +525,15 @@ public ConfigOperateResult updateConfigInfo(final ConfigInfo configInfo, final S String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); if (configTags != null) { // delete all tags and then recreate - removeTagByIdAtomic(oldConfigInfo.getId()); - addConfigTagsRelation(oldConfigInfo.getId(), configTags, configInfo.getDataId(), + removeTagByIdAtomic(oldConfigAllInfo.getId()); + addConfigTagsRelation(oldConfigAllInfo.getId(), configTags, configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); } - + Timestamp now = new Timestamp(System.currentTimeMillis()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigInfo.getId(), oldConfigInfo, srcIp, - srcUser, now, "U"); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigAllInfo.getId(), oldConfigAllInfo, + srcIp, srcUser, now, "U", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(oldConfigAllInfo)); return getConfigInfoOperateResult(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); } catch (CannotGetJdbcConnectionException e) { @@ -550,16 +557,16 @@ public ConfigOperateResult updateConfigInfoCas(final ConfigInfo configInfo, fina final String srcUser, final Map configAdvanceInfo) { return tjt.execute(status -> { try { - ConfigInfo oldConfigInfo = findConfigInfo(configInfo.getDataId(), configInfo.getGroup(), + ConfigAllInfo oldAllConfigInfo = findConfigAllInfo(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); - if (oldConfigInfo == null) { + if (oldAllConfigInfo == null) { if (LogUtil.FATAL_LOG.isErrorEnabled()) { LogUtil.FATAL_LOG.error("expected config info[dataid:{}, group:{}, tenent:{}] but not found.", configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); } return new ConfigOperateResult(false); } - String appNameTmp = oldConfigInfo.getAppName(); + String appNameTmp = oldAllConfigInfo.getAppName(); /* If the appName passed by the user is not empty, use the persistent user's appName, otherwise use db; when emptying appName, you need to pass an empty string @@ -574,14 +581,15 @@ public ConfigOperateResult updateConfigInfoCas(final ConfigInfo configInfo, fina String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); if (configTags != null) { // delete all tags and then recreate - removeTagByIdAtomic(oldConfigInfo.getId()); - addConfigTagsRelation(oldConfigInfo.getId(), configTags, configInfo.getDataId(), + removeTagByIdAtomic(oldAllConfigInfo.getId()); + addConfigTagsRelation(oldAllConfigInfo.getId(), configTags, configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); } Timestamp now = new Timestamp(System.currentTimeMillis()); - historyConfigInfoPersistService.insertConfigHistoryAtomic(oldConfigInfo.getId(), oldConfigInfo, srcIp, - srcUser, now, "U"); + historyConfigInfoPersistService.insertConfigHistoryAtomic(oldAllConfigInfo.getId(), oldAllConfigInfo, + srcIp, srcUser, now, "U", Constants.FORMAL, null, + ConfigExtInfoUtil.getExtInfoFromAllInfo(oldAllConfigInfo)); ConfigInfoStateWrapper configInfoLast = this.findConfigInfoState(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant()); if (configInfoLast == null) { @@ -610,7 +618,7 @@ private int updateConfigInfoAtomicCas(final ConfigInfo configInfo, final String try { ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO); - + MapperContext context = new MapperContext(); context.putUpdateParameter(FieldConstant.CONTENT, configInfo.getContent()); context.putUpdateParameter(FieldConstant.MD5, md5Tmp); @@ -653,11 +661,11 @@ public void updateConfigInfoAtomic(final ConfigInfo configInfo, final String src ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO); jt.update(configInfoMapper.update( - Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified@NOW()", - "app_name", "c_desc", "c_use", "effect", "type", "c_schema", "encrypted_data_key"), - Arrays.asList("data_id", "group_id", "tenant_id")), - configInfo.getContent(), md5Tmp, srcIp, srcUser, appNameTmp, desc, use, effect, type, schema, - encryptedDataKey, configInfo.getDataId(), configInfo.getGroup(), tenantTmp); + Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified@NOW()", "app_name", "c_desc", + "c_use", "effect", "type", "c_schema", "encrypted_data_key"), + Arrays.asList("data_id", "group_id", "tenant_id")), configInfo.getContent(), md5Tmp, srcIp, srcUser, + appNameTmp, desc, use, effect, type, schema, encryptedDataKey, configInfo.getDataId(), + configInfo.getGroup(), tenantTmp); } catch (CannotGetJdbcConnectionException e) { LogUtil.FATAL_LOG.error("[db-error] " + e, e); throw e; @@ -837,7 +845,8 @@ public Page findConfigInfoLike4Page(final int pageNo, final int page String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; final String appName = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("appName"); final String content = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("content"); - final String types = Optional.ofNullable(configAdvanceInfo).map(e -> (String) e.get(ParametersField.TYPES)).orElse(null); + final String types = Optional.ofNullable(configAdvanceInfo).map(e -> (String) e.get(ParametersField.TYPES)) + .orElse(null); final String configTags = configAdvanceInfo == null ? null : (String) configAdvanceInfo.get("config_tags"); PaginationHelper helper = createPaginationHelper(); MapperResult sqlCountRows; @@ -1025,9 +1034,12 @@ public ConfigAllInfo findConfigAllInfo(final String dataId, final String group, public ConfigInfoStateWrapper findConfigInfoState(final String dataId, final String group, final String tenant) { String tenantTmp = StringUtils.isBlank(tenant) ? StringUtils.EMPTY : tenant; try { + ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO); return this.jt.queryForObject( - "SELECT id,data_id,group_id,tenant_id,gmt_modified FROM config_info WHERE data_id=? AND group_id=? AND tenant_id=?", - new Object[] {dataId, group, tenantTmp}, CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER); + configInfoMapper.select(Arrays.asList("id", "data_id", "group_id", "tenant_id", "gmt_modified"), + Arrays.asList("data_id", "group_id", "tenant_id")), new Object[] {dataId, group, tenantTmp}, + CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER); } catch (EmptyResultDataAccessException e) { // Indicates that the data does not exist, returns null. return null; } catch (CannotGetJdbcConnectionException e) { diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImpl.java b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImpl.java index eeec61d6130..a2dde077f3d 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImpl.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImpl.java @@ -46,11 +46,11 @@ import org.springframework.transaction.support.TransactionTemplate; import java.sql.Timestamp; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_DETAIL_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_LIST_ROW_MAPPER; @@ -88,20 +88,23 @@ public PaginationHelper createPaginationHelper() { @Override public void insertConfigHistoryAtomic(long id, ConfigInfo configInfo, String srcIp, String srcUser, - final Timestamp time, String ops) { + final Timestamp time, String ops, String publishType, String grayName, String extInfo) { String appNameTmp = StringUtils.defaultEmptyIfBlank(configInfo.getAppName()); String tenantTmp = StringUtils.defaultEmptyIfBlank(configInfo.getTenant()); final String md5Tmp = MD5Utils.md5Hex(configInfo.getContent(), Constants.ENCODE); String encryptedDataKey = StringUtils.defaultEmptyIfBlank(configInfo.getEncryptedDataKey()); + String publishTypeTmp = StringUtils.defaultEmptyIfBlank(publishType); + String grayNameTemp = StringUtils.defaultEmptyIfBlank(grayName); try { HistoryConfigInfoMapper historyConfigInfoMapper = mapperManager.findMapper( dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); jt.update(historyConfigInfoMapper.insert( Arrays.asList("id", "data_id", "group_id", "tenant_id", "app_name", "content", "md5", "src_ip", - "src_user", "gmt_modified", "op_type", "encrypted_data_key")), id, configInfo.getDataId(), - configInfo.getGroup(), tenantTmp, appNameTmp, configInfo.getContent(), md5Tmp, srcIp, srcUser, time, - ops, encryptedDataKey); + "src_user", "gmt_modified", "op_type", "publish_type", "gray_name", "ext_info", + "encrypted_data_key")), id, configInfo.getDataId(), configInfo.getGroup(), tenantTmp, + appNameTmp, configInfo.getContent(), md5Tmp, srcIp, srcUser, time, ops, publishTypeTmp, + grayNameTemp, extInfo, encryptedDataKey); } catch (DataAccessException e) { LogUtil.FATAL_LOG.error("[db-error] " + e, e); throw e; @@ -121,7 +124,8 @@ public void removeConfigHistory(final Timestamp startTime, final int limitSize) } @Override - public List findDeletedConfig(final Timestamp startTime, long startId, int pageSize) { + public List findDeletedConfig(final Timestamp startTime, long startId, int pageSize, + String publishType) { try { HistoryConfigInfoMapper historyConfigInfoMapper = mapperManager.findMapper( dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); @@ -129,10 +133,25 @@ public List findDeletedConfig(final Timestamp startTime, context.putWhereParameter(FieldConstant.START_TIME, startTime); context.putWhereParameter(FieldConstant.PAGE_SIZE, pageSize); context.putWhereParameter(FieldConstant.LAST_MAX_ID, startId); + context.putWhereParameter(FieldConstant.PUBLISH_TYPE, publishType); MapperResult mapperResult = historyConfigInfoMapper.findDeletedConfig(context); - return jt.query(mapperResult.getSql(), mapperResult.getParamList().toArray(), - CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER); + List configHistoryInfos = jt.query(mapperResult.getSql(), + mapperResult.getParamList().toArray(), HISTORY_DETAIL_ROW_MAPPER); + + List configInfoStateWrappers = new ArrayList<>(); + for (ConfigHistoryInfo configHistoryInfo : configHistoryInfos) { + ConfigInfoStateWrapper configInfoStateWrapper = new ConfigInfoStateWrapper(); + configInfoStateWrapper.setId(configHistoryInfo.getId()); + configInfoStateWrapper.setDataId(configHistoryInfo.getDataId()); + configInfoStateWrapper.setGroup(configHistoryInfo.getGroup()); + configInfoStateWrapper.setTenant(configHistoryInfo.getTenant()); + configInfoStateWrapper.setMd5(configHistoryInfo.getMd5()); + configInfoStateWrapper.setLastModified(configHistoryInfo.getLastModifiedTime().getTime()); + configInfoStateWrapper.setGrayName(configHistoryInfo.getGrayName()); + configInfoStateWrappers.add(configInfoStateWrapper); + } + return configInfoStateWrappers; } catch (DataAccessException e) { LogUtil.FATAL_LOG.error("[db-error] " + e, e); throw e; @@ -174,8 +193,8 @@ public ConfigHistoryInfo detailConfigHistory(Long nid) { dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); String sqlFetchRows = historyConfigInfoMapper.select( Arrays.asList("nid", "data_id", "group_id", "tenant_id", "app_name", "content", "md5", "src_user", - "src_ip", "op_type", "gmt_create", "gmt_modified", "encrypted_data_key"), - Collections.singletonList("nid")); + "src_ip", "op_type", "gmt_create", "gmt_modified", "publish_type", "gray_name", "ext_info", + "encrypted_data_key"), Collections.singletonList("nid")); try { ConfigHistoryInfo historyInfo = jt.queryForObject(sqlFetchRows, new Object[] {nid}, HISTORY_DETAIL_ROW_MAPPER); @@ -222,4 +241,29 @@ public int findConfigHistoryCountByTime(final Timestamp startTime) { } return result; } + + @Override + public ConfigHistoryInfo getNextHistoryInfo(String dataId, String group, String tenant, String publishType, + String grayName, long startNid) { + HistoryConfigInfoMapper historyConfigInfoMapper = mapperManager.findMapper( + dataSourceService.getDataSourceType(), TableConstant.HIS_CONFIG_INFO); + MapperContext context = new MapperContext(); + context.putWhereParameter(FieldConstant.DATA_ID, dataId); + context.putWhereParameter(FieldConstant.GROUP_ID, group); + context.putWhereParameter(FieldConstant.TENANT_ID, tenant); + context.putWhereParameter(FieldConstant.PUBLISH_TYPE, publishType); + context.putWhereParameter(FieldConstant.NID, startNid); + context.putWhereParameter(FieldConstant.GRAY_NAME, grayName); + MapperResult sqlFetchRows = historyConfigInfoMapper.getNextHistoryInfo(context); + try { + ConfigHistoryInfo historyInfo = jt.queryForObject(sqlFetchRows.getSql(), + sqlFetchRows.getParamList().toArray(), HISTORY_DETAIL_ROW_MAPPER); + return historyInfo; + } catch (EmptyResultDataAccessException emptyResultDataAccessException) { + return null; + } catch (DataAccessException e) { + LogUtil.FATAL_LOG.error("[db-error] " + e, e); + throw e; + } + } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/sql/EmbeddedStorageContextUtils.java b/config/src/main/java/com/alibaba/nacos/config/server/service/sql/EmbeddedStorageContextUtils.java index dac6777617f..ecd06f734f7 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/sql/EmbeddedStorageContextUtils.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/sql/EmbeddedStorageContextUtils.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.event.ConfigDumpEvent; import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder; @@ -102,6 +103,29 @@ public static void onModifyConfigTagInfo(ConfigInfo configInfo, String tag, Stri } } + /** + * In the case of the in-cluster storage mode, the logic of horizontal notification is implemented asynchronously + * via the raft state machine, along with the information. + * + * @param configInfo {@link ConfigInfo} + * @param grayName gray name + * @param grayRule gray rule + * @param srcIp The IP of the operator + * @param time Operating time + */ + public static void onModifyConfigGrayInfo(ConfigInfo configInfo, String grayName, String grayRule, String srcIp, Timestamp time) { + if (!EnvUtil.getStandaloneMode()) { + ConfigDumpEvent event = ConfigDumpEvent.builder().remove(false).namespaceId(configInfo.getTenant()) + .dataId(configInfo.getDataId()).group(configInfo.getGroup()).isBeta(false).grayName(grayName) + .grayRule(grayRule).content(configInfo.getContent()).type(configInfo.getType()).handleIp(srcIp) + .lastModifiedTs(time.getTime()).build(); + + Map extendInfo = new HashMap<>(2); + extendInfo.put(Constants.EXTEND_INFO_CONFIG_DUMP_EVENT, JacksonUtils.toJson(event)); + EmbeddedStorageContextHolder.putAllExtendInfo(extendInfo); + } + } + /** * In the case of the in-cluster storage mode, the logic of horizontal notification is implemented asynchronously * via the raft state machine, along with the information. @@ -128,12 +152,12 @@ public static void onDeleteConfigInfo(String namespaceId, String group, String d * In the case of the in-cluster storage mode, the logic of horizontal notification is implemented asynchronously * via the raft state machine, along with the information. * - * @param configInfos {@link ConfigInfo} list + * @param configInfos {@link ConfigAllInfo} list */ - public static void onBatchDeleteConfigInfo(List configInfos) { + public static void onBatchDeleteConfigInfo(List configInfos) { if (!EnvUtil.getStandaloneMode()) { List events = new ArrayList<>(); - for (ConfigInfo configInfo : configInfos) { + for (ConfigAllInfo configInfo : configInfos) { String namespaceId = StringUtils.isBlank(configInfo.getTenant()) ? StringUtils.EMPTY : configInfo.getTenant(); ConfigDumpEvent event = ConfigDumpEvent.builder().remove(true).namespaceId(namespaceId) @@ -190,4 +214,25 @@ public static void onDeleteConfigTagInfo(String namespaceId, String group, Strin } } + /** + * In the case of the in-cluster storage mode, the logic of horizontal notification is implemented asynchronously + * via the raft state machine, along with the information. + * + * @param namespaceId namespaceId + * @param group group + * @param dataId dataId + * @param grayName gray name + * @param srcIp The IP of the operator + */ + public static void onDeleteConfigGrayInfo(String namespaceId, String group, String dataId, String grayName, + String srcIp) { + if (!EnvUtil.getStandaloneMode()) { + ConfigDumpEvent event = ConfigDumpEvent.builder().remove(true).namespaceId(namespaceId).group(group) + .dataId(dataId).isBeta(true).grayName(grayName).handleIp(srcIp).build(); + + Map extendInfo = new HashMap<>(2); + extendInfo.put(Constants.EXTEND_INFO_CONFIG_DUMP_EVENT, JacksonUtils.toJson(event)); + EmbeddedStorageContextHolder.putAllExtendInfo(extendInfo); + } + } } diff --git a/config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java b/config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java index ec9b4a58fcc..5133e14c478 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/service/trace/ConfigTraceService.java @@ -41,8 +41,6 @@ public class ConfigTraceService { public static final String PERSISTENCE_EVENT_BETA = "persist-beta"; - public static final String PERSISTENCE_EVENT_BATCH = "persist-batch"; - public static final String PERSISTENCE_EVENT_TAG = "persist-tag"; /** @@ -101,12 +99,6 @@ public class ConfigTraceService { */ public static final String PULL_EVENT = "pull"; - public static final String PULL_EVENT_BETA = "pull-beta"; - - public static final String PULL_EVENT_BATCH = "pull-batch"; - - public static final String PULL_EVENT_TAG = "pull-tag"; - /** * pull type. */ @@ -197,22 +189,10 @@ public static void logDumpEvent(String dataId, String group, String tenant, Stri delayed, length); } - public static void logDumpBetaEvent(String dataId, String group, String tenant, String requestIpAppName, long ts, - String handleIp, String type, long delayed, long length) { - logDumpEventInner(dataId, group, tenant, requestIpAppName, ts, handleIp, ConfigTraceService.DUMP_EVENT_BETA, - type, delayed, length); - } - - public static void logDumpBatchEvent(String dataId, String group, String tenant, String requestIpAppName, long ts, - String handleIp, String type, long delayed, long length) { - logDumpEventInner(dataId, group, tenant, requestIpAppName, ts, handleIp, ConfigTraceService.DUMP_EVENT_BATCH, - type, delayed, length); - } - - public static void logDumpTagEvent(String dataId, String group, String tenant, String tag, String requestIpAppName, - long ts, String handleIp, String type, long delayed, long length) { + public static void logDumpGrayNameEvent(String dataId, String group, String tenant, String grayName, + String requestIpAppName, long ts, String handleIp, String type, long delayed, long length) { logDumpEventInner(dataId, group, tenant, requestIpAppName, ts, handleIp, - ConfigTraceService.DUMP_EVENT_TAG + "-" + tag, type, delayed, length); + ConfigTraceService.DUMP_EVENT + "-" + grayName, type, delayed, length); } private static void logDumpEventInner(String dataId, String group, String tenant, String requestIpAppName, long ts, diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/ConfigExtInfoUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/ConfigExtInfoUtil.java new file mode 100644 index 00000000000..38820e4eb33 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/ConfigExtInfoUtil.java @@ -0,0 +1,174 @@ +/* + * Copyright 1999-$toady.year 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.config.server.utils; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; + +/** + * Extra info util. + * + * @author Nacos + */ +public class ConfigExtInfoUtil { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigExtInfoUtil.class); + + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + + private static final Map EXTRA_INFO_KEYS_MAPPING = new HashMap<>(); + + static { + EXTRA_INFO_KEYS_MAPPING.put("type", "type"); + EXTRA_INFO_KEYS_MAPPING.put("config_tags", "config_tags"); + EXTRA_INFO_KEYS_MAPPING.put("src_user", "src_user"); + EXTRA_INFO_KEYS_MAPPING.put("desc", "c_desc"); + EXTRA_INFO_KEYS_MAPPING.put("use", "c_use"); + EXTRA_INFO_KEYS_MAPPING.put("effect", "effect"); + EXTRA_INFO_KEYS_MAPPING.put("schema", "c_schema"); + } + + private ConfigExtInfoUtil() { + } + + /** + * Extract the extInfo from advance config info. + */ + public static String getExtraInfoFromAdvanceInfoMap(Map advanceConfigInfoMap, String srcUser) { + try { + if (advanceConfigInfoMap == null || advanceConfigInfoMap.isEmpty()) { + return null; + } + + ObjectNode node = OBJECT_MAPPER.createObjectNode(); + + if (StringUtils.isNotBlank(srcUser)) { + node.put("src_user", srcUser); + } + + for (Map.Entry entry : EXTRA_INFO_KEYS_MAPPING.entrySet()) { + String key = entry.getKey(); + String mappedKey = entry.getValue(); + Object advanceConfigInfoValue = advanceConfigInfoMap.get(key); + if (advanceConfigInfoValue instanceof String && StringUtils.isNotBlank( + (String) advanceConfigInfoValue)) { + node.put(mappedKey, ((String) advanceConfigInfoValue).trim()); + } + } + + return OBJECT_MAPPER.writeValueAsString(node); + } catch (Exception ex) { + LOGGER.error("Failed to get extra info from advance info map", ex); + return null; + } + } + + /** + * Extract the extInfo from all config info. + */ + public static String getExtInfoFromAllInfo(ConfigAllInfo configAllInfo) { + ObjectNode node = OBJECT_MAPPER.createObjectNode(); + + if (StringUtils.isNotBlank(configAllInfo.getType())) { + node.put("type", configAllInfo.getType()); + } + if (StringUtils.isNotBlank(configAllInfo.getConfigTags())) { + node.put("config_tags", configAllInfo.getConfigTags()); + } + if (StringUtils.isNotBlank(configAllInfo.getEffect())) { + node.put("effect", configAllInfo.getEffect()); + } + if (StringUtils.isNotBlank(configAllInfo.getCreateUser())) { + node.put("src_user", configAllInfo.getCreateUser()); + } + if (StringUtils.isNotBlank(configAllInfo.getDesc())) { + node.put("c_desc", configAllInfo.getDesc()); + } + if (StringUtils.isNotBlank(configAllInfo.getUse())) { + node.put("c_use", configAllInfo.getUse()); + } + if (StringUtils.isNotBlank(configAllInfo.getSchema())) { + node.put("c_schema", configAllInfo.getSchema()); + } + + try { + return OBJECT_MAPPER.writeValueAsString(node); + } catch (Exception ex) { + LOGGER.error("Failed to get extra info from all config info", ex); + return null; + } + } + + /** + * Extract the extInfo from gray config info. + */ + public static String getExtInfoFromGrayInfo(String grayName, String grayRuleTmp, String oldSrcUser) { + ObjectNode node = OBJECT_MAPPER.createObjectNode(); + ObjectNode grayRuleNode = OBJECT_MAPPER.createObjectNode(); + + if (StringUtils.isNotBlank(grayName)) { + node.put("gray_name", grayName); + } + + if (StringUtils.isNotBlank(oldSrcUser)) { + node.put("src_user", oldSrcUser); + } + + if (StringUtils.isNotBlank(grayRuleTmp)) { + try { + JsonNode parsedGrayRuleNode = OBJECT_MAPPER.readTree(grayRuleTmp); + if (parsedGrayRuleNode.has(Constants.GRAY_RULE_TYPE)) { + grayRuleNode.put(Constants.GRAY_RULE_TYPE, + parsedGrayRuleNode.get(Constants.GRAY_RULE_TYPE).asText()); + } + if (parsedGrayRuleNode.has(Constants.GRAY_RULE_EXPR)) { + grayRuleNode.put(Constants.GRAY_RULE_EXPR, + parsedGrayRuleNode.get(Constants.GRAY_RULE_EXPR).asText()); + } + if (parsedGrayRuleNode.has(Constants.GRAY_RULE_VERSION)) { + grayRuleNode.put(Constants.GRAY_RULE_VERSION, + parsedGrayRuleNode.get(Constants.GRAY_RULE_VERSION).asText()); + } + if (parsedGrayRuleNode.has(Constants.GRAY_RULE_PRIORITY)) { + grayRuleNode.put(Constants.GRAY_RULE_PRIORITY, + parsedGrayRuleNode.get(Constants.GRAY_RULE_PRIORITY).asText()); + } + node.put("gray_rule", grayRuleNode.toString()); + } catch (Exception ex) { + LOGGER.error("Failed to parse gray rule as json", ex); + return null; + } + } + + try { + return OBJECT_MAPPER.writeValueAsString(node); + } catch (Exception ex) { + LOGGER.error("Failed to serialize extra info from gray info", ex); + return null; + } + } + +} \ No newline at end of file diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/ConfigTagUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/ConfigTagUtil.java new file mode 100644 index 00000000000..95c1b9dc5ec --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/ConfigTagUtil.java @@ -0,0 +1,76 @@ +/* + * 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.config.server.utils; + +import java.util.Arrays; + +/** + * Config Tag util. + * + * @author PoisonGravity + */ +public class ConfigTagUtil { + + public static final String VIRTUAL_SERVICE = "virtual-service"; + + public static final String DESTINATION_RULE = "destination-rule"; + + private static final String TAGS_DELIMITER = ","; + + private static final String HYPHEN = "-"; + + /** + *

Checks if config tags contains "virtual-service" or "destination-rule".

+ * @param configTags the tags to check + * @return {@code true} if the config tags contains "virtual-service" or "destination-rule". + */ + public static boolean isIstio(String configTags) { + if (configTags == null) { + return false; + } + if (configTags.isEmpty()) { + return false; + } + return Arrays.stream(configTags.split(TAGS_DELIMITER)) + .map(tag -> tag.trim().replaceAll(HYPHEN, "")) + .anyMatch(tag -> tag.equalsIgnoreCase(VIRTUAL_SERVICE.replaceAll(HYPHEN, "")) + || tag.equalsIgnoreCase(DESTINATION_RULE.replaceAll(HYPHEN, ""))); + } + + /** + *

Gets the type of Istio from the config tags.

+ * @param configTags the tags to check + * @return the type of Istio if it is found, {@code null} otherwise. + * @throws IllegalArgumentException if configTags is null. + */ + public static String getIstioType(String configTags) { + if (configTags == null) { + throw new IllegalArgumentException("configTags cannot be null."); + } + + if (configTags.isEmpty()) { + return null; + } + + return Arrays.stream(configTags.split(TAGS_DELIMITER)) + .map(tag -> tag.trim().replaceAll(HYPHEN, "")) + .filter(tag -> tag.equalsIgnoreCase(VIRTUAL_SERVICE.replaceAll(HYPHEN, "")) + || tag.equalsIgnoreCase(DESTINATION_RULE.replaceAll(HYPHEN, ""))) + .findFirst() + .orElse(null); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java index 530f72164eb..f3a9bd8da62 100755 --- a/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/MD5Util.java @@ -17,7 +17,6 @@ package com.alibaba.nacos.config.server.utils; import com.alibaba.nacos.config.server.constant.Constants; -import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.core.utils.StringPool; import com.alibaba.nacos.common.utils.StringUtils; @@ -51,18 +50,7 @@ public class MD5Util { */ public static List compareMd5(HttpServletRequest request, HttpServletResponse response, Map clientMd5Map) { - List changedGroupKeys = new ArrayList<>(); - String tag = request.getHeader("Vipserver-Tag"); - for (Map.Entry entry : clientMd5Map.entrySet()) { - String groupKey = entry.getKey(); - String clientMd5 = entry.getValue(); - String ip = RequestUtil.getRemoteIp(request); - boolean isUptodate = ConfigCacheService.isUptodate(groupKey, clientMd5, ip, tag); - if (!isUptodate) { - changedGroupKeys.add(groupKey); - } - } - return changedGroupKeys; + return Md5ComparatorDelegate.getInstance().compareMd5(request, response, clientMd5Map); } /** diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/Md5Comparator.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/Md5Comparator.java new file mode 100644 index 00000000000..aa183df791e --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/Md5Comparator.java @@ -0,0 +1,48 @@ +/* + * Copyright 1999-2024 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.config.server.utils; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.List; +import java.util.Map; + +/** + * The interface Md5 comparator. + * + * @author Sunrisea + */ +public interface Md5Comparator { + + /** + * Gets md 5 comparator name. + * + * @return the md 5 comparator name + */ + public String getName(); + + /** + * Compare md 5 list. + * + * @param request the request + * @param response the response + * @param clientMd5Map the client md 5 map + * @return the list + */ + public List compareMd5(HttpServletRequest request, HttpServletResponse response, + Map clientMd5Map); +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/Md5ComparatorDelegate.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/Md5ComparatorDelegate.java new file mode 100644 index 00000000000..df95aa5ea60 --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/Md5ComparatorDelegate.java @@ -0,0 +1,78 @@ +/* + * Copyright 1999-2024 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.config.server.utils; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +/** + * The type Md5 comparator delegate. + * + * @author Sunrisea + */ +public class Md5ComparatorDelegate { + + private static final Logger LOGGER = LoggerFactory.getLogger(Md5ComparatorDelegate.class); + + private static final Md5ComparatorDelegate INSTANCE = new Md5ComparatorDelegate(); + + private String md5ComparatorType = EnvUtil.getProperty("nacos.config.cache.type", "nacos"); + + private Md5Comparator md5Comparator; + + private Md5ComparatorDelegate() { + Collection md5Comparators = NacosServiceLoader.load(Md5Comparator.class); + for (Md5Comparator each : md5Comparators) { + if (StringUtils.isEmpty(each.getName())) { + LOGGER.warn( + "[Md5ComparatorDelegate] Load Md5Comparator({}) Md5ComparatorName(null/empty) fail. Please add Md5ComparatorName to resolve", + each.getClass().getName()); + continue; + } + LOGGER.info("[Md5ComparatorDelegate] Load Md5Comparator({}) Md5ComparatorName({}) successfully.", + each.getClass().getName(), each.getName()); + if (StringUtils.equals(md5ComparatorType, each.getName())) { + LOGGER.info("[Md5ComparatorDelegate] Matched Md5Comparator found,set md5Comparator={}", + each.getClass().getName()); + md5Comparator = each; + } + } + if (md5Comparator == null) { + LOGGER.info( + "[Md5ComparatorDelegate] Matched Md5Comparator not found, load Default NacosMd5Comparator successfully"); + md5Comparator = new NacosMd5Comparator(); + } + } + + public static Md5ComparatorDelegate getInstance() { + return INSTANCE; + } + + public List compareMd5(HttpServletRequest request, HttpServletResponse response, + Map clientMd5Map) { + return md5Comparator.compareMd5(request, response, clientMd5Map); + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/NacosMd5Comparator.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/NacosMd5Comparator.java new file mode 100644 index 00000000000..572cf26fb7c --- /dev/null +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/NacosMd5Comparator.java @@ -0,0 +1,57 @@ +/* + * Copyright 1999-2024 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.config.server.utils; + +import com.alibaba.nacos.config.server.service.ConfigCacheService; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; + +/** + * The type Nacos md5 comparator. + * + * @author Sunrisea + */ +public class NacosMd5Comparator implements Md5Comparator { + + @Override + public String getName() { + return "nacos"; + } + + @Override + public List compareMd5(HttpServletRequest request, HttpServletResponse response, + Map clientMd5Map) { + List changedGroupKeys = new ArrayList<>(); + String tag = request.getHeader(VIPSERVER_TAG); + for (Map.Entry entry : clientMd5Map.entrySet()) { + String groupKey = entry.getKey(); + String clientMd5 = entry.getValue(); + String ip = RequestUtil.getRemoteIp(request); + boolean isUptodate = ConfigCacheService.isUptodate(groupKey, clientMd5, ip, tag); + if (!isUptodate) { + changedGroupKeys.add(groupKey); + } + } + return changedGroupKeys; + } +} diff --git a/config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java b/config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java index 8739e9c0c44..da1df43aa2d 100644 --- a/config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java +++ b/config/src/main/java/com/alibaba/nacos/config/server/utils/PropertyUtil.java @@ -55,6 +55,13 @@ public class PropertyUtil implements ApplicationContextInitializer GRAY_MIGRATE_FLAG = ThreadLocal.withInitial(() -> false); + /** * Whether to enable the limit check function of capacity management, including the upper limit of configuration * number, configuration content size limit, etc. @@ -102,12 +109,12 @@ public class PropertyUtil implements ApplicationContextInitializer resultInfos = new ArrayList<>(); + final List resultInfos = new ArrayList<>(); + ConfigAllInfo configAllInfo = new ConfigAllInfo(); String dataId = "dataId1123"; String group = "group34567"; String tenant = "tenant45678"; - resultInfos.add(new ConfigInfo(dataId, group, tenant)); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + resultInfos.add(configAllInfo); Mockito.when(configInfoPersistService.removeConfigInfoByIds(eq(Arrays.asList(1L, 2L)), anyString(), eq(null))) .thenReturn(resultInfos); AtomicReference reference = new AtomicReference<>(); @@ -200,8 +211,8 @@ public Class subscribeType() { } }); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete(Constants.CONFIG_CONTROLLER_PATH).param("delType", "ids") - .param("ids", "1,2"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete(Constants.CONFIG_CONTROLLER_PATH) + .param("delType", "ids").param("ids", "1,2"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); @@ -222,10 +233,11 @@ void testGetConfigAdvanceInfo() throws Exception { configAdvanceInfo.setCreateUser("test"); configAdvanceInfo.setDesc("desc"); - when(configInfoPersistService.findConfigAdvanceInfo("test", "test", "")).thenReturn(configAdvanceInfo); + when(configInfoPersistService.findConfigAdvanceInfo("test", "test", "public")).thenReturn(configAdvanceInfo); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH + "/catalog") - .param("dataId", "test").param("group", "test").param("tenant", ""); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.CONFIG_CONTROLLER_PATH + "/catalog").param("dataId", "test").param("group", "test") + .param("tenant", ""); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); String code = JacksonUtils.toObj(actualValue).get("code").toString(); @@ -240,8 +252,8 @@ void testGetConfigAdvanceInfo() throws Exception { @Test void testListener() throws Exception { - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post(Constants.CONFIG_CONTROLLER_PATH + "/listener") - .param("Listening-Configs", "test"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post( + Constants.CONFIG_CONTROLLER_PATH + "/listener").param("Listening-Configs", "test"); int actualValue = mockmvc.perform(builder).andReturn().getResponse().getStatus(); assertEquals(200, actualValue); } @@ -253,13 +265,15 @@ void testGetListeners() throws Exception { SampleResult sampleResult = new SampleResult(); sampleResult.setLisentersGroupkeyStatus(listenersGroupkeyStatus); - when(configSubService.getCollectSampleResult("test", "test", "", 1)).thenReturn(sampleResult); + when(configSubService.getCollectSampleResult("test", "test", "public", 1)).thenReturn(sampleResult); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH + "/listener") - .param("dataId", "test").param("group", "test").param("tenant", "").param("sampleTime", "1"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.CONFIG_CONTROLLER_PATH + "/listener").param("dataId", "test").param("group", "test") + .param("tenant", "").param("sampleTime", "1"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); - GroupkeyListenserStatus groupkeyListenserStatus = JacksonUtils.toObj(actualValue, GroupkeyListenserStatus.class); + GroupkeyListenserStatus groupkeyListenserStatus = JacksonUtils.toObj(actualValue, + GroupkeyListenserStatus.class); assertEquals(200, groupkeyListenserStatus.getCollectStatus()); assertEquals(1, groupkeyListenserStatus.getLisentersGroupkeyStatus().size()); assertEquals("test", groupkeyListenserStatus.getLisentersGroupkeyStatus().get("test")); @@ -278,11 +292,12 @@ void testSearchConfig() throws Exception { page.setPageItems(configInfoList); Map configAdvanceInfo = new HashMap<>(8); - when(configInfoPersistService.findConfigInfo4Page(1, 10, "test", "test", "", configAdvanceInfo)).thenReturn(page); + when(configInfoPersistService.findConfigInfo4Page(1, 10, "test", "test", "public", configAdvanceInfo)).thenReturn( + page); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH).param("search", "accurate") - .param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "").param("config_tags", "") - .param("pageNo", "1").param("pageSize", "10"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH) + .param("search", "accurate").param("dataId", "test").param("group", "test").param("appName", "") + .param("tenant", "").param("config_tags", "").param("pageNo", "1").param("pageSize", "10"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); @@ -310,16 +325,18 @@ void testFuzzySearchConfig() throws Exception { page.setPageItems(configInfoList); Map configAdvanceInfo = new HashMap<>(8); - when(configInfoPersistService.findConfigInfoLike4Page(1, 10, "test", "test", "", configAdvanceInfo)).thenReturn(page); + when(configInfoPersistService.findConfigInfoLike4Page(1, 10, "test", "test", "public", configAdvanceInfo)).thenReturn( + page); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH).param("search", "blur") - .param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "").param("config_tags", "") - .param("pageNo", "1").param("pageSize", "10"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH) + .param("search", "blur").param("dataId", "test").param("group", "test").param("appName", "") + .param("tenant", "").param("config_tags", "").param("pageNo", "1").param("pageSize", "10"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); List resultList = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("pageItems").toString(), List.class); - ConfigInfo resConfigInfo = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("pageItems").get(0).toString(), ConfigInfo.class); + ConfigInfo resConfigInfo = JacksonUtils.toObj( + JacksonUtils.toObj(actualValue).get("pageItems").get(0).toString(), ConfigInfo.class); assertEquals(configInfoList.size(), resultList.size()); assertEquals(configInfo.getDataId(), resConfigInfo.getDataId()); @@ -330,8 +347,8 @@ void testFuzzySearchConfig() throws Exception { @Test void testStopBeta() throws Exception { - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete(Constants.CONFIG_CONTROLLER_PATH).param("beta", "true") - .param("dataId", "test").param("group", "test").param("tenant", ""); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete(Constants.CONFIG_CONTROLLER_PATH) + .param("beta", "true").param("dataId", "test").param("group", "test").param("tenant", ""); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); @@ -344,15 +361,17 @@ void testStopBeta() throws Exception { @Test void testQueryBeta() throws Exception { - ConfigInfoBetaWrapper configInfoBetaWrapper = new ConfigInfoBetaWrapper(); + ConfigInfoGrayWrapper configInfoBetaWrapper = new ConfigInfoGrayWrapper(); configInfoBetaWrapper.setDataId("test"); configInfoBetaWrapper.setGroup("test"); configInfoBetaWrapper.setContent("test"); + configInfoBetaWrapper.setGrayName("beta"); + configInfoBetaWrapper.setGrayRule("{\"type\":\"beta\",\"version\":\"1.0.0\",\"expr\":\"127.0.0.1,127.0.0.2\",\"priority\":-1000}"); + when(configInfoGrayPersistService.findConfigInfo4Gray("test", "test", "public", "beta")).thenReturn( + configInfoBetaWrapper); - when(configInfoBetaPersistService.findConfigInfo4Beta("test", "test", "")).thenReturn(configInfoBetaWrapper); - - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH).param("beta", "true") - .param("dataId", "test").param("group", "test").param("tenant", ""); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH) + .param("beta", "true").param("dataId", "test").param("group", "test").param("tenant", ""); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); String code = JacksonUtils.toObj(actualValue).get("code").toString(); @@ -363,6 +382,8 @@ void testQueryBeta() throws Exception { assertEquals(configInfoBetaWrapper.getDataId(), resConfigInfoBetaWrapper.getDataId()); assertEquals(configInfoBetaWrapper.getGroup(), resConfigInfoBetaWrapper.getGroup()); assertEquals(configInfoBetaWrapper.getContent(), resConfigInfoBetaWrapper.getContent()); + assertEquals("127.0.0.1,127.0.0.2", resConfigInfoBetaWrapper.getBetaIps()); + } @Test @@ -383,8 +404,9 @@ void testExportConfig() throws Exception { Mockito.when(configInfoPersistService.findAllConfigInfo4Export(eq(dataId), eq(group), eq(tenant), eq(appname), eq(Arrays.asList(1L, 2L)))).thenReturn(dataList); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH).param("export", "true") - .param("dataId", dataId).param("group", group).param("tenant", tenant).param("appName", appname).param("ids", "1,2"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH) + .param("export", "true").param("dataId", dataId).param("group", group).param("tenant", tenant) + .param("appName", appname).param("ids", "1,2"); int actualValue = mockmvc.perform(builder).andReturn().getResponse().getStatus(); @@ -407,8 +429,9 @@ void testExportConfigV2() throws Exception { dataList.add(configAllInfo); Mockito.when(configInfoPersistService.findAllConfigInfo4Export(eq(dataId), eq(group), eq(tenant), eq(appname), eq(Arrays.asList(1L, 2L)))).thenReturn(dataList); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH).param("exportV2", "true") - .param("dataId", dataId).param("group", group).param("tenant", tenant).param("appName", appname).param("ids", "1,2"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_PATH) + .param("exportV2", "true").param("dataId", dataId).param("group", group).param("tenant", tenant) + .param("appName", appname).param("ids", "1,2"); int actualValue = mockmvc.perform(builder).andReturn().getResponse().getStatus(); @@ -428,16 +451,19 @@ void testImportAndPublishConfig() throws Exception { when(namespacePersistService.tenantInfoCountByTenantId("public")).thenReturn(1); Map map = new HashMap<>(); map.put("test", "test"); - when(configInfoPersistService.batchInsertOrUpdate(anyList(), anyString(), anyString(), any(), any())).thenReturn(map); + when(configInfoPersistService.batchInsertOrUpdate(anyList(), anyString(), anyString(), any(), + any())).thenReturn(map); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart(Constants.CONFIG_CONTROLLER_PATH).file(file) - .param("import", "true").param("src_user", "test").param("namespace", "public").param("policy", "ABORT"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart(Constants.CONFIG_CONTROLLER_PATH) + .file(file).param("import", "true").param("src_user", "test").param("namespace", "public") + .param("policy", "ABORT"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); String code = JacksonUtils.toObj(actualValue).get("code").toString(); assertEquals("200", code); - Map resultMap = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("data").toString(), Map.class); + Map resultMap = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("data").toString(), + Map.class); assertEquals(map.get("test"), resultMap.get("test").toString()); zipUtilsMockedStatic.close(); @@ -467,16 +493,19 @@ void testImportAndPublishConfigV2() throws Exception { when(namespacePersistService.tenantInfoCountByTenantId("public")).thenReturn(1); Map map = new HashMap<>(); map.put("test", "test"); - when(configInfoPersistService.batchInsertOrUpdate(anyList(), anyString(), anyString(), any(), any())).thenReturn(map); + when(configInfoPersistService.batchInsertOrUpdate(anyList(), anyString(), anyString(), any(), + any())).thenReturn(map); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart(Constants.CONFIG_CONTROLLER_PATH).file(file) - .param("import", "true").param("src_user", "test").param("namespace", "public").param("policy", "ABORT"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart(Constants.CONFIG_CONTROLLER_PATH) + .file(file).param("import", "true").param("src_user", "test").param("namespace", "public") + .param("policy", "ABORT"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); String code = JacksonUtils.toObj(actualValue).get("code").toString(); assertEquals("200", code); - Map resultMap = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("data").toString(), Map.class); + Map resultMap = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("data").toString(), + Map.class); assertEquals(map.get("test"), resultMap.get("test").toString()); zipUtilsMockedStatic.close(); @@ -502,21 +531,24 @@ void testCloneConfig() throws Exception { List idList = new ArrayList<>(configBeansList.size()); idList.add(sameNamespaceCloneConfigBean.getCfgId()); - when(configInfoPersistService.findAllConfigInfo4Export(null, null, null, null, idList)).thenReturn(queryedDataList); + when(configInfoPersistService.findAllConfigInfo4Export(null, null, null, null, idList)).thenReturn( + queryedDataList); Map map = new HashMap<>(); map.put("test", "test"); - when(configInfoPersistService.batchInsertOrUpdate(anyList(), anyString(), anyString(), any(), any())).thenReturn(map); + when(configInfoPersistService.batchInsertOrUpdate(anyList(), anyString(), anyString(), any(), + any())).thenReturn(map); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post(Constants.CONFIG_CONTROLLER_PATH).param("clone", "true") - .param("src_user", "test").param("tenant", "public").param("policy", "ABORT").content(JacksonUtils.toJson(configBeansList)) - .contentType(MediaType.APPLICATION_JSON); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post(Constants.CONFIG_CONTROLLER_PATH) + .param("clone", "true").param("src_user", "test").param("tenant", "public").param("policy", "ABORT") + .content(JacksonUtils.toJson(configBeansList)).contentType(MediaType.APPLICATION_JSON); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); String code = JacksonUtils.toObj(actualValue).get("code").toString(); assertEquals("200", code); - Map resultMap = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("data").toString(), Map.class); + Map resultMap = JacksonUtils.toObj(JacksonUtils.toObj(actualValue).get("data").toString(), + Map.class); assertEquals(map.get("test"), resultMap.get("test").toString()); } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java index f49bc1b6086..4c89bde3c57 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/ConfigServletInnerTest.java @@ -20,12 +20,20 @@ import com.alibaba.nacos.common.constant.HttpHeaderConsts; import com.alibaba.nacos.common.http.param.MediaType; import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.CacheItem; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.ConfigGrayPersistInfo; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.config.server.service.LongPollingService; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; import com.alibaba.nacos.config.server.service.dump.disk.ConfigRocksDbDiskService; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; import com.alibaba.nacos.config.server.utils.GroupKey; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.MD5Util; @@ -54,6 +62,7 @@ import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; import static com.alibaba.nacos.config.server.constant.Constants.CONTENT_MD5; +import static com.alibaba.nacos.config.server.constant.Constants.ENCODE_UTF8; import static com.alibaba.nacos.config.server.utils.RequestUtil.CLIENT_APPNAME_HEADER; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -85,6 +94,7 @@ class ConfigServletInnerTest { void setUp() { EnvUtil.setEnvironment(new StandardEnvironment()); ReflectionTestUtils.setField(configServletInner, "longPollingService", longPollingService); + ReflectionTestUtils.setField(configServletInner, "configQueryChainService", new ConfigQueryChainService()); configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class); propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class); propertyUtilMockedStatic.when(PropertyUtil::getMaxContent).thenReturn(1024 * 1000); @@ -143,33 +153,56 @@ void testDoGetConfigV1Beta() throws Exception { //mock cache item isBeta CacheItem cacheItem = new CacheItem("test"); - cacheItem.setBeta(true); - List ips4Beta = new ArrayList<>(); - ips4Beta.add("localhost"); - cacheItem.setIps4Beta(ips4Beta); - cacheItem.initBetaCacheIfEmpty(); - cacheItem.getConfigCacheBeta().setEncryptedDataKey("betaKey1234567"); - cacheItem.getConfigCacheBeta().setMd5Utf8("md52345Beta"); String dataId = "testDataId135"; String group = "group23"; String tenant = "tenant234"; - configCacheServiceMockedStatic.when(() -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) .thenReturn(cacheItem); - + String mockBetaContent = "content3456543"; + mockGray4Beta(cacheItem, mockBetaContent, "localhost", "betaKey1234567"); MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRemoteAddr("localhost:8080"); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); + request.setRemoteAddr("localhost"); request.addHeader(CLIENT_APPNAME_HEADER, "test"); MockHttpServletResponse response = new MockHttpServletResponse(); - String mockBetaContent = "content3456543"; - when(configRocksDbDiskService.getBetaContent(dataId, group, tenant)).thenReturn(mockBetaContent); - String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, "", "true", "localhost"); + + when(configRocksDbDiskService.getGrayContent(dataId, group, tenant, BetaGrayRule.TYPE_BETA)).thenReturn( + mockBetaContent); + String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, "", "true", + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals("true", response.getHeader("isBeta")); - assertEquals("md52345Beta", response.getHeader(CONTENT_MD5)); + assertEquals(MD5Utils.md5Hex(mockBetaContent, ENCODE_UTF8), response.getHeader(CONTENT_MD5)); assertEquals("betaKey1234567", response.getHeader("Encrypted-Data-Key")); assertEquals(mockBetaContent, response.getContentAsString()); } + private void mockGray4Beta(CacheItem cacheItem, String content, String betaIps, String dataKey) { + cacheItem.initConfigGrayIfEmpty(BetaGrayRule.TYPE_BETA); + ConfigCacheGray configCacheGray = cacheItem.getConfigCacheGray().get(BetaGrayRule.TYPE_BETA); + configCacheGray.setMd5(MD5Utils.md5Hex(content, ENCODE_UTF8)); + configCacheGray.setEncryptedDataKey(dataKey); + ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(BetaGrayRule.TYPE_BETA, + BetaGrayRule.VERSION, betaIps, -1000); + configCacheGray.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo)); + cacheItem.sortConfigGray(); + } + + private void mockGray4Tag(CacheItem cacheItem, String content, String tagValue, String dataKey, long ts) { + cacheItem.initConfigGrayIfEmpty(TagGrayRule.TYPE_TAG + "_" + tagValue); + ConfigCacheGray configCacheGray = cacheItem.getConfigCacheGray().get(TagGrayRule.TYPE_TAG + "_" + tagValue); + configCacheGray.setMd5(MD5Utils.md5Hex(content, ENCODE_UTF8)); + configCacheGray.setLastModifiedTs(ts); + configCacheGray.setEncryptedDataKey(dataKey); + ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(TagGrayRule.TYPE_TAG, + TagGrayRule.VERSION, tagValue, -999); + configCacheGray.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo)); + cacheItem.sortConfigGray(); + } + /** * test get config of tag. * @@ -181,60 +214,64 @@ void testDoGetConfigV1Tag() throws Exception { String dataId = "dataId123455"; String group = "group"; String tenant = "tenant"; - configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))) - .thenReturn(1); + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))).thenReturn(1); //mock cache item with tag. CacheItem cacheItem = new CacheItem("test"); - cacheItem.setBeta(false); - List ips4Beta = new ArrayList<>(); - ips4Beta.add("localhost"); - cacheItem.setIps4Beta(ips4Beta); String autoTag = "auto-tag-test"; - cacheItem.initConfigTagsIfEmpty(autoTag); - cacheItem.getConfigCacheTags().get(autoTag).setEncryptedDataKey("autoTagkey"); - cacheItem.getConfigCacheTags().get(autoTag).setMd5Utf8("md5autotag11"); long autoTagTs = System.currentTimeMillis(); - cacheItem.getConfigCacheTags().get(autoTag).setLastModifiedTs(autoTagTs); + String autoTagContent = "1234566autotag"; + mockGray4Tag(cacheItem, autoTagContent, autoTag, "autoTagkey", autoTagTs); + String specificTag = "specificTag"; - cacheItem.initConfigTagsIfEmpty(specificTag); - cacheItem.getConfigCacheTags().get(specificTag).setEncryptedDataKey("specificTagkey"); - cacheItem.getConfigCacheTags().get(specificTag).setMd5Utf8("md5specificTag11"); + String specificTagContent = "1234566autotag"; long specificTs = System.currentTimeMillis(); - cacheItem.getConfigCacheTags().get(specificTag).setLastModifiedTs(specificTs); + mockGray4Tag(cacheItem, specificTagContent, specificTag, "specificTagkey", specificTs); - configCacheServiceMockedStatic.when(() -> ConfigCacheService.getContentCache(GroupKey2.getKey(dataId, group, tenant))) + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.getContentCache(GroupKey2.getKey(dataId, group, tenant))) .thenReturn(cacheItem); //test auto tag. MockHttpServletRequest request = new MockHttpServletRequest(); - request.setRemoteAddr("localhost:8080"); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); + request.setRemoteAddr("localhost"); request.addHeader(CLIENT_APPNAME_HEADER, "test"); request.addHeader(VIPSERVER_TAG, autoTag); MockHttpServletResponse response = new MockHttpServletResponse(); - String autoTagContent = "1234566autotag"; - Mockito.when(configRocksDbDiskService.getTagContent(dataId, group, tenant, autoTag)).thenReturn(autoTagContent); - String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", "localhost"); + + Mockito.when( + configRocksDbDiskService.getGrayContent(dataId, group, tenant, TagGrayRule.TYPE_TAG + "_" + autoTag)) + .thenReturn(autoTagContent); + String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(autoTagContent, response.getContentAsString()); - assertEquals("md5autotag11", response.getHeader(CONTENT_MD5)); + assertEquals(MD5Utils.md5Hex(autoTagContent, "UTF-8"), response.getHeader(CONTENT_MD5)); assertEquals("autoTagkey", response.getHeader("Encrypted-Data-Key")); //test for specific tag. has higher propority than auto tag. response = new MockHttpServletResponse(); - String specificTagContent = "1234566autotag"; - when(configRocksDbDiskService.getTagContent(dataId, group, tenant, specificTag)).thenReturn(specificTagContent); - actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, specificTag, "true", "localhost"); + request.setParameter("tag", specificTag); + when(configRocksDbDiskService.getGrayContent(dataId, group, tenant, + TagGrayRule.TYPE_TAG + "_" + specificTag)).thenReturn(specificTagContent); + actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, specificTag, "true", + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(specificTagContent, response.getContentAsString()); - assertEquals("md5specificTag11", response.getHeader(CONTENT_MD5)); + assertEquals(MD5Utils.md5Hex(specificTagContent, "UTF-8"), response.getHeader(CONTENT_MD5)); assertEquals("specificTagkey", response.getHeader("Encrypted-Data-Key")); // test for specific tag ,not exist - when(configRocksDbDiskService.getTagContent(dataId, group, tenant, "auto-tag-test-not-exist")).thenReturn(null); + request.setParameter("tag", "auto-tag-test-not-exist"); + when(configRocksDbDiskService.getGrayContent(dataId, group, tenant, + TagGrayRule.TYPE_TAG + "_" + "auto-tag-test-not-exist")).thenReturn(null); response = new MockHttpServletResponse(); - actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, "auto-tag-test-not-exist", "true", - "localhost"); + actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, + "auto-tag-test-not-exist", "true", "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_NOT_FOUND + "", actualValue); String expectedContent = "config data not exist"; String actualContent = response.getContentAsString(); @@ -248,25 +285,29 @@ void testDoGetConfigFormal() throws Exception { String dataId = "dataId1234552333"; String group = "group"; String tenant = "tenant"; - configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))) - .thenReturn(1); + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))).thenReturn(1); //mock cache item . CacheItem cacheItem = new CacheItem("test"); - cacheItem.setBeta(false); String md5 = "md5wertyui"; - String content = "content345678"; - cacheItem.getConfigCache().setMd5Utf8(md5); + final String content = "content345678"; + cacheItem.getConfigCache().setMd5(md5); long ts = System.currentTimeMillis(); cacheItem.getConfigCache().setLastModifiedTs(ts); cacheItem.getConfigCache().setEncryptedDataKey("key2345678"); - configCacheServiceMockedStatic.when(() -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) .thenReturn(cacheItem); MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); MockHttpServletResponse response = new MockHttpServletResponse(); when(configRocksDbDiskService.getContent(dataId, group, tenant)).thenReturn(content); - String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", "localhost"); + String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", + "localhost", ApiVersionEnum.V1); assertEquals(content, response.getContentAsString()); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(md5, response.getHeader(CONTENT_MD5)); @@ -279,25 +320,29 @@ void testDoGetConfigFormalV2() throws Exception { String dataId = "dataId1234552333V2"; String group = "group"; String tenant = "tenant"; - configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))) - .thenReturn(1); + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))).thenReturn(1); //mock cache item . CacheItem cacheItem = new CacheItem("test"); - cacheItem.setBeta(false); String md5 = "md5wertyui"; - String content = "content345678"; - cacheItem.getConfigCache().setMd5Utf8(md5); + final String content = "content345678"; + cacheItem.getConfigCache().setMd5(md5); long ts = System.currentTimeMillis(); cacheItem.getConfigCache().setLastModifiedTs(ts); cacheItem.getConfigCache().setEncryptedDataKey("key2345678"); - configCacheServiceMockedStatic.when(() -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataId, group, tenant))) .thenReturn(cacheItem); MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); MockHttpServletResponse response = new MockHttpServletResponse(); when(configRocksDbDiskService.getContent(dataId, group, tenant)).thenReturn(content); - String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", "localhost", true); + String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, null, "true", + "localhost", ApiVersionEnum.V2); assertEquals(JacksonUtils.toJson(Result.success(content)), response.getContentAsString()); assertEquals(HttpServletResponse.SC_OK + "", actualValue); assertEquals(md5, response.getHeader(CONTENT_MD5)); @@ -307,19 +352,30 @@ void testDoGetConfigFormalV2() throws Exception { @Test void testDoGetConfigNotExist() throws Exception { + String dataId = "test"; + String group = "test"; + final String tenant = "test"; + final String tag = "test"; // if lockResult equals 0,cache item not exist. configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(anyString())).thenReturn(0); MockHttpServletRequest request = new MockHttpServletRequest(); + request.setParameter("dataId", dataId); + request.setParameter("group", group); + request.setParameter("tenant", tenant); + request.setParameter("tag", tag); MockHttpServletResponse response = new MockHttpServletResponse(); - String actualValue = configServletInner.doGetConfig(request, response, "test", "test", "test", "test", "true", "localhost"); + String actualValue = configServletInner.doGetConfig(request, response, dataId, group, tenant, tag, "true", + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_NOT_FOUND + "", actualValue); - configCacheServiceMockedStatic.when(() -> ConfigCacheService.getContentCache(GroupKey2.getKey("test", "test", "test"))) + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.getContentCache(GroupKey2.getKey("test", "test", "test"))) .thenReturn(new CacheItem(GroupKey2.getKey("test", "test", "test"))); // if lockResult less than 0 configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(anyString())).thenReturn(-1); - actualValue = configServletInner.doGetConfig(request, response, "test", "test", "test", "test", "true", "localhost"); + actualValue = configServletInner.doGetConfig(request, response, "test", "test", "test", "test", "true", + "localhost", ApiVersionEnum.V1); assertEquals(HttpServletResponse.SC_CONFLICT + "", actualValue); } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/HistoryControllerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/HistoryControllerTest.java index 9efc72c7aba..3c6f35d6af7 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/controller/HistoryControllerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/HistoryControllerTest.java @@ -92,11 +92,11 @@ void testListConfigHistory() throws Exception { page.setPagesAvailable(2); page.setPageItems(configHistoryInfoList); - when(historyService.listConfigHistory("test", "test", "", 1, 10)).thenReturn(page); + when(historyService.listConfigHistory("test", "test", "public", 1, 10)).thenReturn(page); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.HISTORY_CONTROLLER_PATH).param("search", "accurate") - .param("dataId", "test").param("group", "test").param("tenant", "").param("appName", "").param("pageNo", "1") - .param("pageSize", "10"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.HISTORY_CONTROLLER_PATH) + .param("search", "accurate").param("dataId", "test").param("group", "test").param("tenant", "") + .param("appName", "").param("pageNo", "1").param("pageSize", "10"); MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); @@ -104,7 +104,8 @@ void testListConfigHistory() throws Exception { JsonNode pageItemsNode = JacksonUtils.toObj(actualValue).get("pageItems"); List resultList = JacksonUtils.toObj(pageItemsNode.toString(), List.class); - ConfigHistoryInfo resConfigHistoryInfo = JacksonUtils.toObj(pageItemsNode.get(0).toString(), ConfigHistoryInfo.class); + ConfigHistoryInfo resConfigHistoryInfo = JacksonUtils.toObj(pageItemsNode.get(0).toString(), + ConfigHistoryInfo.class); assertEquals(configHistoryInfoList.size(), resultList.size()); assertEquals(configHistoryInfo.getDataId(), resConfigHistoryInfo.getDataId()); @@ -126,8 +127,8 @@ void testGetConfigHistoryInfo() throws Exception { when(historyController.getConfigHistoryInfo("test", "test", "", 1L)).thenReturn(configHistoryInfo); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.HISTORY_CONTROLLER_PATH).param("dataId", "test") - .param("group", "test").param("tenant", "").param("nid", "1"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.HISTORY_CONTROLLER_PATH) + .param("dataId", "test").param("group", "test").param("tenant", "").param("nid", "1"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); ConfigHistoryInfo resConfigHistoryInfo = JacksonUtils.toObj(actualValue, ConfigHistoryInfo.class); @@ -149,10 +150,11 @@ void testGetPreviousConfigHistoryInfo() throws Exception { configHistoryInfo.setCreatedTime(new Timestamp(new Date().getTime())); configHistoryInfo.setLastModifiedTime(new Timestamp(new Date().getTime())); - when(historyService.getPreviousConfigHistoryInfo("test", "test", "", 1L)).thenReturn(configHistoryInfo); + when(historyService.getPreviousConfigHistoryInfo("test", "test", "public", 1L)).thenReturn(configHistoryInfo); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.HISTORY_CONTROLLER_PATH + "/previous") - .param("dataId", "test").param("group", "test").param("tenant", "").param("id", "1"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.HISTORY_CONTROLLER_PATH + "/previous").param("dataId", "test").param("group", "test") + .param("tenant", "").param("id", "1"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); ConfigHistoryInfo resConfigHistoryInfo = JacksonUtils.toObj(actualValue, ConfigHistoryInfo.class); @@ -174,8 +176,8 @@ void testGetDataIds() throws Exception { configInfoWrappers.add(configInfoWrapper); when(historyService.getConfigListByNamespace("test")).thenReturn(configInfoWrappers); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.HISTORY_CONTROLLER_PATH + "/configs") - .param("tenant", "test"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.HISTORY_CONTROLLER_PATH + "/configs").param("tenant", "test"); String actualValue = mockmvc.perform(builder).andReturn().getResponse().getContentAsString(); List resConfigInfoWrappers = JacksonUtils.toObj(actualValue, List.class); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java index a0985453c6e..7046591ef31 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/ConfigControllerV2Test.java @@ -18,10 +18,11 @@ import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.api.model.v2.Result; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.config.server.constant.Constants; import com.alibaba.nacos.config.server.controller.ConfigServletInner; +import com.alibaba.nacos.config.server.enums.ApiVersionEnum; import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.model.form.ConfigForm; @@ -29,6 +30,7 @@ import com.alibaba.nacos.config.server.service.ConfigOperationService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.core.auth.AuthFilter; +import com.alibaba.nacos.core.code.ControllerMethodsCache; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.sys.env.EnvUtil; import com.fasterxml.jackson.databind.JsonNode; @@ -52,7 +54,9 @@ import jakarta.servlet.ServletContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import java.lang.reflect.Method; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -89,7 +93,10 @@ class ConfigControllerV2Test { private AuthFilter authFilter; @Mock - private AuthConfigs authConfigs; + private NacosAuthConfig authConfig; + + @Mock + private ControllerMethodsCache controllerMethodsCache; private ConfigControllerV2 configControllerV2; @@ -115,7 +122,7 @@ void setUp() { configDetailService = new ConfigDetailService(configInfoPersistService); configControllerV2 = new ConfigControllerV2(inner, configOperationService, configDetailService); mockmvc = MockMvcBuilders.standaloneSetup(configControllerV2).addFilter(authFilter).build(); - when(authConfigs.isAuthEnabled()).thenReturn(false); + when(authConfig.isAuthEnabled()).thenReturn(false); } @Test @@ -126,16 +133,18 @@ void testGetConfig() throws Exception { doAnswer(x -> { x.getArgument(1, HttpServletResponse.class).setStatus(200); - x.getArgument(1, HttpServletResponse.class).setContentType(com.alibaba.nacos.common.http.param.MediaType.APPLICATION_JSON); + x.getArgument(1, HttpServletResponse.class) + .setContentType(com.alibaba.nacos.common.http.param.MediaType.APPLICATION_JSON); x.getArgument(1, HttpServletResponse.class).getWriter().print(JacksonUtils.toJson(stringResult)); return null; - }).when(inner).doGetConfig(any(HttpServletRequest.class), any(HttpServletResponse.class), eq(TEST_DATA_ID), eq(TEST_GROUP), - eq(TEST_NAMESPACE_ID), eq(TEST_TAG), eq(null), anyString(), eq(true)); + }).when(inner).doGetConfig(any(HttpServletRequest.class), any(HttpServletResponse.class), eq(TEST_DATA_ID), + eq(TEST_GROUP), eq(TEST_NAMESPACE_ID_PUBLIC), eq(TEST_TAG), eq(null), anyString(), + eq(ApiVersionEnum.V2)); configControllerV2.getConfig(request, response, TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, TEST_TAG); - verify(inner).doGetConfig(eq(request), eq(response), eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID), eq(TEST_TAG), - eq(null), anyString(), eq(true)); + verify(inner).doGetConfig(eq(request), eq(response), eq(TEST_DATA_ID), eq(TEST_GROUP), + eq(TEST_NAMESPACE_ID_PUBLIC), eq(TEST_TAG), eq(null), anyString(), eq(ApiVersionEnum.V2)); JsonNode resNode = JacksonUtils.toObj(response.getContentAsString()); Integer errCode = JacksonUtils.toObj(resNode.get("code").toString(), Integer.class); String actContent = JacksonUtils.toObj(resNode.get("data").toString(), String.class); @@ -154,7 +163,8 @@ void testPublishConfig() throws Exception { configForm.setContent(TEST_CONTENT); MockHttpServletRequest request = new MockHttpServletRequest(); - when(configOperationService.publishConfig(any(ConfigForm.class), any(ConfigRequestInfo.class), anyString())).thenReturn(true); + when(configOperationService.publishConfig(any(ConfigForm.class), any(ConfigRequestInfo.class), + anyString())).thenReturn(true); Result booleanResult = configControllerV2.publishConfig(configForm, request); @@ -195,14 +205,14 @@ void testPublishConfigWhenNameSpaceIsPublic() throws Exception { configForm.setNamespaceId(TEST_NAMESPACE_ID_PUBLIC); configForm.setContent(TEST_CONTENT); MockHttpServletRequest request = new MockHttpServletRequest(); - - when(configOperationService.publishConfig(any(ConfigForm.class), any(ConfigRequestInfo.class), anyString())).thenAnswer( - (Answer) invocation -> { - if (invocation.getArgument(0, ConfigForm.class).getNamespaceId().equals(TEST_NAMESPACE_ID)) { - return true; - } - return false; - }); + Answer answer = invocationOnMock -> { + if (invocationOnMock.getArgument(0, ConfigForm.class).getNamespaceId().equals(TEST_NAMESPACE_ID_PUBLIC)) { + return true; + } + return false; + }; + when(configOperationService.publishConfig(any(ConfigForm.class), any(ConfigRequestInfo.class), + anyString())).thenAnswer(answer); Result booleanResult = configControllerV2.publishConfig(configForm, request); @@ -217,12 +227,13 @@ void testDeleteConfigWhenNameSpaceIsPublic() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - when(configOperationService.deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID), eq(TEST_TAG), any(), - any())).thenReturn(true); - Result booleanResult = configControllerV2.deleteConfig(request, TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, - TEST_TAG); + when(configOperationService.deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID_PUBLIC), + eq(TEST_TAG), any(), any())).thenReturn(true); + Result booleanResult = configControllerV2.deleteConfig(request, TEST_DATA_ID, TEST_GROUP, + TEST_NAMESPACE_ID, TEST_TAG); - verify(configOperationService).deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID), eq(TEST_TAG), any(), any()); + verify(configOperationService).deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID_PUBLIC), + eq(TEST_TAG), any(), any()); assertEquals(ErrorCode.SUCCESS.getCode(), booleanResult.getCode()); assertTrue(booleanResult.getData()); @@ -233,12 +244,14 @@ void testDeleteConfig() throws Exception { MockHttpServletRequest request = new MockHttpServletRequest(); - when(configOperationService.deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID), eq(TEST_TAG), any(), - any())).thenReturn(true); + when(configOperationService.deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID_PUBLIC), + eq(TEST_TAG), any(), any())).thenReturn(true); - Result booleanResult = configControllerV2.deleteConfig(request, TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, TEST_TAG); + Result booleanResult = configControllerV2.deleteConfig(request, TEST_DATA_ID, TEST_GROUP, + TEST_NAMESPACE_ID, TEST_TAG); - verify(configOperationService).deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID), eq(TEST_TAG), any(), any()); + verify(configOperationService).deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(TEST_NAMESPACE_ID_PUBLIC), + eq(TEST_TAG), any(), any()); assertEquals(ErrorCode.SUCCESS.getCode(), booleanResult.getCode()); assertTrue(booleanResult.getData()); @@ -258,11 +271,14 @@ void testGetConfigByDetail() throws Exception { Map configAdvanceInfo = new HashMap<>(8); configAdvanceInfo.put("content", "server.port"); - when(configInfoPersistService.findConfigInfo4Page(1, 10, "test", "test", "", configAdvanceInfo)).thenReturn(page); + when(configInfoPersistService.findConfigInfo4Page(1, 10, "test", "test", "public", + configAdvanceInfo)).thenReturn(page); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail") - .param("search", "accurate").param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "") - .param("config_tags", "").param("pageNo", "1").param("pageSize", "10").param("config_detail", "server.port"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail").param("search", "accurate") + .param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "") + .param("config_tags", "").param("pageNo", "1").param("pageSize", "10") + .param("config_detail", "server.port"); MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); String actualValue = response.getContentAsString(); @@ -290,11 +306,13 @@ void testGetConfigFuzzyByDetail() throws Exception { Map configAdvanceInfo = new HashMap<>(8); configAdvanceInfo.put("content", "server.port"); - when(configInfoPersistService.findConfigInfoLike4Page(1, 10, "test", "test", "", configAdvanceInfo)).thenReturn(page); + when(configInfoPersistService.findConfigInfoLike4Page(1, 10, "test", "test", "public", + configAdvanceInfo)).thenReturn(page); - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail") - .param("search", "blur").param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "") - .param("config_tags", "").param("pageNo", "1").param("pageSize", "10").param("config_detail", "server.port"); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail").param("search", "blur").param("dataId", "test") + .param("group", "test").param("appName", "").param("tenant", "").param("config_tags", "") + .param("pageNo", "1").param("pageSize", "10").param("config_detail", "server.port"); MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); String actualValue = response.getContentAsString(); @@ -310,11 +328,15 @@ void testGetConfigFuzzyByDetail() throws Exception { @Test void testGetConfigAuthFilter() throws Exception { - when(authConfigs.isAuthEnabled()).thenReturn(true); - - MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get(Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail") - .param("search", "accurate").param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "") - .param("config_tags", "").param("pageNo", "1").param("pageSize", "10").param("config_detail", "server.port"); + when(authConfig.isAuthEnabled()).thenReturn(true); + Method method = Arrays.stream(ConfigControllerV2.class.getMethods()) + .filter(m -> m.getName().equals("searchConfigByDetails")).findFirst().get(); + when(controllerMethodsCache.getMethod(any(HttpServletRequest.class))).thenReturn(method); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get( + Constants.CONFIG_CONTROLLER_V2_PATH + "/searchDetail").param("search", "accurate") + .param("dataId", "test").param("group", "test").param("appName", "").param("tenant", "") + .param("config_tags", "").param("pageNo", "1").param("pageSize", "10") + .param("config_detail", "server.port"); MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); assertEquals(HttpServletResponse.SC_FORBIDDEN, response.getStatus()); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2Test.java b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2Test.java index df6c0613594..e3482359b52 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2Test.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/controller/v2/HistoryControllerV2Test.java @@ -87,12 +87,12 @@ void testListConfigHistory() throws Exception { page.setPagesAvailable(2); page.setPageItems(configHistoryInfoList); - when(historyService.listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1, 10)).thenReturn(page); + when(historyService.listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1, 10)).thenReturn(page); - Result> pageResult = historyControllerV2.listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1, - 10); + Result> pageResult = historyControllerV2.listConfigHistory(TEST_DATA_ID, TEST_GROUP, + TEST_NAMESPACE_ID, 1, 10); - verify(historyService).listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1, 10); + verify(historyService).listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1, 10); List resultList = pageResult.getData().getPageItems(); ConfigHistoryInfo resConfigHistoryInfo = resultList.get(0); @@ -123,12 +123,12 @@ void testListConfigHistoryWhenNameSpaceIsPublic() throws Exception { page.setPagesAvailable(2); page.setPageItems(configHistoryInfoList); - when(historyService.listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1, 10)).thenReturn(page); + when(historyService.listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1, 10)).thenReturn(page); Result> pageResult = historyControllerV2.listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1, 10); - verify(historyService).listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1, 10); + verify(historyService).listConfigHistory(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1, 10); List resultList = pageResult.getData().getPageItems(); ConfigHistoryInfo resConfigHistoryInfo = resultList.get(0); @@ -152,11 +152,13 @@ void testGetConfigHistoryInfoWhenNameSpaceIsPublic() throws Exception { configHistoryInfo.setCreatedTime(new Timestamp(new Date().getTime())); configHistoryInfo.setLastModifiedTime(new Timestamp(new Date().getTime())); - when(historyService.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L)).thenReturn(configHistoryInfo); + when(historyService.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L)).thenReturn( + configHistoryInfo); - Result result = historyControllerV2.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L); + Result result = historyControllerV2.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, + TEST_NAMESPACE_ID_PUBLIC, 1L); - verify(historyService).getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L); + verify(historyService).getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L); ConfigHistoryInfo resConfigHistoryInfo = result.getData(); @@ -178,11 +180,13 @@ void testGetConfigHistoryInfo() throws Exception { configHistoryInfo.setCreatedTime(new Timestamp(new Date().getTime())); configHistoryInfo.setLastModifiedTime(new Timestamp(new Date().getTime())); - when(historyService.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L)).thenReturn(configHistoryInfo); + when(historyService.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L)).thenReturn( + configHistoryInfo); - Result result = historyControllerV2.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L); + Result result = historyControllerV2.getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, + TEST_NAMESPACE_ID, 1L); - verify(historyService).getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L); + verify(historyService).getConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L); ConfigHistoryInfo resConfigHistoryInfo = result.getData(); @@ -204,12 +208,13 @@ void testGetPreviousConfigHistoryInfo() throws Exception { configHistoryInfo.setCreatedTime(new Timestamp(new Date().getTime())); configHistoryInfo.setLastModifiedTime(new Timestamp(new Date().getTime())); - when(historyService.getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L)).thenReturn(configHistoryInfo); + when(historyService.getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L)).thenReturn( + configHistoryInfo); - Result result = historyControllerV2.getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, - 1L); + Result result = historyControllerV2.getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, + TEST_NAMESPACE_ID, 1L); - verify(historyService).getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L); + verify(historyService).getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L); ConfigHistoryInfo resConfigHistoryInfo = result.getData(); @@ -231,12 +236,13 @@ void testGetPreviousConfigHistoryInfoWhenNameSpaceIsPublic() throws Exception { configHistoryInfo.setCreatedTime(new Timestamp(new Date().getTime())); configHistoryInfo.setLastModifiedTime(new Timestamp(new Date().getTime())); - when(historyService.getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L)).thenReturn(configHistoryInfo); + when(historyService.getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L)).thenReturn( + configHistoryInfo); Result result = historyControllerV2.getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L); - verify(historyService).getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID, 1L); + verify(historyService).getPreviousConfigHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_NAMESPACE_ID_PUBLIC, 1L); ConfigHistoryInfo resConfigHistoryInfo = result.getData(); @@ -276,9 +282,9 @@ void testGetConfigListByNamespaceWhenIsPublic() throws NacosApiException { configInfoWrapper.setContent("test"); List configInfoWrappers = Collections.singletonList(configInfoWrapper); - when(historyService.getConfigListByNamespace(TEST_NAMESPACE_ID)).thenReturn(configInfoWrappers); + when(historyService.getConfigListByNamespace(TEST_NAMESPACE_ID_PUBLIC)).thenReturn(configInfoWrappers); Result> result = historyControllerV2.getConfigsByTenant(TEST_NAMESPACE_ID_PUBLIC); - verify(historyService).getConfigListByNamespace(TEST_NAMESPACE_ID); + verify(historyService).getConfigListByNamespace(TEST_NAMESPACE_ID_PUBLIC); assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); List actualList = result.getData(); @@ -288,4 +294,5 @@ void testGetConfigListByNamespaceWhenIsPublic() throws NacosApiException { assertEquals(configInfoWrapper.getGroup(), actualConfigInfoWrapper.getGroup()); assertEquals(configInfoWrapper.getContent(), actualConfigInfoWrapper.getContent()); } + } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/model/ConfigCacheFactoryDelegateTest.java b/config/src/test/java/com/alibaba/nacos/config/server/model/ConfigCacheFactoryDelegateTest.java new file mode 100644 index 00000000000..e377a25e669 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/model/ConfigCacheFactoryDelegateTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Constructor; +import java.util.Collections; + +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ConfigCacheFactoryDelegateTest { + + MockedStatic envUtilMockedStatic; + + MockedStatic nacosServiceLoaderMockedStatic; + + @Mock + NacosConfigCacheFactory nacosConfigCacheFactory; + + @BeforeEach + void setUp() { + envUtilMockedStatic = mockStatic(EnvUtil.class); + nacosServiceLoaderMockedStatic = mockStatic(NacosServiceLoader.class); + } + + @AfterEach + void tearDown() { + envUtilMockedStatic.close(); + nacosServiceLoaderMockedStatic.close(); + } + + @Test + public void test() { + nacosServiceLoaderMockedStatic.when(() -> NacosServiceLoader.load(ConfigCacheFactory.class)) + .thenReturn(Collections.singletonList(nacosConfigCacheFactory)); + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")).thenReturn("lalala"); + ConfigCache configCache = ConfigCacheFactoryDelegate.getInstance().createConfigCache(); + ConfigCache configCache1 = ConfigCacheFactoryDelegate.getInstance().createConfigCache("md5", 123456789L); + ConfigCacheGray configCacheGray = ConfigCacheFactoryDelegate.getInstance().createConfigCacheGray("grayName"); + ConfigCacheGray configCacheGray1 = ConfigCacheFactoryDelegate.getInstance().createConfigCacheGray(); + verify(nacosConfigCacheFactory, times(0)).createConfigCache(); + verify(nacosConfigCacheFactory, times(0)).createConfigCacheGray(); + } + + @Test + public void test2() throws Exception { + when(nacosConfigCacheFactory.getName()).thenReturn("nacos"); + when(nacosConfigCacheFactory.createConfigCache()).thenReturn(new ConfigCache()); + when(nacosConfigCacheFactory.createConfigCacheGray()).thenReturn(new ConfigCacheGray()); + nacosServiceLoaderMockedStatic.when(() -> NacosServiceLoader.load(ConfigCacheFactory.class)) + .thenReturn(Collections.singletonList(nacosConfigCacheFactory)); + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")).thenReturn("nacos"); + Constructor constructor = ConfigCacheFactoryDelegate.class.getDeclaredConstructor(); + constructor.setAccessible(true); + ConfigCacheFactoryDelegate configCacheFactoryDelegate = (ConfigCacheFactoryDelegate) constructor.newInstance(); + configCacheFactoryDelegate.createConfigCache(); + configCacheFactoryDelegate.createConfigCache("md5", 123456789L); + configCacheFactoryDelegate.createConfigCacheGray("grayName"); + configCacheFactoryDelegate.createConfigCacheGray(); + verify(nacosConfigCacheFactory, times(2)).createConfigCache(); + verify(nacosConfigCacheFactory, times(2)).createConfigCacheGray(); + } +} \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessorDelegateTest.java b/config/src/test/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessorDelegateTest.java new file mode 100644 index 00000000000..a68d78d5b40 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/model/ConfigCachePostProcessorDelegateTest.java @@ -0,0 +1,95 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Modifier; +import java.util.Collections; + +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ConfigCachePostProcessorDelegateTest { + + MockedConstruction mockedConstruction; + + MockedStatic envUtilMockedStatic; + + MockedStatic nacosServiceLoaderMockedStatic; + + @Mock + public NacosConfigCachePostProcessor mockConfigCacheMd5PostProcessor; + + @BeforeEach + void setUp() { + envUtilMockedStatic = mockStatic(EnvUtil.class); + nacosServiceLoaderMockedStatic = mockStatic(NacosServiceLoader.class); + } + + @AfterEach + void tearDown() { + envUtilMockedStatic.close(); + nacosServiceLoaderMockedStatic.close(); + } + + @Test + void test1() { + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")).thenReturn("lalala"); + nacosServiceLoaderMockedStatic.when(() -> NacosServiceLoader.load(ConfigCachePostProcessor.class)) + .thenReturn(Collections.singletonList(mockConfigCacheMd5PostProcessor)); + ConfigCachePostProcessorDelegate.getInstance().postProcess(null, null); + verify(mockConfigCacheMd5PostProcessor, times(0)).postProcess(null, null); + } + + @Test + void test2() + throws NoSuchFieldException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException { + when(mockConfigCacheMd5PostProcessor.getName()).thenReturn("nacos"); + doNothing().when(mockConfigCacheMd5PostProcessor).postProcess(null, null); + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")).thenReturn("nacos"); + nacosServiceLoaderMockedStatic.when(() -> NacosServiceLoader.load(ConfigCachePostProcessor.class)) + .thenReturn(Collections.singletonList(mockConfigCacheMd5PostProcessor)); + Constructor constructor = ConfigCachePostProcessorDelegate.class.getDeclaredConstructor(); + constructor.setAccessible(true); + Field field = ConfigCachePostProcessorDelegate.class.getDeclaredField("INSTANCE"); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + field.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + ConfigCachePostProcessorDelegate delegate = (ConfigCachePostProcessorDelegate) constructor.newInstance(); + field.set(null, delegate); + ConfigCachePostProcessorDelegate.getInstance().postProcess(null, null); + verify(mockConfigCacheMd5PostProcessor, times(1)).postProcess(null, null); + } +} \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/model/NacosConfigCacheFactoryTest.java b/config/src/test/java/com/alibaba/nacos/config/server/model/NacosConfigCacheFactoryTest.java new file mode 100644 index 00000000000..5c768056f13 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/model/NacosConfigCacheFactoryTest.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class NacosConfigCacheFactoryTest { + + @Test + public void testCreateConfigCache() { + NacosConfigCacheFactory nacosConfigCacheFactory = new NacosConfigCacheFactory(); + ConfigCache configCache = nacosConfigCacheFactory.createConfigCache(); + assertEquals(ConfigCache.class, configCache.getClass()); + ConfigCacheGray configCacheGray = nacosConfigCacheFactory.createConfigCacheGray(); + assertEquals(ConfigCacheGray.class, configCacheGray.getClass()); + } + + @Test + public void testGetName() { + NacosConfigCacheFactory nacosConfigCacheFactory = new NacosConfigCacheFactory(); + assertEquals("nacos", nacosConfigCacheFactory.getName()); + } +} \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/model/NacosConfigCachePostProcessorTest.java b/config/src/test/java/com/alibaba/nacos/config/server/model/NacosConfigCachePostProcessorTest.java new file mode 100644 index 00000000000..47c53c19439 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/model/NacosConfigCachePostProcessorTest.java @@ -0,0 +1,31 @@ +/* + * Copyright 1999-2024 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.config.server.model; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class NacosConfigCachePostProcessorTest { + + @Test + public void test() { + NacosConfigCachePostProcessor nacosConfigCacheMd5PostProcessor = new NacosConfigCachePostProcessor(); + assertEquals("nacos", nacosConfigCacheMd5PostProcessor.getName()); + nacosConfigCacheMd5PostProcessor.postProcess(null, null); + } +} \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandlerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandlerTest.java index 81e7aad105d..15771287e6a 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandlerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigChangeClusterSyncRequestHandlerTest.java @@ -21,6 +21,7 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.api.remote.response.ResponseCode; +import com.alibaba.nacos.config.server.service.ConfigGrayModelMigrateService; import com.alibaba.nacos.config.server.service.dump.DumpService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -31,6 +32,8 @@ import java.io.IOException; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) class ConfigChangeClusterSyncRequestHandlerTest { @@ -40,9 +43,13 @@ class ConfigChangeClusterSyncRequestHandlerTest { @Mock private DumpService dumpService; + @Mock + private ConfigGrayModelMigrateService configGrayModelMigrateService; + @BeforeEach void setUp() throws IOException { - configChangeClusterSyncRequestHandler = new ConfigChangeClusterSyncRequestHandler(dumpService); + configChangeClusterSyncRequestHandler = new ConfigChangeClusterSyncRequestHandler(dumpService, + configGrayModelMigrateService); } @Test @@ -59,4 +66,41 @@ void testHandle() throws NacosException { configChangeSyncRequest, meta); assertEquals(configChangeClusterSyncResponse.getResultCode(), ResponseCode.SUCCESS.getCode()); } + + @Test + void testHandleBetaCompatibleFromOldServer() throws NacosException { + ConfigChangeClusterSyncRequest configChangeSyncRequest = new ConfigChangeClusterSyncRequest(); + configChangeSyncRequest.setRequestId(""); + configChangeSyncRequest.setDataId("dataId"); + configChangeSyncRequest.setGroup("group123"); + configChangeSyncRequest.setTenant("tenant..."); + configChangeSyncRequest.setLastModified(1L); + configChangeSyncRequest.setBeta(true); + RequestMeta meta = new RequestMeta(); + meta.setClientIp("1.1.1.1"); + ConfigChangeClusterSyncResponse configChangeClusterSyncResponse = configChangeClusterSyncRequestHandler.handle( + configChangeSyncRequest, meta); + verify(configGrayModelMigrateService, times(1)).checkMigrateBeta(configChangeSyncRequest.getDataId(), + configChangeSyncRequest.getGroup(), configChangeSyncRequest.getTenant()); + assertEquals(configChangeClusterSyncResponse.getResultCode(), ResponseCode.SUCCESS.getCode()); + } + + @Test + void testHandleOldCompatibleFromOldServer() throws NacosException { + ConfigChangeClusterSyncRequest configChangeSyncRequest = new ConfigChangeClusterSyncRequest(); + configChangeSyncRequest.setRequestId(""); + configChangeSyncRequest.setDataId("dataId"); + configChangeSyncRequest.setGroup("group123"); + configChangeSyncRequest.setTenant("tenant..."); + configChangeSyncRequest.setTag("tag1234"); + configChangeSyncRequest.setLastModified(1L); + RequestMeta meta = new RequestMeta(); + meta.setClientIp("1.1.1.1"); + ConfigChangeClusterSyncResponse configChangeClusterSyncResponse = configChangeClusterSyncRequestHandler.handle( + configChangeSyncRequest, meta); + verify(configGrayModelMigrateService, times(1)).checkMigrateTag(configChangeSyncRequest.getDataId(), + configChangeSyncRequest.getGroup(), configChangeSyncRequest.getTenant(), + configChangeSyncRequest.getTag()); + assertEquals(configChangeClusterSyncResponse.getResultCode(), ResponseCode.SUCCESS.getCode()); + } } \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandlerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandlerTest.java index d5ffb5c0a38..f4074c41e6e 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandlerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigPublishRequestHandlerTest.java @@ -29,8 +29,10 @@ import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; import com.alibaba.nacos.config.server.model.ConfigOperateResult; import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; -import com.alibaba.nacos.config.server.service.AggrWhitelist; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.service.ConfigOperationService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.persistence.configuration.DatasourceConfiguration; @@ -49,9 +51,9 @@ import java.util.concurrent.atomic.AtomicReference; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; @@ -67,7 +69,8 @@ class ConfigPublishRequestHandlerTest { @Mock ConfigInfoTagPersistService configInfoTagPersistService; - MockedStatic aggrWhitelistMockedStatic; + @Mock + ConfigInfoGrayPersistService configInfoGrayPersistService; MockedStatic envUtilMockedStatic; @@ -75,18 +78,16 @@ class ConfigPublishRequestHandlerTest { @BeforeEach void setUp() { - aggrWhitelistMockedStatic = Mockito.mockStatic(AggrWhitelist.class); envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); - - configPublishRequestHandler = new ConfigPublishRequestHandler(configInfoPersistService, configInfoTagPersistService, - configInfoBetaPersistService); + ConfigOperationService configOperationService = new ConfigOperationService(configInfoPersistService, + configInfoTagPersistService, configInfoBetaPersistService, configInfoGrayPersistService); + configPublishRequestHandler = new ConfigPublishRequestHandler(configOperationService); DatasourceConfiguration.setEmbeddedStorage(false); } @AfterEach void after() { - aggrWhitelistMockedStatic.close(); envUtilMockedStatic.close(); } @@ -149,8 +150,6 @@ public Class subscribeType() { assertEquals(group, reference.get().group); assertEquals(tenant, reference.get().tenant); assertEquals(timestamp, reference.get().lastModifiedTs); - assertFalse(reference.get().isBatch); - assertFalse(reference.get().isBeta); } @@ -203,8 +202,8 @@ public Class subscribeType() { long id = timestamp / 1000; configOperateResult.setId(id); configOperateResult.setLastModified(timestamp); - when(configInfoPersistService.insertOrUpdateCas(eq(requestMeta.getClientIp()), eq(srcUser), any(ConfigInfo.class), - any(Map.class))).thenReturn(configOperateResult); + when(configInfoPersistService.insertOrUpdateCas(eq(requestMeta.getClientIp()), eq(srcUser), + any(ConfigInfo.class), any(Map.class))).thenReturn(configOperateResult); ConfigPublishResponse response = configPublishRequestHandler.handle(configPublishRequest, requestMeta); assertEquals(ResponseCode.SUCCESS.getCode(), response.getResultCode()); @@ -214,8 +213,6 @@ public Class subscribeType() { assertEquals(group, reference.get().group); assertEquals(tenant, reference.get().tenant); assertEquals(timestamp, reference.get().lastModifiedTs); - assertFalse(reference.get().isBatch); - assertFalse(reference.get().isBeta); } /** @@ -272,8 +269,8 @@ public Class subscribeType() { long id = timestamp / 1000; configOperateResult.setId(id); configOperateResult.setLastModified(timestamp); - when(configInfoPersistService.insertOrUpdateCas(eq(requestMeta.getClientIp()), eq(srcUser), any(ConfigInfo.class), - any(Map.class))).thenThrow(new NacosRuntimeException(502, "mock error")); + when(configInfoPersistService.insertOrUpdateCas(eq(requestMeta.getClientIp()), eq(srcUser), + any(ConfigInfo.class), any(Map.class))).thenThrow(new NacosRuntimeException(502, "mock error")); ConfigPublishResponse response = configPublishRequestHandler.handle(configPublishRequest, requestMeta); assertEquals(ResponseCode.FAIL.getCode(), response.getResultCode()); @@ -283,48 +280,6 @@ public Class subscribeType() { } - @Test - void testPublishAggrCheckFail() throws NacosException, InterruptedException { - - RequestMeta requestMeta = new RequestMeta(); - String clientIp = "127.0.0.1"; - requestMeta.setClientIp(clientIp); - - String dataId = "testPublishAggrCheckFail"; - String group = "group"; - String tenant = "tenant"; - String content = "content"; - ConfigPublishRequest configPublishRequest = new ConfigPublishRequest(); - configPublishRequest.setDataId(dataId); - configPublishRequest.setGroup(group); - configPublishRequest.setTenant(tenant); - configPublishRequest.setContent(content); - when(AggrWhitelist.isAggrDataId(eq(dataId))).thenReturn(Boolean.TRUE); - - AtomicReference reference = new AtomicReference<>(); - NotifyCenter.registerSubscriber(new Subscriber() { - - @Override - public void onEvent(Event event) { - ConfigDataChangeEvent event1 = (ConfigDataChangeEvent) event; - if (event1.dataId.equals(dataId)) { - reference.set((ConfigDataChangeEvent) event); - } - } - - @Override - public Class subscribeType() { - return ConfigDataChangeEvent.class; - } - }); - ConfigPublishResponse response = configPublishRequestHandler.handle(configPublishRequest, requestMeta); - - assertEquals(ResponseCode.FAIL.getCode(), response.getResultCode()); - assertTrue(response.getMessage().contains("is aggr")); - Thread.sleep(500L); - assertTrue(reference.get() == null); - } - @Test void testBetaPublishNotCas() throws NacosException, InterruptedException { String dataId = "testBetaPublish"; @@ -370,8 +325,10 @@ public Class subscribeType() { long id = timestamp / 1000; configOperateResult.setId(id); configOperateResult.setLastModified(timestamp); - when(configInfoBetaPersistService.insertOrUpdateBeta(any(ConfigInfo.class), eq(betaIps), eq(requestMeta.getClientIp()), - eq(srcUser))).thenReturn(configOperateResult); + when(configInfoBetaPersistService.insertOrUpdateBeta(any(ConfigInfo.class), eq(betaIps), + eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGray(any(ConfigInfo.class), eq(BetaGrayRule.TYPE_BETA), + anyString(), eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(configOperateResult); ConfigPublishResponse response = configPublishRequestHandler.handle(configPublishRequest, requestMeta); assertEquals(ResponseCode.SUCCESS.getCode(), response.getResultCode()); @@ -381,8 +338,7 @@ public Class subscribeType() { assertEquals(group, reference.get().group); assertEquals(tenant, reference.get().tenant); assertEquals(timestamp, reference.get().lastModifiedTs); - assertFalse(reference.get().isBatch); - assertTrue(reference.get().isBeta); + assertEquals("beta", reference.get().grayName); } @@ -432,8 +388,10 @@ public Class subscribeType() { long id = timestamp / 1000; configOperateResult.setId(id); configOperateResult.setLastModified(timestamp); - when(configInfoBetaPersistService.insertOrUpdateBetaCas(any(ConfigInfo.class), eq(betaIps), eq(requestMeta.getClientIp()), - eq(srcUser))).thenReturn(configOperateResult); + when(configInfoBetaPersistService.insertOrUpdateBetaCas(any(ConfigInfo.class), eq(betaIps), + eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGrayCas(any(ConfigInfo.class), eq(BetaGrayRule.TYPE_BETA), + anyString(), eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(configOperateResult); ConfigPublishResponse response = configPublishRequestHandler.handle(configPublishRequest, requestMeta); assertEquals(ResponseCode.SUCCESS.getCode(), response.getResultCode()); @@ -443,8 +401,8 @@ public Class subscribeType() { assertEquals(group, reference.get().group); assertEquals(tenant, reference.get().tenant); assertEquals(timestamp, reference.get().lastModifiedTs); - assertFalse(reference.get().isBatch); - assertTrue(reference.get().isBeta); + assertEquals(tenant, reference.get().tenant); + assertEquals("beta", reference.get().grayName); } @@ -495,9 +453,10 @@ public Class subscribeType() { long id = timestamp / 1000; configOperateResult.setId(id); configOperateResult.setLastModified(timestamp); - when(configInfoTagPersistService.insertOrUpdateTag(any(ConfigInfo.class), eq(tag), eq(requestMeta.getClientIp()), - eq(srcUser))).thenReturn(configOperateResult); - + when(configInfoTagPersistService.insertOrUpdateTag(any(ConfigInfo.class), eq(tag), + eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGray(any(ConfigInfo.class), eq("tag_" + tag), anyString(), + eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(configOperateResult); ConfigPublishResponse response = configPublishRequestHandler.handle(configPublishRequest, requestMeta); assertEquals(ResponseCode.SUCCESS.getCode(), response.getResultCode()); @@ -507,9 +466,8 @@ public Class subscribeType() { assertEquals(group, reference.get().group); assertEquals(tenant, reference.get().tenant); assertEquals(timestamp, reference.get().lastModifiedTs); - assertFalse(reference.get().isBatch); - assertFalse(reference.get().isBeta); - assertEquals(tag, reference.get().tag); + + assertEquals("tag_" + tag, reference.get().grayName); } @@ -554,9 +512,10 @@ public Class subscribeType() { long id = timestamp / 1000; configOperateResult.setId(id); configOperateResult.setLastModified(timestamp); - when(configInfoTagPersistService.insertOrUpdateTagCas(any(ConfigInfo.class), eq(tag), eq(requestMeta.getClientIp()), - eq(srcUser))).thenReturn(configOperateResult); - + when(configInfoTagPersistService.insertOrUpdateTagCas(any(ConfigInfo.class), eq(tag), + eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGrayCas(any(ConfigInfo.class), eq("tag_" + tag), anyString(), + eq(requestMeta.getClientIp()), eq(srcUser))).thenReturn(configOperateResult); ConfigPublishResponse response = configPublishRequestHandler.handle(configPublishRequest, requestMeta); assertEquals(ResponseCode.SUCCESS.getCode(), response.getResultCode()); @@ -566,10 +525,7 @@ public Class subscribeType() { assertEquals(group, reference.get().group); assertEquals(tenant, reference.get().tenant); assertEquals(timestamp, reference.get().lastModifiedTs); - assertFalse(reference.get().isBatch); - assertFalse(reference.get().isBeta); - assertEquals(tag, reference.get().tag); - + assertEquals("tag_" + tag, reference.get().grayName); } } \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java index 6467ad8e948..f0eeb784681 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigQueryRequestHandlerTest.java @@ -16,12 +16,18 @@ package com.alibaba.nacos.config.server.remote; +import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.config.remote.request.ConfigQueryRequest; import com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse; import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.config.server.model.CacheItem; -import com.alibaba.nacos.config.server.model.ConfigCache; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.ConfigGrayPersistInfo; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; +import com.alibaba.nacos.config.server.model.gray.TagGrayRule; +import com.alibaba.nacos.config.server.service.query.ConfigQueryChainService; import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; import com.alibaba.nacos.config.server.service.dump.disk.ConfigRocksDbDiskService; @@ -30,6 +36,7 @@ import com.alibaba.nacos.sys.env.EnvUtil; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.MockedStatic; @@ -38,7 +45,6 @@ import org.springframework.core.env.StandardEnvironment; import java.io.IOException; -import java.util.Arrays; import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; import static com.alibaba.nacos.api.config.remote.response.ConfigQueryResponse.CONFIG_NOT_FOUND; @@ -49,7 +55,9 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.when; +// todo open the test case @ExtendWith(MockitoExtension.class) +@Disabled class ConfigQueryRequestHandlerTest { static MockedStatic configCacheServiceMockedStatic; @@ -82,8 +90,8 @@ void setUp() throws IOException { configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class); propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class); configDiskServiceFactoryMockedStatic = Mockito.mockStatic(ConfigDiskServiceFactory.class); - configQueryRequestHandler = new ConfigQueryRequestHandler(); - final String groupKey = GroupKey2.getKey(dataId, group, ""); + configQueryRequestHandler = new ConfigQueryRequestHandler(new ConfigQueryChainService()); + final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID); when(ConfigCacheService.tryConfigReadLock(groupKey)).thenReturn(1); propertyUtilMockedStatic.when(PropertyUtil::getMaxContent).thenReturn(1024 * 1000); @@ -97,14 +105,13 @@ void setUp() throws IOException { @Test void testGetNormal() throws Exception { - final String groupKey = GroupKey2.getKey(dataId, group, ""); + final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID); String content = "content_from_notdirectreadÄãºÃ" + System.currentTimeMillis(); ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class); when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService); CacheItem cacheItem = new CacheItem(groupKey); - cacheItem.getConfigCache().setMd5Gbk(MD5Utils.md5Hex(content, "GBK")); - cacheItem.getConfigCache().setMd5Utf8(MD5Utils.md5Hex(content, "UTF-8")); + cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8")); cacheItem.getConfigCache().setEncryptedDataKey("key_testGetNormal_NotDirectRead"); when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem); @@ -114,7 +121,7 @@ void testGetNormal() throws Exception { RequestMeta requestMeta = new RequestMeta(); requestMeta.setClientIp("127.0.0.1"); - when(configRocksDbDiskService.getContent(eq(dataId), eq(group), eq(null))).thenReturn(content); + when(configRocksDbDiskService.getContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID))).thenReturn(content); ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta); assertEquals(content, response.getContent()); assertEquals(MD5Utils.md5Hex(content, "UTF-8"), response.getMd5()); @@ -135,19 +142,20 @@ void testGetNormal() throws Exception { @Test void testGetBeta() throws Exception { - final String groupKey = GroupKey2.getKey(dataId, group, ""); + final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID); ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class); when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService); CacheItem cacheItem = new CacheItem(groupKey); - cacheItem.initBetaCacheIfEmpty(); + cacheItem.initConfigGrayIfEmpty(BetaGrayRule.TYPE_BETA); String content = "content_from_beta_notdirectreadÄãºÃ" + System.currentTimeMillis(); - cacheItem.getConfigCacheBeta().setMd5Gbk(MD5Utils.md5Hex(content, "GBK")); - cacheItem.getConfigCacheBeta().setMd5Utf8(MD5Utils.md5Hex(content, "UTF-8")); - cacheItem.getConfigCacheBeta().setEncryptedDataKey("key_testGetBeta_NotDirectRead"); - cacheItem.setBeta(true); - cacheItem.setIps4Beta(Arrays.asList("127.0.0.1")); - + ConfigCacheGray configCacheGrayBeta = cacheItem.getConfigCacheGray().get(BetaGrayRule.TYPE_BETA); + configCacheGrayBeta.setMd5(MD5Utils.md5Hex(content, "UTF-8")); + configCacheGrayBeta.setEncryptedDataKey("key_testGetBeta_NotDirectRead"); + ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(BetaGrayRule.TYPE_BETA, + BetaGrayRule.VERSION, "127.0.0.1", -1000); + configCacheGrayBeta.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo)); + cacheItem.sortConfigGray(); when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem); ConfigQueryRequest configQueryRequest = new ConfigQueryRequest(); @@ -156,7 +164,8 @@ void testGetBeta() throws Exception { RequestMeta requestMeta = new RequestMeta(); requestMeta.setClientIp("127.0.0.1"); - when(configRocksDbDiskService.getBetaContent(eq(dataId), eq(group), eq(null))).thenReturn(content); + when(configRocksDbDiskService.getGrayContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID), + eq(BetaGrayRule.TYPE_BETA))).thenReturn(content); ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta); //check content&md5 assertEquals(content, response.getContent()); @@ -175,14 +184,13 @@ void testGetBeta() throws Exception { @Test void testGetTagNotFound() throws Exception { - final String groupKey = GroupKey2.getKey(dataId, group, ""); + final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID); String content = "content_from_tag_withtagÄãºÃ" + System.currentTimeMillis(); ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class); when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService); CacheItem cacheItem = new CacheItem(groupKey); - cacheItem.getConfigCache().setMd5Gbk(MD5Utils.md5Hex(content, "GBK")); - cacheItem.getConfigCache().setMd5Utf8(MD5Utils.md5Hex(content, "UTF-8")); + cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8")); cacheItem.getConfigCache().setEncryptedDataKey("key_testGetTag_NotFound"); when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem); @@ -201,9 +209,9 @@ void testGetTagNotFound() throws Exception { //check content&md5 assertNull(response.getContent()); - assertNull(response.getMd5()); + assertEquals(MD5Utils.md5Hex(content, "UTF-8"), response.getMd5()); assertEquals(CONFIG_NOT_FOUND, response.getErrorCode()); - assertNull(response.getEncryptedDataKey()); + assertEquals("key_testGetTag_NotFound", response.getEncryptedDataKey()); //check flags. assertFalse(response.isBeta()); @@ -219,26 +227,27 @@ void testGetTagNotFound() throws Exception { @Test void testGetTagWithTag() throws Exception { - final String groupKey = GroupKey2.getKey(dataId, group, ""); + final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID); String content = "content_from_tag_notdirectreadÄãºÃ" + System.currentTimeMillis(); ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class); when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService); CacheItem cacheItem = new CacheItem(groupKey); - cacheItem.getConfigCache().setMd5Gbk(MD5Utils.md5Hex(content, "GBK")); - cacheItem.getConfigCache().setMd5Utf8(MD5Utils.md5Hex(content, "UTF-8")); + cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8")); cacheItem.getConfigCache().setEncryptedDataKey("key_formal"); - ConfigCache configCacheTag = new ConfigCache(); + String specificTag = "specific_tag"; + cacheItem.initConfigGrayIfEmpty(TagGrayRule.TYPE_TAG + "_" + specificTag); + ConfigCacheGray configCacheGrayTag = cacheItem.getConfigCacheGray() + .get(TagGrayRule.TYPE_TAG + "_" + specificTag); String tagContent = "content_from_specific_tag_directreadÄãºÃ" + System.currentTimeMillis(); - configCacheTag.setMd5Gbk(MD5Utils.md5Hex(tagContent, "GBK")); - configCacheTag.setMd5Utf8(MD5Utils.md5Hex(tagContent, "UTF-8")); - configCacheTag.setEncryptedDataKey("key_testGetTag_NotDirectRead"); - cacheItem.initConfigTagsIfEmpty(); + configCacheGrayTag.setMd5(MD5Utils.md5Hex(tagContent, "UTF-8")); + configCacheGrayTag.setEncryptedDataKey("key_testGetTag_NotDirectRead"); + ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(TagGrayRule.TYPE_TAG, + TagGrayRule.VERSION, specificTag, -999); + configCacheGrayTag.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo)); + cacheItem.sortConfigGray(); //specific tag to get - String specificTag = "specific_tag"; - //just for compare. - cacheItem.getConfigCacheTags().put(specificTag, configCacheTag); when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem); ConfigQueryRequest configQueryRequest = new ConfigQueryRequest(); @@ -251,7 +260,8 @@ void testGetTagWithTag() throws Exception { requestMeta.setClientIp("127.0.0.1"); //mock disk read. - when(configRocksDbDiskService.getTagContent(eq(dataId), eq(group), eq(null), eq(specificTag))).thenReturn(tagContent); + when(configRocksDbDiskService.getGrayContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID), + eq(TagGrayRule.TYPE_TAG + "_" + specificTag))).thenReturn(tagContent); ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta); //check content&md5 @@ -272,33 +282,34 @@ void testGetTagWithTag() throws Exception { @Test void testGetTagAutoTag() throws Exception { - final String groupKey = GroupKey2.getKey(dataId, group, ""); + final String groupKey = GroupKey2.getKey(dataId, group, Constants.DEFAULT_NAMESPACE_ID); String content = "content_from_tag_notdirectreadÄãºÃ" + System.currentTimeMillis(); ConfigRocksDbDiskService configRocksDbDiskService = Mockito.mock(ConfigRocksDbDiskService.class); when(ConfigDiskServiceFactory.getInstance()).thenReturn(configRocksDbDiskService); + String autoTag = "auto_tag"; CacheItem cacheItem = new CacheItem(groupKey); - cacheItem.getConfigCache().setMd5Gbk(MD5Utils.md5Hex(content, "GBK")); - cacheItem.getConfigCache().setMd5Utf8(MD5Utils.md5Hex(content, "UTF-8")); - ConfigCache configCacheTag = new ConfigCache(); + cacheItem.initConfigGrayIfEmpty(TagGrayRule.TYPE_TAG + "_" + autoTag); + cacheItem.getConfigCache().setMd5(MD5Utils.md5Hex(content, "UTF-8")); + ConfigCacheGray configCacheGrayTag = cacheItem.getConfigCacheGray().get(TagGrayRule.TYPE_TAG + "_" + autoTag); String tagContent = "content_from_specific_tag_directreadÄãºÃ" + System.currentTimeMillis(); - configCacheTag.setMd5Gbk(MD5Utils.md5Hex(tagContent, "GBK")); - configCacheTag.setMd5Utf8(MD5Utils.md5Hex(tagContent, "UTF-8")); - configCacheTag.setEncryptedDataKey("key_testGetTag_AutoTag_NotDirectRead"); - cacheItem.initConfigTagsIfEmpty(); - String autoTag = "auto_tag"; - cacheItem.getConfigCacheTags().put(autoTag, configCacheTag); + configCacheGrayTag.setMd5(MD5Utils.md5Hex(tagContent, "UTF-8")); + configCacheGrayTag.setEncryptedDataKey("key_testGetTag_AutoTag_NotDirectRead"); + ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(TagGrayRule.TYPE_TAG, + TagGrayRule.VERSION, autoTag, -999); + configCacheGrayTag.resetGrayRule(GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo)); + cacheItem.sortConfigGray(); when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(cacheItem); ConfigQueryRequest configQueryRequest = new ConfigQueryRequest(); configQueryRequest.setDataId(dataId); configQueryRequest.setGroup(group); - configQueryRequest.putHeader(VIPSERVER_TAG, autoTag); RequestMeta requestMeta = new RequestMeta(); requestMeta.setClientIp("127.0.0.1"); - + requestMeta.getAppLabels().put(VIPSERVER_TAG, autoTag); //mock disk read. - when(configRocksDbDiskService.getTagContent(eq(dataId), eq(group), eq(null), eq(autoTag))).thenReturn(tagContent); + when(configRocksDbDiskService.getGrayContent(eq(dataId), eq(group), eq(Constants.DEFAULT_NAMESPACE_ID), + eq(TagGrayRule.TYPE_TAG + "_" + autoTag))).thenReturn(tagContent); ConfigQueryResponse response = configQueryRequestHandler.handle(configQueryRequest, requestMeta); //check content&md5 @@ -324,8 +335,8 @@ void testGetConfigNotExistAndConflict() throws Exception { String group = "group" + System.currentTimeMillis(); String tenant = "tenant" + System.currentTimeMillis(); //test config not exist - configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))) - .thenReturn(0); + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))).thenReturn(0); final String groupKey = GroupKey2.getKey(dataId, group, tenant); when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(null); @@ -345,8 +356,8 @@ void testGetConfigNotExistAndConflict() throws Exception { //test config conflict when(ConfigCacheService.getContentCache(eq(groupKey))).thenReturn(new CacheItem(groupKey)); - configCacheServiceMockedStatic.when(() -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))) - .thenReturn(-1); + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.tryConfigReadLock(GroupKey2.getKey(dataId, group, tenant))).thenReturn(-1); ConfigQueryResponse responseConflict = configQueryRequestHandler.handle(configQueryRequest, requestMeta); assertEquals(ConfigQueryResponse.CONFIG_QUERY_CONFLICT, responseConflict.getErrorCode()); assertNull(responseConflict.getContent()); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandlerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandlerTest.java index f489b112a83..8d05297ccff 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandlerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/remote/ConfigRemoveRequestHandlerTest.java @@ -21,8 +21,8 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.api.remote.response.ResponseCode; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -43,11 +43,12 @@ class ConfigRemoveRequestHandlerTest { private ConfigInfoPersistService configInfoPersistService; @Mock - private ConfigInfoTagPersistService configInfoTagPersistService; + private ConfigInfoGrayPersistService configInfoGrayPersistService; @BeforeEach void setUp() throws Exception { - configRemoveRequestHandler = new ConfigRemoveRequestHandler(configInfoPersistService, configInfoTagPersistService); + configRemoveRequestHandler = new ConfigRemoveRequestHandler(configInfoPersistService, + configInfoGrayPersistService); Mockito.mockStatic(ConfigTraceService.class); } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/remote/RpcConfigChangeNotifierTest.java b/config/src/test/java/com/alibaba/nacos/config/server/remote/RpcConfigChangeNotifierTest.java index 98e0806b46f..a838db8d737 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/remote/RpcConfigChangeNotifierTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/remote/RpcConfigChangeNotifierTest.java @@ -131,7 +131,7 @@ void testOnDataEvent() throws InterruptedException { //mock push tps passed Mockito.when(tpsControlManager.check(any(TpsCheckRequest.class))).thenReturn(new TpsCheckResponse(true, 200, "success")); - rpcConfigChangeNotifier.onEvent(new LocalDataChangeEvent(groupKey, true, betaIps)); + rpcConfigChangeNotifier.onEvent(new LocalDataChangeEvent(groupKey)); //wait rpc push executed. Thread.sleep(50L); //expect rpc push task run. diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/AggrWhitelistTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/AggrWhitelistTest.java deleted file mode 100644 index dbda0ad4bf9..00000000000 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/AggrWhitelistTest.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * 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.config.server.service; - -import org.junit.jupiter.api.Test; - -import java.util.ArrayList; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -class AggrWhitelistTest { - - @Test - void testIsAggrDataId() { - List list = new ArrayList(); - list.add("com.taobao.jiuren.*"); - list.add("NS_NACOS_SUBSCRIPTION_TOPIC_*"); - list.add("com.taobao.tae.AppListOnGrid-*"); - AggrWhitelist.compile(list); - - assertFalse(AggrWhitelist.isAggrDataId("com.abc")); - assertFalse(AggrWhitelist.isAggrDataId("com.taobao.jiuren")); - assertFalse(AggrWhitelist.isAggrDataId("com.taobao.jiurenABC")); - assertTrue(AggrWhitelist.isAggrDataId("com.taobao.jiuren.abc")); - assertTrue(AggrWhitelist.isAggrDataId("NS_NACOS_SUBSCRIPTION_TOPIC_abc")); - assertTrue(AggrWhitelist.isAggrDataId("com.taobao.tae.AppListOnGrid-abc")); - } -} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java index edb32244646..b8b79b60c15 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/ClientTrackServiceTest.java @@ -17,9 +17,13 @@ package com.alibaba.nacos.config.server.service; import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.MockedStatic; +import org.mockito.Mockito; import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.context.web.WebAppConfiguration; @@ -31,9 +35,19 @@ @WebAppConfiguration class ClientTrackServiceTest { + MockedStatic envUtilMockedStatic; + @BeforeEach void before() { ClientTrackService.clientRecords.clear(); + envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")) + .thenReturn("nacos"); + } + + @AfterEach + void after() { + envUtilMockedStatic.close(); } @Test @@ -43,8 +57,9 @@ void testTrackClientMd5() { String group = "online"; String groupKey = GroupKey2.getKey(dataId, group); String md5 = "xxxxxxxxxxxxx"; + String content = "test"; - ConfigCacheService.updateMd5(groupKey, md5, System.currentTimeMillis(), ""); + ConfigCacheService.updateMd5(groupKey, md5, content, System.currentTimeMillis(), ""); ClientTrackService.trackClientMd5(clientIp, groupKey, md5); ClientTrackService.trackClientMd5(clientIp, groupKey, md5); @@ -54,7 +69,7 @@ void testTrackClientMd5() { assertEquals(1, ClientTrackService.subscriberCount()); //服务端数据更新 - ConfigCacheService.updateMd5(groupKey, md5 + "111", System.currentTimeMillis(), ""); + ConfigCacheService.updateMd5(groupKey, md5 + "111", content, System.currentTimeMillis(), ""); assertFalse(ClientTrackService.isClientUptodate(clientIp).get(groupKey)); } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigCacheServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigCacheServiceTest.java index fccdb2e5b90..cfa563e88c3 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigCacheServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigCacheServiceTest.java @@ -18,7 +18,8 @@ import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.config.server.model.CacheItem; -import com.alibaba.nacos.config.server.model.ConfigCache; +import com.alibaba.nacos.config.server.model.ConfigCacheGray; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskService; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; import com.alibaba.nacos.config.server.utils.GroupKey2; @@ -37,8 +38,6 @@ import java.io.IOException; import java.lang.reflect.Field; -import java.util.Arrays; -import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -67,7 +66,8 @@ class ConfigCacheServiceTest { void before() { envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); configDiskServiceFactoryMockedStatic = Mockito.mockStatic(ConfigDiskServiceFactory.class); - configDiskServiceFactoryMockedStatic.when(() -> ConfigDiskServiceFactory.getInstance()).thenReturn(configDiskService); + configDiskServiceFactoryMockedStatic.when(() -> ConfigDiskServiceFactory.getInstance()) + .thenReturn(configDiskService); propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class); } @@ -92,12 +92,13 @@ void testDumpFormal() throws Exception { long ts = System.currentTimeMillis(); String type = "json"; String encryptedDataKey = "key12345"; - boolean result = ConfigCacheService.dumpWithMd5(dataId, group, tenant, content, md5, ts, type, encryptedDataKey); + boolean result = ConfigCacheService.dumpWithMd5(dataId, group, tenant, content, md5, ts, type, + encryptedDataKey); assertTrue(result); //verify cache. CacheItem contentCache1 = ConfigCacheService.getContentCache(groupKey); assertEquals(ts, contentCache1.getConfigCache().getLastModifiedTs()); - assertEquals(md5, contentCache1.getConfigCache().getMd5Utf8()); + assertEquals(md5, contentCache1.getConfigCache().getMd5()); assertEquals(type, contentCache1.getType()); assertEquals(encryptedDataKey, contentCache1.getConfigCache().getEncryptedDataKey()); Mockito.verify(configDiskService, times(1)).saveToDisk(eq(dataId), eq(group), eq(tenant), eq(content)); @@ -110,7 +111,7 @@ void testDumpFormal() throws Exception { Mockito.verify(configDiskService, times(1)).saveToDisk(eq(dataId), eq(group), eq(tenant), eq(contentNew)); assertEquals(newTs, contentCache1.getConfigCache().getLastModifiedTs()); String newMd5 = MD5Utils.md5Hex(contentNew, "UTF-8"); - assertEquals(newMd5, contentCache1.getConfigCache().getMd5Utf8()); + assertEquals(newMd5, contentCache1.getConfigCache().getMd5()); //modified ts old long oldTs2 = newTs - 123L; @@ -120,7 +121,7 @@ void testDumpFormal() throws Exception { Mockito.verify(configDiskService, times(0)).saveToDisk(eq(dataId), eq(group), eq(tenant), eq(contentWithOldTs)); //not change ts and md5 assertEquals(newTs, contentCache1.getConfigCache().getLastModifiedTs()); - assertEquals(newMd5, contentCache1.getConfigCache().getMd5Utf8()); + assertEquals(newMd5, contentCache1.getConfigCache().getMd5()); //modified ts new only long newTs2 = newTs + 123L; @@ -132,7 +133,8 @@ void testDumpFormal() throws Exception { .saveToDisk(anyString(), anyString(), anyString(), anyString()); try { long newTs3 = newTs2 + 123L; - boolean dumpErrorResult = ConfigCacheService.dump(dataId, group, tenant, contentNew + "234567", newTs3, type, encryptedDataKey); + boolean dumpErrorResult = ConfigCacheService.dump(dataId, group, tenant, contentNew + "234567", newTs3, + type, encryptedDataKey); envUtilMockedStatic.verify(() -> EnvUtil.systemExit(), times(1)); assertFalse(dumpErrorResult); } catch (Throwable throwable) { @@ -149,163 +151,100 @@ void testDumpFormal() throws Exception { } @Test - void testDumpBeta() throws Exception { + public void testDumpGray() throws Exception { String dataId = "dataIdtestDumpBetaNewCache123"; String group = "group11"; String tenant = "tenant112"; - String content = "mockContnet11"; + String grayName = "grayName"; + String grayRule = "{\"type\":\"tag\",\"version\":\"1.0.0\",\"expr\":\"dgray123\",\"priority\":1}"; + String content = "mockContent11"; + String md5 = MD5Utils.md5Hex(content, "UTF-8"); String groupKey = GroupKey2.getKey(dataId, group, tenant); String encryptedDataKey = "key12345"; - List betaIps = Arrays.asList("127.0.0.1", "127.0.0.2"); long ts = System.currentTimeMillis(); - //init beta cache - boolean result = ConfigCacheService.dumpBeta(dataId, group, tenant, content, ts, String.join(",", betaIps), encryptedDataKey); + //init gray cache + boolean result = ConfigCacheService.dumpGray(dataId, group, tenant, grayName, grayRule, content, ts, + encryptedDataKey); assertTrue(result); CacheItem contentCache = ConfigCacheService.getContentCache(groupKey); - assertEquals(md5, contentCache.getConfigCacheBeta().getMd5Utf8()); - assertEquals(ts, contentCache.getConfigCacheBeta().getLastModifiedTs()); - assertEquals(betaIps, contentCache.getIps4Beta()); - assertEquals(encryptedDataKey, contentCache.getConfigCacheBeta().getEncryptedDataKey()); - Mockito.verify(configDiskService, times(1)).saveBetaToDisk(eq(dataId), eq(group), eq(tenant), eq(content)); + assertEquals(md5, contentCache.getConfigCacheGray().get(grayName).getMd5()); + assertEquals(ts, contentCache.getConfigCacheGray().get(grayName).getLastModifiedTs()); + assertEquals(encryptedDataKey, contentCache.getConfigCacheGray().get(grayName).getEncryptedDataKey()); + Mockito.verify(configDiskService, times(1)) + .saveGrayToDisk(eq(dataId), eq(group), eq(tenant), eq(grayName), eq(content)); //ts newer ,md5 update long tsNew = System.currentTimeMillis(); String contentNew = content + tsNew; String md5New = MD5Utils.md5Hex(contentNew, "UTF-8"); - List betaIpsNew = Arrays.asList("127.0.0.1", "127.0.0.2", "127.0.0.3"); - boolean resultNew = ConfigCacheService.dumpBeta(dataId, group, tenant, contentNew, tsNew, String.join(",", betaIpsNew), + boolean resultNew = ConfigCacheService.dumpGray(dataId, group, tenant, grayName, grayRule, contentNew, tsNew, encryptedDataKey); assertTrue(resultNew); - assertEquals(md5New, contentCache.getConfigCacheBeta().getMd5Utf8()); - assertEquals(tsNew, contentCache.getConfigCacheBeta().getLastModifiedTs()); - assertEquals(encryptedDataKey, contentCache.getConfigCacheBeta().getEncryptedDataKey()); - assertEquals(betaIpsNew, contentCache.getIps4Beta()); - Mockito.verify(configDiskService, times(1)).saveBetaToDisk(eq(dataId), eq(group), eq(tenant), eq(contentNew)); + assertEquals(md5New, contentCache.getConfigCacheGray().get(grayName).getMd5()); + assertEquals(tsNew, contentCache.getConfigCacheGray().get(grayName).getLastModifiedTs()); + assertEquals(encryptedDataKey, contentCache.getConfigCacheGray().get(grayName).getEncryptedDataKey()); + Mockito.verify(configDiskService, times(1)) + .saveGrayToDisk(eq(dataId), eq(group), eq(tenant), eq(grayName), eq(contentNew)); //ts old ,md5 update long tsOld = tsNew - 1; String contentWithOldTs = "contentWithOldTs" + tsOld; - List betaIpsWithOldTs = Arrays.asList("127.0.0.1", "127.0.0.2", "127.0.0.4"); - boolean resultOld = ConfigCacheService.dumpBeta(dataId, group, tenant, contentWithOldTs, tsOld, String.join(",", betaIpsWithOldTs), - encryptedDataKey); + boolean resultOld = ConfigCacheService.dumpGray(dataId, group, tenant, grayName, grayRule, contentWithOldTs, + tsOld, encryptedDataKey); assertTrue(resultOld); - assertEquals(md5New, contentCache.getConfigCacheBeta().getMd5Utf8()); - assertEquals(tsNew, contentCache.getConfigCacheBeta().getLastModifiedTs()); - assertEquals(encryptedDataKey, contentCache.getConfigCacheBeta().getEncryptedDataKey()); - assertEquals(betaIpsNew, contentCache.getIps4Beta()); - Mockito.verify(configDiskService, times(0)).saveBetaToDisk(eq(dataId), eq(group), eq(tenant), eq(contentWithOldTs)); + assertEquals(md5New, contentCache.getConfigCacheGray().get(grayName).getMd5()); + assertEquals(tsNew, contentCache.getConfigCacheGray().get(grayName).getLastModifiedTs()); + assertEquals(encryptedDataKey, contentCache.getConfigCacheGray().get(grayName).getEncryptedDataKey()); + Mockito.verify(configDiskService, times(0)) + .saveGrayToDisk(eq(dataId), eq(group), eq(tenant), eq(grayName), eq(contentWithOldTs)); - //ts new ,md5 not update,beta ips list changes + //ts new ,md5 not update,grayRule changes long tsNew2 = tsNew + 1; + String grayRuleNew = "{\"type\":\"tag\",\"version\":\"1.0.0\",\"expr\":\"gray1234\",\"priority\":1}"; + String contentWithPrev = contentNew; - List betaIpsNew2 = Arrays.asList("127.0.0.1", "127.0.0.2", "127.0.0.4", "127.0.0.5"); - boolean resultNew2 = ConfigCacheService.dumpBeta(dataId, group, tenant, contentWithPrev, tsNew2, String.join(",", betaIpsNew2), - encryptedDataKey); + boolean resultNew2 = ConfigCacheService.dumpGray(dataId, group, tenant, grayName, grayRuleNew, contentWithPrev, + tsNew2, encryptedDataKey); assertTrue(resultNew2); - assertEquals(md5New, contentCache.getConfigCacheBeta().getMd5Utf8()); - assertEquals(tsNew2, contentCache.getConfigCacheBeta().getLastModifiedTs()); - assertEquals(encryptedDataKey, contentCache.getConfigCacheBeta().getEncryptedDataKey()); - assertEquals(betaIpsNew2, contentCache.getIps4Beta()); + assertEquals(md5New, contentCache.getConfigCacheGray().get(grayName).getMd5()); + assertEquals(tsNew2, contentCache.getConfigCacheGray().get(grayName).getLastModifiedTs()); + assertEquals(encryptedDataKey, contentCache.getConfigCacheGray().get(grayName).getEncryptedDataKey()); + assertEquals(GrayRuleManager.constructGrayRule(GrayRuleManager.deserializeConfigGrayPersistInfo(grayRuleNew)), + contentCache.getConfigCacheGray().get(grayName).getGrayRule()); //ts new only,md5 not update,beta ips not change long tsNew3 = tsNew2 + 1; String contentWithPrev2 = contentNew; - List betaIpsNew3 = betaIpsNew2; - boolean resultNew3 = ConfigCacheService.dumpBeta(dataId, group, tenant, contentWithPrev2, tsNew3, String.join(",", betaIpsNew3), - encryptedDataKey); + String grayRulePrev = grayRuleNew; + boolean resultNew3 = ConfigCacheService.dumpGray(dataId, group, tenant, grayName, grayRulePrev, + contentWithPrev2, tsNew3, encryptedDataKey); assertTrue(resultNew3); - assertEquals(md5New, contentCache.getConfigCacheBeta().getMd5Utf8()); - assertEquals(tsNew3, contentCache.getConfigCacheBeta().getLastModifiedTs()); - assertEquals(encryptedDataKey, contentCache.getConfigCacheBeta().getEncryptedDataKey()); - assertEquals(betaIpsNew2, contentCache.getIps4Beta()); + assertEquals(md5New, contentCache.getConfigCacheGray().get(grayName).getMd5()); + assertEquals(tsNew3, contentCache.getConfigCacheGray().get(grayName).getLastModifiedTs()); + assertEquals(encryptedDataKey, contentCache.getConfigCacheGray().get(grayName).getEncryptedDataKey()); + assertEquals(GrayRuleManager.constructGrayRule(GrayRuleManager.deserializeConfigGrayPersistInfo(grayRuleNew)), + contentCache.getConfigCacheGray().get(grayName).getGrayRule()); //ts not update,md5 not update,beta ips not change long tsNew4 = tsNew3; String contentWithPrev4 = contentNew; - List betaIpsNew4 = betaIpsNew2; - boolean resultNew4 = ConfigCacheService.dumpBeta(dataId, group, tenant, contentWithPrev4, tsNew4, String.join(",", betaIpsNew4), - encryptedDataKey); + boolean resultNew4 = ConfigCacheService.dumpGray(dataId, group, tenant, grayName, grayRulePrev, + contentWithPrev4, tsNew4, encryptedDataKey); assertTrue(resultNew4); - assertEquals(md5New, contentCache.getConfigCacheBeta().getMd5Utf8()); - assertEquals(tsNew3, contentCache.getConfigCacheBeta().getLastModifiedTs()); - assertEquals(encryptedDataKey, contentCache.getConfigCacheBeta().getEncryptedDataKey()); - assertEquals(betaIpsNew4, contentCache.getIps4Beta()); + assertEquals(md5New, contentCache.getConfigCacheGray().get(grayName).getMd5()); + assertEquals(tsNew3, contentCache.getConfigCacheGray().get(grayName).getLastModifiedTs()); + assertEquals(encryptedDataKey, contentCache.getConfigCacheGray().get(grayName).getEncryptedDataKey()); + assertEquals(GrayRuleManager.constructGrayRule(GrayRuleManager.deserializeConfigGrayPersistInfo(grayRuleNew)), + contentCache.getConfigCacheGray().get(grayName).getGrayRule()); //test remove - boolean removeBeta = ConfigCacheService.removeBeta(dataId, group, tenant); + boolean removeBeta = ConfigCacheService.removeGray(dataId, group, tenant, grayName); assertTrue(removeBeta); - Mockito.verify(configDiskService, times(1)).removeConfigInfo4Beta(dataId, group, tenant); - ConfigCache betaCacheAfterRemove = ConfigCacheService.getContentCache(groupKey).getConfigCacheBeta(); - assertNull(betaCacheAfterRemove); - } - - @Test - void testDumpTag() throws Exception { - String dataId = "dataIdtestDumpTag133323"; - String group = "group11"; - String tenant = "tenant112"; - String content = "mockContnet11"; - String tag = "tag12345"; - String groupKey = GroupKey2.getKey(dataId, group, tenant); - String encryptedDataKey = "key12345"; - long ts = System.currentTimeMillis(); - - //init dump tag - boolean dumpTagResult = ConfigCacheService.dumpTag(dataId, group, tenant, tag, content, ts, encryptedDataKey); - assertTrue(dumpTagResult); - Mockito.verify(configDiskService, times(1)).saveTagToDisk(eq(dataId), eq(group), eq(tenant), eq(tag), eq(content)); - CacheItem contentCache = ConfigCacheService.getContentCache(groupKey); - ConfigCache configCacheTag = contentCache.getConfigCacheTags().get(tag); - assertEquals(ts, configCacheTag.getLastModifiedTs()); - String md5 = MD5Utils.md5Hex(content, "UTF-8"); - assertEquals(md5, configCacheTag.getMd5Utf8()); - - //ts newer ,md5 update - long tsNew = System.currentTimeMillis(); - String contentNew = content + tsNew; - String md5New = MD5Utils.md5Hex(contentNew, "UTF-8"); - boolean resultNew = ConfigCacheService.dumpTag(dataId, group, tenant, tag, contentNew, tsNew, encryptedDataKey); - assertTrue(resultNew); - assertEquals(md5New, configCacheTag.getMd5Utf8()); - assertEquals(tsNew, configCacheTag.getLastModifiedTs()); - assertEquals(encryptedDataKey, configCacheTag.getEncryptedDataKey()); - Mockito.verify(configDiskService, times(1)).saveTagToDisk(eq(dataId), eq(group), eq(tenant), eq(tag), eq(contentNew)); - - //ts old ,md5 update - long tsOld = tsNew - 1; - String contentWithOldTs = "contentWithOldTs" + tsOld; - boolean resultOld = ConfigCacheService.dumpTag(dataId, group, tenant, tag, contentWithOldTs, tsOld, encryptedDataKey); - assertTrue(resultOld); - assertEquals(md5New, configCacheTag.getMd5Utf8()); - assertEquals(tsNew, configCacheTag.getLastModifiedTs()); - assertEquals(encryptedDataKey, configCacheTag.getEncryptedDataKey()); - Mockito.verify(configDiskService, times(0)).saveTagToDisk(eq(dataId), eq(group), eq(tenant), eq(tag), eq(contentWithOldTs)); - - //ts new only,md5 not update - long tsNew2 = tsNew + 1; - String contentWithPrev2 = contentNew; - boolean resultNew2 = ConfigCacheService.dumpTag(dataId, group, tenant, tag, contentWithPrev2, tsNew2, encryptedDataKey); - assertTrue(resultNew2); - assertEquals(md5New, configCacheTag.getMd5Utf8()); - assertEquals(tsNew2, configCacheTag.getLastModifiedTs()); - assertEquals(encryptedDataKey, configCacheTag.getEncryptedDataKey()); - - //ts not update,md5 not update - long tsNew3 = tsNew2; - String contentWithPrev3 = contentNew; - boolean resultNew3 = ConfigCacheService.dumpTag(dataId, group, tenant, tag, contentWithPrev3, tsNew3, encryptedDataKey); - assertTrue(resultNew3); - assertEquals(md5New, configCacheTag.getMd5Utf8()); - assertEquals(tsNew3, configCacheTag.getLastModifiedTs()); - assertEquals(encryptedDataKey, configCacheTag.getEncryptedDataKey()); - - //test remove - boolean removeTag = ConfigCacheService.removeTag(dataId, group, tenant, tag); - assertTrue(removeTag); - Mockito.verify(configDiskService, times(1)).removeConfigInfo4Tag(dataId, group, tenant, tag); - Map configCacheTags = ConfigCacheService.getContentCache(groupKey).getConfigCacheTags(); - assertNull(configCacheTags); + Mockito.verify(configDiskService, times(1)).removeConfigInfo4Gray(dataId, group, tenant, grayName); + Map grayCacheAfterRemove = ConfigCacheService.getContentCache(groupKey) + .getConfigCacheGray(); + assertNull(grayCacheAfterRemove); } @Test diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigChangePublisherTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigChangePublisherTest.java index 79f4e065631..aaa0aaebd1a 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigChangePublisherTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigChangePublisherTest.java @@ -62,7 +62,8 @@ public Class subscribeType() { EnvUtil.setIsStandalone(true); DatasourceConfiguration.setEmbeddedStorage(true); - ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent("chuntaojun", "chuntaojun", System.currentTimeMillis())); + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent("chuntaojun", "chuntaojun", null, System.currentTimeMillis())); Thread.sleep(2000); assertNotNull(reference.get()); reference.set(null); @@ -70,7 +71,8 @@ public Class subscribeType() { // nacos is standalone mode and use external storage EnvUtil.setIsStandalone(true); DatasourceConfiguration.setEmbeddedStorage(false); - ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent("chuntaojun", "chuntaojun", System.currentTimeMillis())); + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent("chuntaojun", "chuntaojun", null, System.currentTimeMillis())); Thread.sleep(2000); assertNotNull(reference.get()); reference.set(null); @@ -78,7 +80,8 @@ public Class subscribeType() { // nacos is cluster mode and use embedded storage EnvUtil.setIsStandalone(false); DatasourceConfiguration.setEmbeddedStorage(true); - ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent("chuntaojun", "chuntaojun", System.currentTimeMillis())); + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent("chuntaojun", "chuntaojun", null, System.currentTimeMillis())); Thread.sleep(2000); assertNull(reference.get()); reference.set(null); @@ -86,7 +89,8 @@ public Class subscribeType() { // nacos is cluster mode and use external storage EnvUtil.setIsStandalone(false); DatasourceConfiguration.setEmbeddedStorage(false); - ConfigChangePublisher.notifyConfigChange(new ConfigDataChangeEvent("chuntaojun", "chuntaojun", System.currentTimeMillis())); + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent("chuntaojun", "chuntaojun", null, System.currentTimeMillis())); Thread.sleep(2000); assertNotNull(reference.get()); reference.set(null); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigOperationServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigOperationServiceTest.java index 7451b7214dc..48414cd2098 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigOperationServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/ConfigOperationServiceTest.java @@ -22,6 +22,7 @@ import com.alibaba.nacos.config.server.model.ConfigRequestInfo; import com.alibaba.nacos.config.server.model.form.ConfigForm; import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.sys.env.EnvUtil; @@ -34,6 +35,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -59,15 +61,18 @@ class ConfigOperationServiceTest { @Mock private ConfigInfoBetaPersistService configInfoBetaPersistService; + @Mock + private ConfigInfoGrayPersistService configInfoGrayPersistService; + @BeforeEach void setUp() throws Exception { EnvUtil.setEnvironment(new StandardEnvironment()); this.configOperationService = new ConfigOperationService(configInfoPersistService, configInfoTagPersistService, - configInfoBetaPersistService); + configInfoBetaPersistService, configInfoGrayPersistService); } @Test - void testPublishConfig() throws NacosException { + void testPublishConfigBeta() throws NacosException { ConfigForm configForm = new ConfigForm(); configForm.setDataId("test"); configForm.setGroup("test"); @@ -75,55 +80,115 @@ void testPublishConfig() throws NacosException { ConfigRequestInfo configRequestInfo = new ConfigRequestInfo(); - // if betaIps is blank, tag is blank and casMd5 is blank - when(configInfoPersistService.insertOrUpdate(any(), any(), any(ConfigInfo.class), any())).thenReturn(new ConfigOperateResult()); - Boolean aResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); - verify(configInfoPersistService).insertOrUpdate(any(), any(), any(ConfigInfo.class), any()); - assertTrue(aResult); + configRequestInfo.setCasMd5(""); + configForm.setTag(""); - // if betaIps is blank, tag is blank and casMd5 is not blank + // if betaIps is not blank and casMd5 is blank + configRequestInfo.setBetaIps("test-betaIps"); + when(configInfoBetaPersistService.insertOrUpdateBeta(any(ConfigInfo.class), eq("test-betaIps"), any(), + any())).thenReturn(new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGray(any(ConfigInfo.class), eq("beta"), anyString(), + eq(configRequestInfo.getSrcIp()), eq(configForm.getSrcUser()))).thenReturn(new ConfigOperateResult()); + Boolean eResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); + verify(configInfoBetaPersistService).insertOrUpdateBeta(any(ConfigInfo.class), eq("test-betaIps"), any(), + any()); + assertTrue(eResult); + + } + + @Test + void testPublishConfigBetaCas() throws NacosException { + ConfigForm configForm = new ConfigForm(); + configForm.setDataId("test"); + configForm.setGroup("test"); + configForm.setContent("test content"); + + ConfigRequestInfo configRequestInfo = new ConfigRequestInfo(); + + configRequestInfo.setCasMd5("casMd5"); + configForm.setTag(""); + + // if betaIps is not blank and casMd5 is not blank + configRequestInfo.setBetaIps("test-betaIps"); configRequestInfo.setCasMd5("test casMd5"); - when(configInfoPersistService.insertOrUpdateCas(any(), any(), any(ConfigInfo.class), any())).thenReturn(new ConfigOperateResult()); - Boolean bResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); - verify(configInfoPersistService).insertOrUpdateCas(any(), any(), any(ConfigInfo.class), any()); - assertTrue(bResult); + when(configInfoBetaPersistService.insertOrUpdateBetaCas(any(ConfigInfo.class), eq("test-betaIps"), any(), + any())).thenReturn(new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGrayCas(any(ConfigInfo.class), eq("beta"), anyString(), + eq(configRequestInfo.getSrcIp()), eq(configForm.getSrcUser()))).thenReturn(new ConfigOperateResult()); + Boolean fResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); + verify(configInfoBetaPersistService).insertOrUpdateBetaCas(any(ConfigInfo.class), eq("test-betaIps"), any(), + any()); + assertTrue(fResult); + } + + @Test + void testPublishConfigTag() throws NacosException { + ConfigForm configForm = new ConfigForm(); + configForm.setDataId("test"); + configForm.setGroup("test"); + configForm.setContent("test content"); + + ConfigRequestInfo configRequestInfo = new ConfigRequestInfo(); + configRequestInfo.setCasMd5(""); + String tag = "testTag"; + configForm.setTag(tag); - // if betaIps is blank, tag is not blank and casMd5 is blank - configForm.setTag("test tag"); - when(configInfoTagPersistService.insertOrUpdateTag(any(ConfigInfo.class), eq("test tag"), any(), any())).thenReturn( + when(configInfoTagPersistService.insertOrUpdateTag(any(ConfigInfo.class), eq(tag), any(), any())).thenReturn( new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGray(any(ConfigInfo.class), eq("tag_" + tag), anyString(), + eq(configRequestInfo.getSrcIp()), eq(configForm.getSrcUser()))).thenReturn(new ConfigOperateResult()); Boolean cResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); - verify(configInfoTagPersistService).insertOrUpdateTag(any(ConfigInfo.class), eq("test tag"), any(), any()); + verify(configInfoTagPersistService).insertOrUpdateTag(any(ConfigInfo.class), eq(tag), any(), any()); assertTrue(cResult); - // if betaIps is blank, tag is not blank and casMd5 is not blank - configForm.setTag("test tag"); - configRequestInfo.setCasMd5("test casMd5"); - when(configInfoTagPersistService.insertOrUpdateTagCas(any(ConfigInfo.class), eq("test tag"), any(), any())).thenReturn( + } + + @Test + void testPublishConfigTagCas() throws NacosException { + ConfigForm configForm = new ConfigForm(); + configForm.setDataId("test"); + configForm.setGroup("test"); + configForm.setContent("test content"); + + ConfigRequestInfo configRequestInfo = new ConfigRequestInfo(); + + configRequestInfo.setCasMd5("casMd5"); + String tag = "testTag"; + configForm.setTag(tag); + when(configInfoTagPersistService.insertOrUpdateTagCas(any(ConfigInfo.class), eq(tag), any(), any())).thenReturn( new ConfigOperateResult()); + when(configInfoGrayPersistService.insertOrUpdateGrayCas(any(ConfigInfo.class), eq("tag_" + tag), anyString(), + eq(configRequestInfo.getSrcIp()), eq(configForm.getSrcUser()))).thenReturn(new ConfigOperateResult()); Boolean dResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); - verify(configInfoTagPersistService).insertOrUpdateTagCas(any(ConfigInfo.class), eq("test tag"), any(), any()); + verify(configInfoTagPersistService).insertOrUpdateTagCas(any(ConfigInfo.class), eq(tag), any(), any()); assertTrue(dResult); - configRequestInfo.setCasMd5(""); - configForm.setTag(""); + } + + @Test + void testPublishConfig() throws NacosException { + ConfigForm configForm = new ConfigForm(); + configForm.setDataId("test"); + configForm.setGroup("test"); + configForm.setContent("test content"); - // if betaIps is not blank and casMd5 is blank - configRequestInfo.setBetaIps("test-betaIps"); - when(configInfoBetaPersistService.insertOrUpdateBeta(any(ConfigInfo.class), eq("test-betaIps"), any(), any())).thenReturn( + ConfigRequestInfo configRequestInfo = new ConfigRequestInfo(); + + // if betaIps is blank, tag is blank and casMd5 is blank + when(configInfoPersistService.insertOrUpdate(any(), any(), any(ConfigInfo.class), any())).thenReturn( new ConfigOperateResult()); - Boolean eResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); - verify(configInfoBetaPersistService).insertOrUpdateBeta(any(ConfigInfo.class), eq("test-betaIps"), any(), any()); - assertTrue(eResult); + Boolean aResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); + verify(configInfoPersistService).insertOrUpdate(any(), any(), any(ConfigInfo.class), any()); + assertTrue(aResult); - // if betaIps is not blank and casMd5 is not blank - configRequestInfo.setBetaIps("test-betaIps"); + // if betaIps is blank, tag is blank and casMd5 is not blank configRequestInfo.setCasMd5("test casMd5"); - when(configInfoBetaPersistService.insertOrUpdateBetaCas(any(ConfigInfo.class), eq("test-betaIps"), any(), any())).thenReturn( + when(configInfoPersistService.insertOrUpdateCas(any(), any(), any(ConfigInfo.class), any())).thenReturn( new ConfigOperateResult()); - Boolean fResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); - verify(configInfoBetaPersistService).insertOrUpdateBetaCas(any(ConfigInfo.class), eq("test-betaIps"), any(), any()); - assertTrue(fResult); + Boolean bResult = configOperationService.publishConfig(configForm, configRequestInfo, ""); + verify(configInfoPersistService).insertOrUpdateCas(any(), any(), any(ConfigInfo.class), any()); + assertTrue(bResult); + configRequestInfo.setCasMd5(""); } @Test @@ -136,7 +201,8 @@ void testDeleteConfig() { // if tag is not blank Boolean bResult = configOperationService.deleteConfig("test", "test", "", "test", "1.1.1.1", "test"); - verify(configInfoTagPersistService).removeConfigInfoTag(eq("test"), eq("test"), eq(""), eq("test"), any(), any()); + verify(configInfoTagPersistService).removeConfigInfoTag(eq("test"), eq("test"), eq(""), eq("test"), any(), + any()); assertTrue(bResult); } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/HistoryServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/HistoryServiceTest.java index 0d2b29e8f7c..c813392d5be 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/HistoryServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/HistoryServiceTest.java @@ -16,8 +16,13 @@ package com.alibaba.nacos.config.server.service; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.enums.OperationType; import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfoDetail; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.persistence.model.Page; @@ -54,6 +59,14 @@ class HistoryServiceTest { private static final String TEST_TENANT = ""; private static final String TEST_CONTENT = "test config"; + + private static final String TEST_UPDATED_CONTENT = "test config updated"; + + private static final String TEST_OP_TYPE = OperationType.UPDATE.getValue(); + + private static final String TEST_MD5 = "77963b7a931377ad4ab5ad6a9cd718aa"; + + private static final String TEST_UPDATED_MD5 = "3ba1e44fa18519221f6c70afc0e8ae84"; private HistoryService historyService; @@ -62,10 +75,13 @@ class HistoryServiceTest { @Mock private ConfigInfoPersistService configInfoPersistService; + + @Mock + private ConfigInfoGrayPersistService configInfoGrayPersistService; @BeforeEach void setUp() throws Exception { - this.historyService = new HistoryService(historyConfigInfoPersistService, configInfoPersistService); + this.historyService = new HistoryService(historyConfigInfoPersistService, configInfoPersistService, configInfoGrayPersistService); } @Test @@ -166,4 +182,47 @@ void testGetConfigListByNamespace() { assertEquals(configInfoWrapper.getGroup(), actualConfigInfoWrapper.getGroup()); assertEquals(configInfoWrapper.getContent(), actualConfigInfoWrapper.getContent()); } + + @Test + void testGetConfigHistoryInfoPair() throws Exception { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId(TEST_DATA_ID); + configHistoryInfo.setGroup(TEST_GROUP); + configHistoryInfo.setContent(TEST_CONTENT); + configHistoryInfo.setTenant(TEST_TENANT); + configHistoryInfo.setOpType(TEST_OP_TYPE); + configHistoryInfo.setMd5(TEST_MD5); + configHistoryInfo.setPublishType(Constants.FORMAL); + configHistoryInfo.setGrayName(StringUtils.EMPTY); + configHistoryInfo.setCreatedTime(new Timestamp(new Date().getTime())); + configHistoryInfo.setLastModifiedTime(new Timestamp(new Date().getTime())); + + when(historyConfigInfoPersistService.detailConfigHistory(1L)).thenReturn(configHistoryInfo); + + ConfigHistoryInfo nextHistoryInfo = new ConfigHistoryInfo(); + nextHistoryInfo.setDataId(TEST_DATA_ID); + nextHistoryInfo.setGroup(TEST_GROUP); + nextHistoryInfo.setTenant(TEST_TENANT); + nextHistoryInfo.setOpType(TEST_OP_TYPE); + nextHistoryInfo.setMd5(TEST_UPDATED_MD5); + nextHistoryInfo.setContent(TEST_UPDATED_CONTENT); + nextHistoryInfo.setPublishType(Constants.FORMAL); + nextHistoryInfo.setGrayName(StringUtils.EMPTY); + nextHistoryInfo.setCreatedTime(new Timestamp(new Date().getTime())); + nextHistoryInfo.setLastModifiedTime(new Timestamp(new Date().getTime())); + + when(historyConfigInfoPersistService.getNextHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_TENANT, Constants.FORMAL, + StringUtils.EMPTY, 1L)).thenReturn(nextHistoryInfo); + + ConfigHistoryInfoDetail resConfigHistoryInfoDetail = historyService.getConfigHistoryInfoDetail(TEST_DATA_ID, TEST_GROUP, + TEST_TENANT, 1L); + + verify(historyConfigInfoPersistService).getNextHistoryInfo(TEST_DATA_ID, TEST_GROUP, TEST_TENANT, Constants.FORMAL, + StringUtils.EMPTY, 1L); + + assertEquals(nextHistoryInfo.getDataId(), resConfigHistoryInfoDetail.getDataId()); + assertEquals(nextHistoryInfo.getGroup(), resConfigHistoryInfoDetail.getGroup()); + assertEquals(nextHistoryInfo.getMd5(), resConfigHistoryInfoDetail.getUpdatedMd5()); + assertEquals(nextHistoryInfo.getContent(), resConfigHistoryInfoDetail.getUpdatedContent()); + } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/LongPollingServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/LongPollingServiceTest.java index 2d4524db25b..5a7b3f6e532 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/LongPollingServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/LongPollingServiceTest.java @@ -84,7 +84,6 @@ void before() { connectionControlManagerMockedStatic = Mockito.mockStatic(ControlManagerCenter.class); connectionControlManagerMockedStatic.when(() -> ControlManagerCenter.getInstance()).thenReturn(controlManagerCenter); Mockito.when(controlManagerCenter.getConnectionControlManager()).thenReturn(connectionControlManager); - } @AfterEach @@ -111,6 +110,9 @@ void testAddLongPollingClientHasNotEqualsMd5() throws IOException { clientMd5Map.put(groupKeyEquals, md5Equals0); String md5NotEquals1 = MD5Utils.md5Hex("countNotEquals", "UTF-8"); clientMd5Map.put(groupKeyNotEquals, md5NotEquals1); + MockedStatic md5UtilMockedStatic = Mockito.mockStatic(MD5Util.class); + md5UtilMockedStatic.when(() -> MD5Util.compareMd5(any(), any(), any())) + .thenReturn(Arrays.asList(groupKeyNotEquals)); HttpServletRequest httpServletRequest = Mockito.mock(HttpServletRequest.class); Mockito.when(httpServletRequest.getHeader(eq(LongPollingService.LONG_POLLING_NO_HANG_UP_HEADER))).thenReturn(null); @@ -131,7 +133,7 @@ void testAddLongPollingClientHasNotEqualsMd5() throws IOException { //expect print not equals group Mockito.verify(printWriter, times(1)).println(eq(responseString)); Mockito.verify(httpServletResponse, times(1)).setStatus(eq(HttpServletResponse.SC_OK)); - + md5UtilMockedStatic.close(); } @Test diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorkerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorkerTest.java index cde308bce33..2ebb02fd5fd 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorkerTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpChangeConfigWorkerTest.java @@ -105,8 +105,7 @@ void after() throws IllegalAccessException { dynamicDataSourceMockedStatic.close(); envUtilMockedStatic.close(); ConfigDiskServiceFactory.getInstance().clearAll(); - ConfigDiskServiceFactory.getInstance().clearAllBeta(); - ConfigDiskServiceFactory.getInstance().clearAllTag(); + ConfigDiskServiceFactory.getInstance().clearAllGray(); Field[] declaredFields = ConfigDiskServiceFactory.class.getDeclaredFields(); for (Field filed : declaredFields) { @@ -121,7 +120,7 @@ void after() throws IllegalAccessException { void testDumpChangeIfOff() { PropertyUtil.setDumpChangeOn(false); dumpChangeConfigWorker.run(); - Mockito.verify(historyConfigInfoPersistService, times(0)).findDeletedConfig(any(), anyLong(), anyInt()); + Mockito.verify(historyConfigInfoPersistService, times(0)).findDeletedConfig(any(), anyLong(), anyInt(), any()); } @Test @@ -141,7 +140,7 @@ void testDumpChangeOfDeleteConfigs() { assertEquals("encrykey" + 1, ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataIdPrefix + 1, "group" + 1, "tenant" + 1)).getConfigCache() .getEncryptedDataKey()); - Mockito.when(historyConfigInfoPersistService.findDeletedConfig(eq(startTime), eq(0L), eq(3))).thenReturn(firstPageDeleted); + Mockito.when(historyConfigInfoPersistService.findDeletedConfig(eq(startTime), eq(0L), eq(3), eq("formal"))).thenReturn(firstPageDeleted); //mock delete config query is null Mockito.when(configInfoPersistService.findConfigInfoState(eq(dataIdPrefix + 1), eq("group" + 1), eq("tenant" + 1))) .thenReturn(null); @@ -150,7 +149,7 @@ void testDumpChangeOfDeleteConfigs() { dumpChangeConfigWorker.run(); //expect delete page return pagesize and will select second page - Mockito.verify(historyConfigInfoPersistService, times(1)).findDeletedConfig(eq(startTime), eq(3L), eq(3)); + Mockito.verify(historyConfigInfoPersistService, times(1)).findDeletedConfig(eq(startTime), eq(3L), eq(3), eq("formal")); //expect cache to be cleared. assertNull(ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataIdPrefix + 1, "group" + 1, "tenant" + 1))); } @@ -189,7 +188,7 @@ void testDumpChangeOfChangedConfigsNewTimestampOverride() { .getLastModifiedTs()); assertEquals(MD5Utils.md5Hex(configInfoWrapperNewForId1.getContent(), "UTF-8"), ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataIdPrefix + 1, "group" + 1, "tenant" + 1)).getConfigCache() - .getMd5Utf8()); + .getMd5()); } @Test @@ -225,7 +224,7 @@ void testDumpChangeOfChangedConfigsNewTimestampEqualMd5() { .getLastModifiedTs()); assertEquals(MD5Utils.md5Hex(configInfoWrapperNewForId1.getContent(), "UTF-8"), ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataIdPrefix + 1, "group" + 1, "tenant" + 1)).getConfigCache() - .getMd5Utf8()); + .getMd5()); } @@ -264,7 +263,7 @@ void testDumpChangeOfChangedConfigsOldTimestamp() { .getLastModifiedTs()); assertEquals(MD5Utils.md5Hex("content" + 1, "UTF-8"), ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataIdPrefix + 1, "group" + 1, "tenant" + 1)).getConfigCache() - .getMd5Utf8()); + .getMd5()); } @@ -303,7 +302,7 @@ void testDumpChangeOfChangedConfigsEqualsTimestampMd5Update() { .getLastModifiedTs()); assertEquals(MD5Utils.md5Hex(configInfoWrapperNewForId1.getContent(), "UTF-8"), ConfigCacheService.getContentCache(GroupKey.getKeyTenant(dataIdPrefix + 1, "group" + 1, "tenant" + 1)).getConfigCache() - .getMd5Utf8()); + .getMd5()); } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpChangeGrayConfigWorkerTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpChangeGrayConfigWorkerTest.java new file mode 100644 index 00000000000..f1f825241e5 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpChangeGrayConfigWorkerTest.java @@ -0,0 +1,119 @@ +/* + * 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.config.server.service.dump; + +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; +import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.config.server.utils.ConfigExecutor; +import com.alibaba.nacos.config.server.utils.GroupKey; +import com.alibaba.nacos.config.server.utils.PropertyUtil; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +public class DumpChangeGrayConfigWorkerTest { + + DumpChangeGrayConfigWorker dumpGrayConfigWorker; + + @Mock + ConfigInfoGrayPersistService configInfoGrayPersistService; + + @Mock + HistoryConfigInfoPersistService historyConfigInfoPersistService; + + static MockedStatic envUtilMockedStatic; + + static MockedStatic configCacheServiceMockedStatic; + + static MockedStatic configExecutorMockedStatic; + + + /** + * Clean up. + */ + @AfterEach + public void after() { + envUtilMockedStatic.close(); + configCacheServiceMockedStatic.close(); + configExecutorMockedStatic.close(); + + } + + @BeforeEach + public void setUp() { + envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); + configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class); + configExecutorMockedStatic = Mockito.mockStatic(ConfigExecutor.class); + + envUtilMockedStatic.when(() -> EnvUtil.getAvailableProcessors(anyInt())).thenReturn(2); + dumpGrayConfigWorker = new DumpChangeGrayConfigWorker(configInfoGrayPersistService, + new Timestamp(System.currentTimeMillis()), historyConfigInfoPersistService); + } + + @Test + public void testdumpGrayConfigWorkerRun() { + List mockList = new ArrayList<>(); + ConfigInfoGrayWrapper mock1 = mock(1); + mockList.add(mock1); + when(configInfoGrayPersistService.findChangeConfig(any(Timestamp.class), any(long.class), eq(100))).thenReturn( + mockList); + configCacheServiceMockedStatic.when(() -> ConfigCacheService.getContentMd5( + eq(GroupKey.getKeyTenant(mock1.getDataId(), mock1.getGroup(), mock1.getTenant())))).thenReturn(""); + + dumpGrayConfigWorker.run(); + //verify dump gray executed + configCacheServiceMockedStatic.verify( + () -> ConfigCacheService.dumpGray(eq(mock1.getDataId()), eq(mock1.getGroup()), eq(mock1.getTenant()), + eq(mock1.getGrayName()), eq(mock1.getGrayRule()), eq(mock1.getContent()), + eq(mock1.getLastModified()), eq(mock1.getEncryptedDataKey()))); + //verify task scheduled + configExecutorMockedStatic.verify(() -> ConfigExecutor.scheduleConfigChangeTask(any(DumpChangeGrayConfigWorker.class), + eq(PropertyUtil.getDumpChangeWorkerInterval()), eq(TimeUnit.MILLISECONDS))); + + } + + ConfigInfoGrayWrapper mock(int id) { + ConfigInfoGrayWrapper configInfoGrayWrapper = new ConfigInfoGrayWrapper(); + configInfoGrayWrapper.setDataId("mockdataid" + id); + configInfoGrayWrapper.setGroup("mockgroup" + id); + configInfoGrayWrapper.setTenant("tenant" + id); + configInfoGrayWrapper.setContent("content" + id); + configInfoGrayWrapper.setGrayName("graytags1" + id); + configInfoGrayWrapper.setGrayRule( + "{\"type\":\"tagv2\",\"version\":\"1.0.0\",\"expr\":\"middleware.server.key\\u003dgray123\",\"priority\":1}"); + return configInfoGrayWrapper; + } +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorTest.java index d01fc330d12..e3d8ccf93d8 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorTest.java @@ -18,8 +18,6 @@ import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.config.server.model.CacheItem; -import com.alibaba.nacos.config.server.model.ConfigInfoBetaWrapper; -import com.alibaba.nacos.config.server.model.ConfigInfoTagWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; import com.alibaba.nacos.config.server.service.ConfigCacheService; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskService; @@ -27,9 +25,8 @@ import com.alibaba.nacos.config.server.service.dump.disk.ConfigRocksDbDiskService; import com.alibaba.nacos.config.server.service.dump.processor.DumpProcessor; import com.alibaba.nacos.config.server.service.dump.task.DumpTask; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.persistence.datasource.DataSourceService; import com.alibaba.nacos.persistence.datasource.DynamicDataSource; @@ -46,7 +43,6 @@ import java.io.IOException; import java.lang.reflect.Field; -import java.util.Arrays; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -67,10 +63,7 @@ class DumpProcessorTest { ConfigInfoPersistService configInfoPersistService; @Mock - ConfigInfoBetaPersistService configInfoBetaPersistService; - - @Mock - ConfigInfoTagPersistService configInfoTagPersistService; + ConfigInfoGrayPersistService configInfoGrayPersistService; ExternalDumpService dumpService; @@ -85,14 +78,18 @@ void init() throws Exception { dynamicDataSourceMockedStatic = Mockito.mockStatic(DynamicDataSource.class); envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); when(EnvUtil.getNacosHome()).thenReturn(System.getProperty("user.home")); - when(EnvUtil.getProperty(eq(CommonConstant.NACOS_PLUGIN_DATASOURCE_LOG), eq(Boolean.class), eq(false))).thenReturn(false); + when(EnvUtil.getProperty(eq(CommonConstant.NACOS_PLUGIN_DATASOURCE_LOG), eq(Boolean.class), + eq(false))).thenReturn(false); + when(EnvUtil.getProperty(eq("memory_limit_file_path"), + eq("/sys/fs/cgroup/memory/memory.limit_in_bytes"))).thenReturn( + "/sys/fs/cgroup/memory/memory.limit_in_bytes"); + dynamicDataSourceMockedStatic.when(DynamicDataSource::getInstance).thenReturn(dynamicDataSource); when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); - dumpService = new ExternalDumpService(configInfoPersistService, null, null, null, configInfoBetaPersistService, - configInfoTagPersistService, null, null); - dumpProcessor = new DumpProcessor(configInfoPersistService, configInfoBetaPersistService, configInfoTagPersistService); + dumpService = new ExternalDumpService(configInfoPersistService, null, null, configInfoGrayPersistService, null); + dumpProcessor = new DumpProcessor(configInfoPersistService, configInfoGrayPersistService); Field[] declaredFields = ConfigDiskServiceFactory.class.getDeclaredFields(); for (Field filed : declaredFields) { if (filed.getName().equals("configDiskService")) { @@ -112,8 +109,7 @@ void after() { dynamicDataSourceMockedStatic.close(); envUtilMockedStatic.close(); ConfigDiskServiceFactory.getInstance().clearAll(); - ConfigDiskServiceFactory.getInstance().clearAllBeta(); - ConfigDiskServiceFactory.getInstance().clearAllTag(); + ConfigDiskServiceFactory.getInstance().clearAllGray(); } @@ -131,17 +127,18 @@ void testDumpNormalAndRemove() throws IOException { configInfoWrapper.setContent(content); configInfoWrapper.setLastModified(time); - Mockito.when(configInfoPersistService.findConfigInfo(eq(dataId), eq(group), eq(tenant))).thenReturn(configInfoWrapper); + Mockito.when(configInfoPersistService.findConfigInfo(eq(dataId), eq(group), eq(tenant))) + .thenReturn(configInfoWrapper); String handlerIp = "127.0.0.1"; long lastModified = System.currentTimeMillis(); - DumpTask dumpTask = new DumpTask(GroupKey2.getKey(dataId, group, tenant), false, false, false, null, lastModified, handlerIp); + DumpTask dumpTask = new DumpTask(GroupKey2.getKey(dataId, group, tenant), null, lastModified, handlerIp); boolean process = dumpProcessor.process(dumpTask); assertTrue(process); //Check cache CacheItem contentCache = ConfigCacheService.getContentCache(GroupKey2.getKey(dataId, group, tenant)); - assertEquals(MD5Utils.md5Hex(content, "UTF-8"), contentCache.getConfigCache().getMd5Utf8()); + assertEquals(MD5Utils.md5Hex(content, "UTF-8"), contentCache.getConfigCache().getMd5()); assertEquals(time, contentCache.getConfigCache().getLastModifiedTs()); //check disk String contentFromDisk = ConfigDiskServiceFactory.getInstance().getContent(dataId, group, tenant); @@ -161,98 +158,4 @@ void testDumpNormalAndRemove() throws IOException { assertNull(contentFromDiskAfterRemove); } - - @Test - void testDumpBetaAndRemove() throws IOException { - String dataId = "testDataIdBeta"; - String group = "testGroup"; - String tenant = "testTenant"; - String content = "testContentBeta你好" + System.currentTimeMillis(); - long time = System.currentTimeMillis(); - ConfigInfoBetaWrapper configInfoWrapper = new ConfigInfoBetaWrapper(); - configInfoWrapper.setDataId(dataId); - configInfoWrapper.setGroup(group); - configInfoWrapper.setTenant(tenant); - configInfoWrapper.setContent(content); - configInfoWrapper.setLastModified(time); - String betaIps = "127.0.0.1123,127.0.0.11"; - configInfoWrapper.setBetaIps(betaIps); - - Mockito.when(configInfoBetaPersistService.findConfigInfo4Beta(eq(dataId), eq(group), eq(tenant))).thenReturn(configInfoWrapper); - - String handlerIp = "127.0.0.1"; - long lastModified = System.currentTimeMillis(); - DumpTask dumpTask = new DumpTask(GroupKey2.getKey(dataId, group, tenant), true, false, false, null, lastModified, handlerIp); - boolean process = dumpProcessor.process(dumpTask); - assertTrue(process); - - //Check cache - CacheItem contentCache = ConfigCacheService.getContentCache(GroupKey2.getKey(dataId, group, tenant)); - assertEquals(MD5Utils.md5Hex(content, "UTF-8"), contentCache.getConfigCacheBeta().getMd5Utf8()); - assertEquals(time, contentCache.getConfigCacheBeta().getLastModifiedTs()); - assertTrue(contentCache.ips4Beta.containsAll(Arrays.asList(betaIps.split(",")))); - //check disk - String contentFromDisk = ConfigDiskServiceFactory.getInstance().getBetaContent(dataId, group, tenant); - assertEquals(content, contentFromDisk); - - // remove - Mockito.when(configInfoBetaPersistService.findConfigInfo4Beta(eq(dataId), eq(group), eq(tenant))).thenReturn(null); - boolean processRemove = dumpProcessor.process(dumpTask); - assertTrue(processRemove); - - //Check cache - CacheItem contentCacheAfterRemove = ConfigCacheService.getContentCache(GroupKey2.getKey(dataId, group, tenant)); - assertTrue(contentCacheAfterRemove == null || contentCacheAfterRemove.getConfigCacheBeta() == null); - //check disk - String contentFromDiskAfterRemove = ConfigDiskServiceFactory.getInstance().getBetaContent(dataId, group, tenant); - assertNull(contentFromDiskAfterRemove); - - } - - @Test - void testDumpTagAndRemove() throws IOException { - String dataId = "testDataIdBeta"; - String group = "testGroup"; - String tenant = "testTenant"; - String tag = "testTag111"; - String content = "testContentBeta你好" + System.currentTimeMillis(); - long time = System.currentTimeMillis(); - ConfigInfoTagWrapper configInfoWrapper = new ConfigInfoTagWrapper(); - configInfoWrapper.setDataId(dataId); - configInfoWrapper.setGroup(group); - configInfoWrapper.setTenant(tenant); - configInfoWrapper.setContent(content); - configInfoWrapper.setLastModified(time); - configInfoWrapper.setTag(tag); - Mockito.when(configInfoTagPersistService.findConfigInfo4Tag(eq(dataId), eq(group), eq(tenant), eq(tag))) - .thenReturn(configInfoWrapper); - - String handlerIp = "127.0.0.1"; - long lastModified = System.currentTimeMillis(); - DumpTask dumpTask = new DumpTask(GroupKey2.getKey(dataId, group, tenant), false, false, true, tag, lastModified, handlerIp); - boolean process = dumpProcessor.process(dumpTask); - assertTrue(process); - - //Check cache - CacheItem contentCache = ConfigCacheService.getContentCache(GroupKey2.getKey(dataId, group, tenant)); - assertEquals(MD5Utils.md5Hex(content, "UTF-8"), contentCache.getConfigCacheTags().get(tag).getMd5Utf8()); - assertEquals(time, contentCache.getConfigCacheTags().get(tag).getLastModifiedTs()); - //check disk - String contentFromDisk = ConfigDiskServiceFactory.getInstance().getTagContent(dataId, group, tenant, tag); - assertEquals(content, contentFromDisk); - - // remove - Mockito.when(configInfoTagPersistService.findConfigInfo4Tag(eq(dataId), eq(group), eq(tenant), eq(tag))).thenReturn(null); - boolean processRemove = dumpProcessor.process(dumpTask); - assertTrue(processRemove); - - //Check cache - CacheItem contentCacheAfterRemove = ConfigCacheService.getContentCache(GroupKey2.getKey(dataId, group, tenant)); - assertTrue(contentCacheAfterRemove == null || contentCache.getConfigCacheTags() == null - || contentCache.getConfigCacheTags().get(tag) == null); - //check disk - String contentFromDiskAfterRemove = ConfigDiskServiceFactory.getInstance().getTagContent(dataId, group, tenant, tag); - assertNull(contentFromDiskAfterRemove); - - } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorUserRwaDiskTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorUserRwaDiskTest.java index 27a3bf09fc9..41f464686ce 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorUserRwaDiskTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpProcessorUserRwaDiskTest.java @@ -49,14 +49,4 @@ public void testDumpNormalAndRemove() throws IOException { super.testDumpNormalAndRemove(); } - - @Test - public void testDumpBetaAndRemove() throws IOException { - super.testDumpBetaAndRemove(); - } - - @Test - public void testDumpTagAndRemove() throws IOException { - super.testDumpTagAndRemove(); - } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpServiceTest.java index 884561422d1..850a42bc11f 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/DumpServiceTest.java @@ -17,14 +17,10 @@ package com.alibaba.nacos.config.server.service.dump; import com.alibaba.nacos.config.server.manager.TaskManager; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; import com.alibaba.nacos.config.server.service.dump.task.DumpTask; -import com.alibaba.nacos.config.server.service.merge.MergeDatumService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.config.server.utils.ConfigExecutor; import com.alibaba.nacos.config.server.utils.GroupKey; @@ -44,14 +40,10 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; import org.springframework.test.util.ReflectionTestUtils; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; import java.util.concurrent.TimeUnit; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; @@ -78,16 +70,7 @@ class DumpServiceTest { HistoryConfigInfoPersistService historyConfigInfoPersistService; @Mock - ConfigInfoAggrPersistService configInfoAggrPersistService; - - @Mock - ConfigInfoBetaPersistService configInfoBetaPersistService; - - @Mock - ConfigInfoTagPersistService configInfoTagPersistService; - - @Mock - MergeDatumService mergeDatumService; + ConfigInfoGrayPersistService configInfoGrayPersistService; @Mock ServerMemberManager memberManager; @@ -117,11 +100,12 @@ void setUp() { ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "localDataSourceService", dataSourceService); ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "basicDataSourceService", dataSourceService); - dumpService = new ExternalDumpService(configInfoPersistService, namespacePersistService, historyConfigInfoPersistService, - configInfoAggrPersistService, configInfoBetaPersistService, configInfoTagPersistService, mergeDatumService, memberManager); + dumpService = new ExternalDumpService(configInfoPersistService, namespacePersistService, + historyConfigInfoPersistService, configInfoGrayPersistService, memberManager); configExecutorMocked = Mockito.mockStatic(ConfigExecutor.class); historyConfigCleanerManagerMockedStatic = Mockito.mockStatic(HistoryConfigCleanerManager.class); - historyConfigCleanerManagerMockedStatic.when(() -> HistoryConfigCleanerManager.getHistoryConfigCleaner(anyString())) + historyConfigCleanerManagerMockedStatic.when( + () -> HistoryConfigCleanerManager.getHistoryConfigCleaner(anyString())) .thenReturn(defaultHistoryConfigCleaner); } @@ -138,106 +122,80 @@ void after() { void dumpRequest() throws Throwable { String dataId = "12345667dataId"; String group = "234445group"; - DumpRequest dumpRequest = DumpRequest.create(dataId, group, "testtenant", System.currentTimeMillis(), "127.0.0.1"); + DumpRequest dumpRequest = DumpRequest.create(dataId, group, "testtenant", System.currentTimeMillis(), + "127.0.0.1"); // TaskManager dumpTaskMgr; ReflectionTestUtils.setField(dumpService, "dumpTaskMgr", dumpTaskMgr); Mockito.doNothing().when(dumpTaskMgr).addTask(any(), any()); dumpService.dump(dumpRequest); Mockito.verify(dumpTaskMgr, times(1)) .addTask(eq(GroupKey.getKeyTenant(dataId, group, dumpRequest.getTenant())), any(DumpTask.class)); - dumpRequest.setBeta(true); - dumpService.dump(dumpRequest); - Mockito.verify(dumpTaskMgr, times(1)) - .addTask(eq(GroupKey.getKeyTenant(dataId, group, dumpRequest.getTenant()) + "+beta"), any(DumpTask.class)); - dumpRequest.setBeta(false); - dumpRequest.setBatch(true); - dumpService.dump(dumpRequest); - Mockito.verify(dumpTaskMgr, times(1)) - .addTask(eq(GroupKey.getKeyTenant(dataId, group, dumpRequest.getTenant()) + "+batch"), any(DumpTask.class)); - dumpRequest.setBatch(false); - dumpRequest.setTag("testTag111"); + + dumpRequest.setGrayName("tag_123"); dumpService.dump(dumpRequest); - Mockito.verify(dumpTaskMgr, times(1)) - .addTask(eq(GroupKey.getKeyTenant(dataId, group, dumpRequest.getTenant()) + "+tag+" + dumpRequest.getTag()), - any(DumpTask.class)); + Mockito.verify(dumpTaskMgr, times(1)).addTask( + eq(GroupKey.getKeyTenant(dataId, group, dumpRequest.getTenant()) + "+gray+" + + dumpRequest.getGrayName()), any(DumpTask.class)); } @Test void dumpOperate() throws Throwable { - configExecutorMocked.when(() -> ConfigExecutor.scheduleConfigTask(any(Runnable.class), anyInt(), anyInt(), any(TimeUnit.class))) + configExecutorMocked.when( + () -> ConfigExecutor.scheduleConfigTask(any(Runnable.class), anyInt(), anyInt(), any(TimeUnit.class))) .thenAnswer(invocation -> null); - configExecutorMocked.when(() -> ConfigExecutor.scheduleConfigChangeTask(any(Runnable.class), anyInt(), any(TimeUnit.class))) + configExecutorMocked.when( + () -> ConfigExecutor.scheduleConfigChangeTask(any(Runnable.class), anyInt(), any(TimeUnit.class))) .thenAnswer(invocation -> null); Mockito.when(namespacePersistService.isExistTable(BETA_TABLE_NAME)).thenReturn(true); Mockito.when(namespacePersistService.isExistTable(TAG_TABLE_NAME)).thenReturn(true); - ConfigInfoChanged hasDatum = new ConfigInfoChanged(); - hasDatum.setDataId("hasDatumdataId1"); - hasDatum.setTenant("tenant1"); - hasDatum.setGroup("group1"); - ConfigInfoChanged noDatum = new ConfigInfoChanged(); - noDatum.setDataId("dataId1"); - noDatum.setTenant("tenant1"); - noDatum.setGroup("group1"); - List configList = configInfoAggrPersistService.findAllAggrGroup(); - configList.add(hasDatum); - configList.add(noDatum); - Mockito.when(configInfoAggrPersistService.findAllAggrGroup()).thenReturn(configList); - List> result = new ArrayList<>(); - result.add(Arrays.asList(hasDatum)); - result.add(Arrays.asList(noDatum)); - Mockito.when(mergeDatumService.splitList(anyList(), anyInt())).thenReturn(result); - Mockito.doNothing().when(mergeDatumService).executeConfigsMerge(anyList()); + Mockito.when(configInfoPersistService.findConfigMaxId()).thenReturn(300L); dumpService.dumpOperate(); // expect dump Mockito.verify(configInfoPersistService, times(1)).findAllConfigInfoFragment(0, 100, true); Mockito.verify(configInfoPersistService, times(1)).findConfigMaxId(); - Mockito.verify(configInfoBetaPersistService, times(1)).configInfoBetaCount(); - Mockito.verify(configInfoTagPersistService, times(1)).configInfoTagCount(); - - Mockito.verify(mergeDatumService, times(2)).executeConfigsMerge(anyList()); + Mockito.verify(configInfoGrayPersistService, times(1)).configInfoGrayCount(); // expect dump formal,beta,tag,history clear,config change task to be scheduled. // expect config clear history task be scheduled. configExecutorMocked.verify( - () -> ConfigExecutor.scheduleConfigTask(any(DumpService.DumpAllProcessorRunner.class), anyLong(), anyLong(), - eq(TimeUnit.MINUTES)), times(1)); - configExecutorMocked.verify( - () -> ConfigExecutor.scheduleConfigTask(any(DumpService.DumpAllBetaProcessorRunner.class), anyLong(), anyLong(), - eq(TimeUnit.MINUTES)), times(1)); + () -> ConfigExecutor.scheduleConfigTask(any(DumpService.DumpAllProcessorRunner.class), anyLong(), + anyLong(), eq(TimeUnit.MINUTES)), times(1)); + configExecutorMocked.verify( - () -> ConfigExecutor.scheduleConfigTask(any(DumpService.DumpAllTagProcessorRunner.class), anyLong(), anyLong(), - eq(TimeUnit.MINUTES)), times(1)); + () -> ConfigExecutor.scheduleConfigTask(any(DumpService.DumpAllGrayProcessorRunner.class), anyLong(), + anyLong(), eq(TimeUnit.MINUTES)), times(1)); configExecutorMocked.verify( - () -> ConfigExecutor.scheduleConfigChangeTask(any(DumpChangeConfigWorker.class), anyLong(), eq(TimeUnit.MILLISECONDS)), - times(1)); + () -> ConfigExecutor.scheduleConfigChangeTask(any(DumpChangeConfigWorker.class), anyLong(), + eq(TimeUnit.MILLISECONDS)), times(1)); configExecutorMocked.verify( () -> ConfigExecutor.scheduleConfigTask(any(DumpService.ConfigHistoryClear.class), anyLong(), anyLong(), - eq(TimeUnit.MINUTES)), times(1) - ); + eq(TimeUnit.MINUTES)), times(1)); } @Test void clearHistory() { envUtilMockedStatic.when(() -> EnvUtil.getProperty(eq("nacos.config.retention.days"))).thenReturn("10"); Mockito.when(memberManager.isFirstIp()).thenReturn(true); - DumpService.ConfigHistoryClear configHistoryClear = dumpService.new ConfigHistoryClear(defaultHistoryConfigCleaner); + DumpService.ConfigHistoryClear configHistoryClear = dumpService.new ConfigHistoryClear( + defaultHistoryConfigCleaner); configHistoryClear.run(); Mockito.verify(defaultHistoryConfigCleaner, times(1)).cleanHistoryConfig(); } @Test void testHandleConfigDataChange() { - ConfigDataChangeEvent configDataChangeEvent = new ConfigDataChangeEvent("dataId", "group", System.currentTimeMillis()); + ConfigDataChangeEvent configDataChangeEvent = new ConfigDataChangeEvent("dataId", "group", null, + System.currentTimeMillis()); ReflectionTestUtils.setField(dumpService, "dumpTaskMgr", dumpTaskMgr); Mockito.doNothing().when(dumpTaskMgr).addTask(any(), any()); dumpService.handleConfigDataChange(configDataChangeEvent); - Mockito.verify(dumpTaskMgr, times(1)) - .addTask(eq(GroupKey.getKeyTenant(configDataChangeEvent.dataId, configDataChangeEvent.group, configDataChangeEvent.tenant)), - any(DumpTask.class)); + Mockito.verify(dumpTaskMgr, times(1)).addTask( + eq(GroupKey.getKeyTenant(configDataChangeEvent.dataId, configDataChangeEvent.group, + configDataChangeEvent.tenant)), any(DumpTask.class)); } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskServiceTest.java index cf01eb366c0..bec451631bd 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/disk/ConfigRawDiskServiceTest.java @@ -75,73 +75,27 @@ void testTargetFileWithInvalidParam() { * 测试获取beta文件路径. */ @Test - void testTargetBetaFile() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method method = ConfigRawDiskService.class.getDeclaredMethod("targetBetaFile", String.class, String.class, - String.class); + void testTargetGrayFile() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { + Method method = ConfigRawDiskService.class.getDeclaredMethod("targetGrayFile", String.class, String.class, + String.class, String.class); method.setAccessible(true); - File result = (File) method.invoke(null, "aaaa-dsaknkf", "aaaa.dsaknkf", "aaaa:dsaknkf"); + File result = (File) method.invoke(null, "data345678", "group3456", "tenant1234", "graynem4567"); // 分解路径 Path path = Paths.get(result.getPath()); Path parent = path.getParent(); Path grandParent = parent.getParent(); + Path grand2Parent = grandParent.getParent(); + // 获取最后三段路径 - String lastSegment = path.getFileName().toString(); - String secondLastSegment = parent.getFileName().toString(); + String fourthLastSegment = grand2Parent.getFileName().toString(); + assertEquals(fourthLastSegment, "tenant1234"); String thirdLastSegment = grandParent.getFileName().toString(); - assertEquals(isWindows() ? "aaaa-dsaknkf" : thirdLastSegment, thirdLastSegment); - assertEquals(isWindows() ? "aaaa.dsaknkf" : secondLastSegment, secondLastSegment); - assertEquals(isWindows() ? "aaaa%A5%dsaknkf" : lastSegment, lastSegment); - } - - @Test - void testTargetBetaFileWithInvalidParam() throws NoSuchMethodException { - Method method = ConfigRawDiskService.class.getDeclaredMethod("targetBetaFile", String.class, String.class, - String.class); - method.setAccessible(true); - assertThrows(InvocationTargetException.class, () -> method.invoke(null, "../aaa", "testG", "testNS")); - assertThrows(InvocationTargetException.class, () -> method.invoke(null, "testD", "../aaa", "testNS")); - assertThrows(InvocationTargetException.class, () -> method.invoke(null, "testD", "testG", "../aaa")); - } - - /** - * 测试获取tag文件路径. - * - * @throws NoSuchMethodException 方法不存在异常 - * @throws IllegalAccessException 非法访问异常 - * @throws InvocationTargetException 目标异常 - */ - @Test - void testTargetTagFile() throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { - Method method = ConfigRawDiskService.class.getDeclaredMethod("targetTagFile", String.class, String.class, - String.class, String.class); - method.setAccessible(true); - File result = (File) method.invoke(null, "aaaa-dsaknkf", "aaaa.dsaknkf", "aaaa:dsaknkf", "aaaa_dsaknkf"); - // 分解路径 - Path path = Paths.get(result.getPath()); - Path parent = path.getParent(); - Path grandParent = parent.getParent(); - Path greatGrandParent = grandParent.getParent(); - // 获取最后四段路径 + assertEquals(isWindows() ? "aaaa-dsaknkf" : thirdLastSegment, "group3456"); String secondLastSegment = parent.getFileName().toString(); - String thirdLastSegment = grandParent.getFileName().toString(); - String fourthLastSegment = greatGrandParent.getFileName().toString(); - assertEquals(isWindows() ? "aaaa-dsaknkf" : fourthLastSegment, fourthLastSegment); - assertEquals(isWindows() ? "aaaa.dsaknkf" : thirdLastSegment, thirdLastSegment); - assertEquals(isWindows() ? "aaaa%A5%dsaknkf" : secondLastSegment, secondLastSegment); + assertEquals(isWindows() ? "aaaa-dsaknkf" : secondLastSegment, "data345678"); String lastSegment = path.getFileName().toString(); - assertEquals("aaaa_dsaknkf", lastSegment); + assertEquals(isWindows() ? "aaaa-dsaknkf" : lastSegment, "graynem4567"); + } - @Test - void testTargetTagFileWithInvalidParam() throws NoSuchMethodException { - Method method = ConfigRawDiskService.class.getDeclaredMethod("targetTagFile", String.class, String.class, - String.class, String.class); - method.setAccessible(true); - assertThrows(InvocationTargetException.class, - () -> method.invoke(null, "../aaa", "testG", "testNS", "testTag")); - assertThrows(InvocationTargetException.class, - () -> method.invoke(null, "testD", "../aaa", "testNS", "testTag")); - assertThrows(InvocationTargetException.class, () -> method.invoke(null, "testD", "testG", "../aaa", "testTag")); - assertThrows(InvocationTargetException.class, () -> method.invoke(null, "testD", "testG", "testNS", "../aaa")); - } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessorTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessorTest.java index 206d950adcf..4a5d90325df 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessorTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/dump/processor/DumpAllProcessorTest.java @@ -23,9 +23,8 @@ import com.alibaba.nacos.config.server.service.dump.ExternalDumpService; import com.alibaba.nacos.config.server.service.dump.disk.ConfigDiskServiceFactory; import com.alibaba.nacos.config.server.service.dump.task.DumpAllTask; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoGrayPersistService; import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; import com.alibaba.nacos.config.server.utils.GroupKey2; import com.alibaba.nacos.config.server.utils.PropertyUtil; import com.alibaba.nacos.persistence.datasource.DataSourceService; @@ -63,10 +62,7 @@ class DumpAllProcessorTest { DataSourceService dataSourceService; @Mock - ConfigInfoBetaPersistService configInfoBetaPersistService; - - @Mock - ConfigInfoTagPersistService configInfoTagPersistService; + ConfigInfoGrayPersistService configInfoGrayPersistService; DumpAllProcessor dumpAllProcessor; @@ -74,6 +70,8 @@ class DumpAllProcessorTest { MockedStatic dynamicDataSourceMockedStatic; + MockedStatic propertyUtilMockedStatic; + @Mock ConfigInfoPersistService configInfoPersistService; @@ -85,6 +83,8 @@ class DumpAllProcessorTest { void init() throws Exception { dynamicDataSourceMockedStatic = Mockito.mockStatic(DynamicDataSource.class); envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); + propertyUtilMockedStatic = Mockito.mockStatic(PropertyUtil.class); + propertyUtilMockedStatic.when(PropertyUtil::getAllDumpPageSize).thenReturn(100); dumpAllProcessor = new DumpAllProcessor(configInfoPersistService); when(EnvUtil.getNacosHome()).thenReturn(System.getProperty("user.home")); when(EnvUtil.getProperty(eq(CommonConstant.NACOS_PLUGIN_DATASOURCE_LOG), eq(Boolean.class), eq(false))).thenReturn(false); @@ -92,18 +92,19 @@ void init() throws Exception { when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); - dumpService = new ExternalDumpService(configInfoPersistService, null, null, null, configInfoBetaPersistService, - configInfoTagPersistService, null, null); + dumpService = new ExternalDumpService(configInfoPersistService, null, null, configInfoGrayPersistService, null); dumpAllProcessor = new DumpAllProcessor(configInfoPersistService); envUtilMockedStatic.when(() -> EnvUtil.getProperty(eq("memory_limit_file_path"), eq("/sys/fs/cgroup/memory/memory.limit_in_bytes"))) .thenReturn(mockMem); + } @AfterEach void after() throws Exception { dynamicDataSourceMockedStatic.close(); envUtilMockedStatic.close(); + propertyUtilMockedStatic.close(); } private ConfigInfoWrapper createNewConfig(int id) { @@ -158,7 +159,7 @@ void testDumpAllOnStartUp() throws Exception { //Check cache CacheItem contentCache1 = ConfigCacheService.getContentCache( GroupKey2.getKey(configInfoWrapper1.getDataId(), configInfoWrapper1.getGroup(), configInfoWrapper1.getTenant())); - assertEquals(md51, contentCache1.getConfigCache().getMd5Utf8()); + assertEquals(md51, contentCache1.getConfigCache().getMd5()); // check if config1 is updated assertTrue(timestamp < contentCache1.getConfigCache().getLastModifiedTs()); //check disk @@ -169,7 +170,7 @@ void testDumpAllOnStartUp() throws Exception { //Check cache CacheItem contentCache2 = ConfigCacheService.getContentCache( GroupKey2.getKey(configInfoWrapper2.getDataId(), configInfoWrapper2.getGroup(), configInfoWrapper2.getTenant())); - assertEquals(MD5Utils.md5Hex(configInfoWrapper2.getContent(), "UTF-8"), contentCache2.getConfigCache().getMd5Utf8()); + assertEquals(MD5Utils.md5Hex(configInfoWrapper2.getContent(), "UTF-8"), contentCache2.getConfigCache().getMd5()); // check if config2 is updated assertEquals(timestamp, contentCache2.getConfigCache().getLastModifiedTs()); //check disk @@ -231,7 +232,7 @@ void testDumpAllOnCheckAll() throws Exception { CacheItem contentCache1 = ConfigCacheService.getContentCache( GroupKey2.getKey(configInfoWrapper1.getDataId(), configInfoWrapper1.getGroup(), configInfoWrapper1.getTenant())); // check if config1 is not updated - assertEquals(md51, contentCache1.getConfigCache().getMd5Utf8()); + assertEquals(md51, contentCache1.getConfigCache().getMd5()); assertEquals(latterTimestamp, contentCache1.getConfigCache().getLastModifiedTs()); //check disk String contentFromDisk1 = ConfigDiskServiceFactory.getInstance() @@ -242,7 +243,7 @@ void testDumpAllOnCheckAll() throws Exception { CacheItem contentCache2 = ConfigCacheService.getContentCache( GroupKey2.getKey(configInfoWrapper2.getDataId(), configInfoWrapper2.getGroup(), configInfoWrapper2.getTenant())); // check if config2 is updated - assertEquals(MD5Utils.md5Hex(configInfoWrapperSingle2.getContent(), "UTF-8"), contentCache2.getConfigCache().getMd5Utf8()); + assertEquals(MD5Utils.md5Hex(configInfoWrapperSingle2.getContent(), "UTF-8"), contentCache2.getConfigCache().getMd5()); assertEquals(configInfoWrapper2.getLastModified(), contentCache2.getConfigCache().getLastModifiedTs()); //check disk String contentFromDisk2 = ConfigDiskServiceFactory.getInstance() diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/merge/MergeDatumServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/merge/MergeDatumServiceTest.java deleted file mode 100644 index f2eeeb570d4..00000000000 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/merge/MergeDatumServiceTest.java +++ /dev/null @@ -1,235 +0,0 @@ -/* - * 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.config.server.service.merge; - -import com.alibaba.nacos.config.server.manager.TaskManager; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; -import com.alibaba.nacos.consistency.cp.CPProtocol; -import com.alibaba.nacos.core.distributed.ProtocolManager; -import com.alibaba.nacos.persistence.configuration.DatasourceConfiguration; -import com.alibaba.nacos.persistence.datasource.DataSourceService; -import com.alibaba.nacos.persistence.datasource.DynamicDataSource; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.sys.env.EnvUtil; -import com.alibaba.nacos.sys.utils.ApplicationUtils; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.util.ReflectionTestUtils; - -import java.util.ArrayList; -import java.util.List; - -import static com.alibaba.nacos.persistence.constants.PersistenceConstant.CONFIG_MODEL_RAFT_GROUP; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; - -@ExtendWith(SpringExtension.class) -class MergeDatumServiceTest { - - @Mock - ConfigInfoPersistService configInfoPersistService; - - @Mock - ConfigInfoAggrPersistService configInfoAggrPersistService; - - @Mock - ConfigInfoTagPersistService configInfoTagPersistService; - - @Mock - ProtocolManager protocolManager; - - MockedStatic envUtilMockedStatic; - - MockedStatic applicationUtilsMockedStaticc; - - @Mock - private DataSourceService dataSourceService; - - private MergeDatumService mergeDatumService; - - @BeforeEach - void setUp() { - envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); - applicationUtilsMockedStaticc = Mockito.mockStatic(ApplicationUtils.class); - applicationUtilsMockedStaticc.when(() -> ApplicationUtils.getBean(eq(ProtocolManager.class))).thenReturn(protocolManager); - - ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "localDataSourceService", dataSourceService); - ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "basicDataSourceService", dataSourceService); - mergeDatumService = new MergeDatumService(configInfoPersistService, configInfoAggrPersistService, configInfoTagPersistService); - - } - - @AfterEach - void after() { - envUtilMockedStatic.close(); - applicationUtilsMockedStaticc.close(); - } - - @Test - void testSplitList() { - String dataId = "dataID"; - int count = 5; - List configList = new ArrayList<>(); - configList.add(create(dataId, 0)); - configList.add(create(dataId, 1)); - configList.add(create(dataId, 2)); - configList.add(create(dataId, 3)); - configList.add(create(dataId, 4)); - configList.add(create(dataId, 5)); - configList.add(create(dataId, 6)); - configList.add(create(dataId, 7)); - configList.add(create(dataId, 8)); - - List> lists = mergeDatumService.splitList(configList, count); - int originalCount = configList.size(); - int actualCount = 0; - for (int i = 0; i < lists.size(); i++) { - List indexList = lists.get(i); - for (int j = 0; j < indexList.size(); j++) { - ConfigInfoChanged configInfoChanged = indexList.get(j); - actualCount++; - assertEquals(configInfoChanged, configList.get((j * count) + i)); - } - } - - assertEquals(originalCount, actualCount); - - } - - private ConfigInfoChanged create(String dataID, int i) { - ConfigInfoChanged hasDatum = new ConfigInfoChanged(); - hasDatum.setDataId(dataID + i); - hasDatum.setTenant("tenant1"); - hasDatum.setGroup("group1"); - return hasDatum; - } - - @Test - void executeMergeConfigTask() { - envUtilMockedStatic.when(() -> EnvUtil.getProperty(eq("nacos.config.retention.days"))).thenReturn("10"); - ConfigInfoChanged hasDatum = new ConfigInfoChanged(); - hasDatum.setDataId("hasDatumdataId1"); - hasDatum.setTenant("tenant1"); - hasDatum.setGroup("group1"); - ConfigInfoChanged noDatum = new ConfigInfoChanged(); - noDatum.setDataId("dataId1"); - noDatum.setTenant("tenant1"); - noDatum.setGroup("group1"); - List configInfoList = new ArrayList<>(); - configInfoList.add(hasDatum); - configInfoList.add(noDatum); - - when(configInfoAggrPersistService.aggrConfigInfoCount(eq(hasDatum.getDataId()), eq(hasDatum.getGroup()), - eq(hasDatum.getTenant()))).thenReturn(2); - Page datumPage = new Page<>(); - ConfigInfoAggr configInfoAggr1 = new ConfigInfoAggr(); - configInfoAggr1.setContent("12344"); - ConfigInfoAggr configInfoAggr2 = new ConfigInfoAggr(); - configInfoAggr2.setContent("12345666"); - datumPage.getPageItems().add(configInfoAggr1); - datumPage.getPageItems().add(configInfoAggr2); - - when(configInfoAggrPersistService.findConfigInfoAggrByPage(eq(hasDatum.getDataId()), eq(hasDatum.getGroup()), - eq(hasDatum.getTenant()), anyInt(), anyInt())).thenReturn(datumPage); - - when(configInfoAggrPersistService.aggrConfigInfoCount(eq(noDatum.getDataId()), eq(noDatum.getGroup()), - eq(noDatum.getTenant()))).thenReturn(0); - - mergeDatumService.executeMergeConfigTask(configInfoList, 1000); - } - - @Test - void testAddMergeTaskExternalModel() { - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - String clientIp = "127.0.0.1"; - DatasourceConfiguration.setEmbeddedStorage(false); - TaskManager mockTasker = Mockito.mock(TaskManager.class); - ReflectionTestUtils.setField(mergeDatumService, "mergeTasks", mockTasker); - mergeDatumService.addMergeTask(dataId, group, tenant, clientIp); - Mockito.verify(mockTasker, times(1)).addTask(anyString(), any(MergeDataTask.class)); - } - - @Test - void testAddMergeTaskEmbeddedAndStandAloneModel() { - - DatasourceConfiguration.setEmbeddedStorage(true); - envUtilMockedStatic.when(() -> EnvUtil.getStandaloneMode()).thenReturn(true); - TaskManager mockTasker = Mockito.mock(TaskManager.class); - ReflectionTestUtils.setField(mergeDatumService, "mergeTasks", mockTasker); - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - String clientIp = "127.0.0.1"; - mergeDatumService.addMergeTask(dataId, group, tenant, clientIp); - Mockito.verify(mockTasker, times(1)).addTask(anyString(), any(MergeDataTask.class)); - } - - @Test - void testAddMergeTaskEmbeddedAndClusterModelLeader() { - - DatasourceConfiguration.setEmbeddedStorage(true); - envUtilMockedStatic.when(() -> EnvUtil.getStandaloneMode()).thenReturn(false); - TaskManager mockTasker = Mockito.mock(TaskManager.class); - ReflectionTestUtils.setField(mergeDatumService, "mergeTasks", mockTasker); - //mock is leader - CPProtocol cpProtocol = Mockito.mock(CPProtocol.class); - when(protocolManager.getCpProtocol()).thenReturn(cpProtocol); - when(cpProtocol.isLeader(eq(CONFIG_MODEL_RAFT_GROUP))).thenReturn(true); - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - String clientIp = "127.0.0.1"; - mergeDatumService.addMergeTask(dataId, group, tenant, clientIp); - Mockito.verify(mockTasker, times(1)).addTask(anyString(), any(MergeDataTask.class)); - } - - @Test - void testAddMergeTaskEmbeddedAndClusterModelNotLeader() { - - DatasourceConfiguration.setEmbeddedStorage(true); - envUtilMockedStatic.when(() -> EnvUtil.getStandaloneMode()).thenReturn(false); - TaskManager mockTasker = Mockito.mock(TaskManager.class); - ReflectionTestUtils.setField(mergeDatumService, "mergeTasks", mockTasker); - //mock not leader - CPProtocol cpProtocol = Mockito.mock(CPProtocol.class); - when(protocolManager.getCpProtocol()).thenReturn(cpProtocol); - when(cpProtocol.isLeader(eq(CONFIG_MODEL_RAFT_GROUP))).thenReturn(false); - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - String clientIp = "127.0.0.1"; - mergeDatumService.addMergeTask(dataId, group, tenant, clientIp); - Mockito.verify(mockTasker, times(0)).addTask(anyString(), any(MergeDataTask.class)); - } -} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessorTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessorTest.java deleted file mode 100644 index 12855d8ff61..00000000000 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/merge/MergeTaskProcessorTest.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * 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.config.server.service.merge; - -import com.alibaba.nacos.common.notify.Event; -import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.notify.listener.Subscriber; -import com.alibaba.nacos.config.server.model.ConfigInfo; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigOperateResult; -import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoAggrPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; -import com.alibaba.nacos.config.server.service.repository.ConfigInfoTagPersistService; -import com.alibaba.nacos.persistence.datasource.DataSourceService; -import com.alibaba.nacos.persistence.datasource.DynamicDataSource; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.sys.env.EnvUtil; -import com.alibaba.nacos.sys.utils.InetUtils; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.test.util.ReflectionTestUtils; - -import java.util.concurrent.atomic.AtomicReference; - -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyInt; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.when; - -@ExtendWith(SpringExtension.class) - -public class MergeTaskProcessorTest { - - @Mock - ConfigInfoPersistService configInfoPersistService; - - @Mock - ConfigInfoAggrPersistService configInfoAggrPersistService; - - @Mock - ConfigInfoTagPersistService configInfoTagPersistService; - - MockedStatic envUtilMockedStatic; - - MergeTaskProcessor mergeTaskProcessor; - - MockedStatic inetUtilsMockedStatic; - - @Mock - private DataSourceService dataSourceService; - - @Mock - private MergeDatumService mergeDatumService; - - @BeforeEach - void setUp() { - envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); - inetUtilsMockedStatic = Mockito.mockStatic(InetUtils.class); - ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "localDataSourceService", dataSourceService); - ReflectionTestUtils.setField(DynamicDataSource.getInstance(), "basicDataSourceService", dataSourceService); - mergeTaskProcessor = new MergeTaskProcessor(configInfoPersistService, configInfoAggrPersistService, configInfoTagPersistService, - mergeDatumService); - inetUtilsMockedStatic.when(InetUtils::getSelfIP).thenReturn("127.0.0.1"); - after(); - } - - public void after() { - envUtilMockedStatic.close(); - inetUtilsMockedStatic.close(); - } - - /** - * test aggr has datum and merge it expect: 1.config to be inserted 2.config data change event to be published - */ - @Test - void testMergerExistAggrConfig() throws InterruptedException { - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - when(configInfoAggrPersistService.aggrConfigInfoCount(eq(dataId), eq(group), eq(tenant))).thenReturn(2); - Page datumPage = new Page<>(); - ConfigInfoAggr configInfoAggr1 = new ConfigInfoAggr(); - configInfoAggr1.setContent("12344"); - ConfigInfoAggr configInfoAggr2 = new ConfigInfoAggr(); - configInfoAggr2.setContent("12345666"); - datumPage.getPageItems().add(configInfoAggr1); - datumPage.getPageItems().add(configInfoAggr2); - - when(configInfoAggrPersistService.findConfigInfoAggrByPage(eq(dataId), eq(group), eq(tenant), anyInt(), anyInt())).thenReturn( - datumPage); - - when(configInfoPersistService.insertOrUpdate(eq(null), eq(null), any(ConfigInfo.class), eq(null))).thenReturn( - new ConfigOperateResult()); - - AtomicReference reference = new AtomicReference<>(); - NotifyCenter.registerSubscriber(new Subscriber() { - - @Override - public void onEvent(Event event) { - ConfigDataChangeEvent event1 = (ConfigDataChangeEvent) event; - if (event1.dataId.equals(dataId) && event1.group.equals(group) && tenant.equals(event1.tenant)) { - reference.set((ConfigDataChangeEvent) event); - } - } - - @Override - public Class subscribeType() { - return ConfigDataChangeEvent.class; - } - }); - - MergeDataTask mergeDataTask = new MergeDataTask(dataId, group, tenant, "127.0.0.1"); - mergeTaskProcessor.process(mergeDataTask); - - Mockito.verify(configInfoPersistService, times(1)).insertOrUpdate(eq(null), eq(null), any(ConfigInfo.class), eq(null)); - Thread.sleep(1000L); - assertTrue(reference.get() != null); - - } - - /** - * test aggr has datum and remove it. - */ - @Test - void testMergerNotExistAggrConfig() throws InterruptedException { - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - when(configInfoAggrPersistService.aggrConfigInfoCount(eq(dataId), eq(group), eq(tenant))).thenReturn(0); - - AtomicReference reference = new AtomicReference<>(); - NotifyCenter.registerSubscriber(new Subscriber() { - - @Override - public void onEvent(Event event) { - ConfigDataChangeEvent event1 = (ConfigDataChangeEvent) event; - if (event1.dataId.equals(dataId) && event1.group.equals(group) && tenant.equals(event1.tenant)) { - reference.set((ConfigDataChangeEvent) event); - } - } - - @Override - public Class subscribeType() { - return ConfigDataChangeEvent.class; - } - }); - - MergeDataTask mergeDataTask = new MergeDataTask(dataId, group, tenant, "127.0.0.1"); - Mockito.doNothing().when(configInfoPersistService).removeConfigInfo(eq(dataId), eq(group), eq(tenant), eq("127.0.0.1"), eq(null)); - //Mockito.doNothing().when(configInfoTagPersistService).removeConfigInfoTag(eq(dataId), eq(group), eq(tenant),eq(),eq("127.0.0.1"),eq(null)); - mergeTaskProcessor.process(mergeDataTask); - Mockito.verify(configInfoPersistService, times(1)).removeConfigInfo(eq(dataId), eq(group), eq(tenant), eq("127.0.0.1"), eq(null)); - Thread.sleep(1000L); - assertTrue(reference.get() != null); - } - - /** - * test aggr has no datum and remove it tag. - */ - @Test - void testTagMergerNotExistAggrConfig() throws InterruptedException { - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - String tag = "23456789"; - when(configInfoAggrPersistService.aggrConfigInfoCount(eq(dataId), eq(group), eq(tenant))).thenReturn(0); - - AtomicReference reference = new AtomicReference<>(); - NotifyCenter.registerSubscriber(new Subscriber() { - - @Override - public void onEvent(Event event) { - ConfigDataChangeEvent event1 = (ConfigDataChangeEvent) event; - if (event1.dataId.equals(dataId) && event1.group.equals(group) && tenant.equals(event1.tenant) && tag.equals(event1.tag)) { - reference.set((ConfigDataChangeEvent) event); - } - } - - @Override - public Class subscribeType() { - return ConfigDataChangeEvent.class; - } - }); - - MergeDataTask mergeDataTask = new MergeDataTask(dataId, group, tenant, tag, "127.0.0.1"); - - Mockito.doNothing().when(configInfoTagPersistService) - .removeConfigInfoTag(eq(dataId), eq(group), eq(tenant), eq(tag), eq("127.0.0.1"), eq(null)); - mergeTaskProcessor.process(mergeDataTask); - Mockito.verify(configInfoTagPersistService, times(1)) - .removeConfigInfoTag(eq(dataId), eq(group), eq(tenant), eq(tag), eq("127.0.0.1"), eq(null)); - Thread.sleep(1000L); - assertTrue(reference.get() != null); - } - - /** - * test aggr has no datum and remove it tag. - */ - @Test - void testTagMergerError() throws InterruptedException { - String dataId = "dataId12345"; - String group = "group123"; - String tenant = "tenant1234"; - when(configInfoAggrPersistService.aggrConfigInfoCount(eq(dataId), eq(group), eq(tenant))).thenThrow(new NullPointerException()); - - MergeDataTask mergeDataTask = new MergeDataTask(dataId, group, tenant, "127.0.0.1"); - - mergeTaskProcessor.process(mergeDataTask); - Mockito.verify(mergeDatumService, times(1)).addMergeTask(eq(dataId), eq(group), eq(tenant), eq("127.0.0.1")); - - } -} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyServiceTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyServiceTest.java index a6072adab3e..c64869934f0 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyServiceTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/notify/AsyncNotifyServiceTest.java @@ -96,15 +96,16 @@ void testSyncConfigChangeCallback() { ReflectionTestUtils.setField(asyncNotifyService, "configClusterRpcClientProxy", configClusterRpcClientProxy); String dataId = "testDataId" + timeStamp; String group = "testGroup"; - AsyncNotifyService.NotifySingleRpcTask notifySingleRpcTask = new AsyncNotifyService.NotifySingleRpcTask(dataId, group, null, null, - 0, false, false, member1); - configExecutorMocked.when(() -> ConfigExecutor.scheduleAsyncNotify(any(Runnable.class), anyLong(), any(TimeUnit.class))) + AsyncNotifyService.NotifySingleRpcTask notifySingleRpcTask = new AsyncNotifyService.NotifySingleRpcTask(dataId, + group, null, null, 0, member1); + configExecutorMocked.when( + () -> ConfigExecutor.scheduleAsyncNotify(any(Runnable.class), anyLong(), any(TimeUnit.class))) .thenAnswer(invocation -> null); - notifySingleRpcTask.setBatch(true); notifySingleRpcTask.setTag("test"); notifySingleRpcTask.setBeta(false); - AsyncRpcNotifyCallBack asyncRpcNotifyCallBack = new AsyncRpcNotifyCallBack(asyncNotifyService, notifySingleRpcTask); + AsyncRpcNotifyCallBack asyncRpcNotifyCallBack = new AsyncRpcNotifyCallBack(asyncNotifyService, + notifySingleRpcTask); ConfigChangeClusterSyncResponse successResponse = new ConfigChangeClusterSyncResponse(); //1. success response asyncRpcNotifyCallBack.onResponse(successResponse); @@ -116,8 +117,8 @@ void testSyncConfigChangeCallback() { // expect schedule twice fail or exception response. configExecutorMocked.verify( - () -> ConfigExecutor.scheduleAsyncNotify(any(AsyncNotifyService.AsyncRpcTask.class), anyLong(), any(TimeUnit.class)), - times(2)); + () -> ConfigExecutor.scheduleAsyncNotify(any(AsyncNotifyService.AsyncRpcTask.class), anyLong(), + any(TimeUnit.class)), times(2)); } /** @@ -146,15 +147,18 @@ void testHandleConfigDataChangeEvent() { Mockito.when(serverMemberManager.allMembersWithoutSelf()).thenReturn(memberList); - configExecutorMocked.when(() -> ConfigExecutor.scheduleAsyncNotify(any(Runnable.class), anyLong(), any(TimeUnit.class))) + configExecutorMocked.when( + () -> ConfigExecutor.scheduleAsyncNotify(any(Runnable.class), anyLong(), any(TimeUnit.class))) .thenAnswer(invocation -> null); String dataId = "testDataId" + timeStamp; String group = "testGroup"; AsyncNotifyService asyncNotifyService = new AsyncNotifyService(serverMemberManager); - asyncNotifyService.handleConfigDataChangeEvent(new ConfigDataChangeEvent(dataId, group, System.currentTimeMillis())); + asyncNotifyService.handleConfigDataChangeEvent( + new ConfigDataChangeEvent(dataId, group, null, System.currentTimeMillis())); // expect schedule twice fail or exception response. - configExecutorMocked.verify(() -> ConfigExecutor.executeAsyncNotify(any(AsyncNotifyService.AsyncRpcTask.class)), times(1)); + configExecutorMocked.verify(() -> ConfigExecutor.executeAsyncNotify(any(AsyncNotifyService.AsyncRpcTask.class)), + times(1)); } @@ -184,8 +188,9 @@ void testExecuteAsyncRpcTask() throws Exception { for (Member member : memberList) { // grpc report data change only - rpcQueue.add(new AsyncNotifyService.NotifySingleRpcTask(dataId, group, null, null, System.currentTimeMillis(), false, false, - member)); + rpcQueue.add( + new AsyncNotifyService.NotifySingleRpcTask(dataId, group, null, null, System.currentTimeMillis(), + member)); } AsyncNotifyService asyncNotifyService = new AsyncNotifyService(serverMemberManager); @@ -195,14 +200,18 @@ void testExecuteAsyncRpcTask() throws Exception { Mockito.when(serverMemberManager.hasMember(eq(member1.getAddress()))).thenReturn(true); Mockito.when(serverMemberManager.hasMember(eq(member2.getAddress()))).thenReturn(true); Mockito.when(serverMemberManager.hasMember(eq(member3.getAddress()))).thenReturn(true); - Mockito.when(serverMemberManager.stateCheck(eq(member1.getAddress()), eq(HEALTHY_CHECK_STATUS))).thenReturn(true); - Mockito.when(serverMemberManager.stateCheck(eq(member2.getAddress()), eq(HEALTHY_CHECK_STATUS))).thenReturn(true); + Mockito.when(serverMemberManager.stateCheck(eq(member1.getAddress()), eq(HEALTHY_CHECK_STATUS))) + .thenReturn(true); + Mockito.when(serverMemberManager.stateCheck(eq(member2.getAddress()), eq(HEALTHY_CHECK_STATUS))) + .thenReturn(true); // mock stateCheck fail before notify member3 - Mockito.when(serverMemberManager.stateCheck(eq(member3.getAddress()), eq(HEALTHY_CHECK_STATUS))).thenReturn(false); + Mockito.when(serverMemberManager.stateCheck(eq(member3.getAddress()), eq(HEALTHY_CHECK_STATUS))) + .thenReturn(false); //mock syncConfigChange exception when notify member2 Mockito.doThrow(new NacosException()).when(configClusterRpcClientProxy) .syncConfigChange(eq(member2), any(ConfigChangeClusterSyncRequest.class), any(RequestCallBack.class)); - configExecutorMocked.when(() -> ConfigExecutor.scheduleAsyncNotify(any(Runnable.class), anyLong(), any(TimeUnit.class))) + configExecutorMocked.when( + () -> ConfigExecutor.scheduleAsyncNotify(any(Runnable.class), anyLong(), any(TimeUnit.class))) .thenAnswer(invocation -> null); asyncNotifyService.executeAsyncRpcTask(rpcQueue); @@ -216,8 +225,8 @@ void testExecuteAsyncRpcTask() throws Exception { //verify scheduleAsyncNotify member2 & member3 in task when syncConfigChange fail configExecutorMocked.verify( - () -> ConfigExecutor.scheduleAsyncNotify(any(AsyncNotifyService.AsyncRpcTask.class), anyLong(), any(TimeUnit.class)), - times(2)); + () -> ConfigExecutor.scheduleAsyncNotify(any(AsyncNotifyService.AsyncRpcTask.class), anyLong(), + any(TimeUnit.class)), times(2)); } } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjectorTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjectorTest.java index 3e93e4f69c4..9a0681d2fee 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjectorTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/ConfigRowMapperInjectorTest.java @@ -22,10 +22,10 @@ import com.alibaba.nacos.config.server.model.ConfigInfo; import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; import com.alibaba.nacos.config.server.model.ConfigInfo4Tag; -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; import com.alibaba.nacos.config.server.model.ConfigInfoBase; import com.alibaba.nacos.config.server.model.ConfigInfoBetaWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoChanged; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoTagWrapper; import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; @@ -55,8 +55,8 @@ class ConfigRowMapperInjectorTest { @Test void testInit() { ConfigRowMapperInjector configRowMapperInjector = new ConfigRowMapperInjector(); - assertEquals(ConfigRowMapperInjector.CONFIG_INFO_WRAPPER_ROW_MAPPER, - RowMapperManager.getRowMapper(ConfigRowMapperInjector.CONFIG_INFO_WRAPPER_ROW_MAPPER.getClass().getCanonicalName())); + assertEquals(ConfigRowMapperInjector.CONFIG_INFO_WRAPPER_ROW_MAPPER, RowMapperManager.getRowMapper( + ConfigRowMapperInjector.CONFIG_INFO_WRAPPER_ROW_MAPPER.getClass().getCanonicalName())); } @Test @@ -319,26 +319,38 @@ void testConfigInfoBaseRowMapper() throws SQLException { } @Test - void testConfigInfoAggrRowMapper() throws SQLException { + void testConfigInfoGrayRowMapper() throws SQLException { - ConfigInfoAggr preConfig = new ConfigInfoAggr(); + ConfigInfoGrayWrapper preConfig = new ConfigInfoGrayWrapper(); preConfig.setDataId("testDataId"); preConfig.setGroup("group_id11"); preConfig.setContent("content1123434t"); - preConfig.setDatumId("datum4567890"); + preConfig.setGrayName("grayName"); + preConfig.setGrayRule("rule12345"); preConfig.setTenant("tenang34567890"); preConfig.setAppName("app3456789"); + preConfig.setEncryptedDataKey("key12345"); + Timestamp timestamp = Timestamp.valueOf("2024-12-12 12:34:34"); ResultSetImpl resultSet = Mockito.mock(ResultSetImpl.class); Mockito.when(resultSet.getString(eq("data_id"))).thenReturn(preConfig.getDataId()); Mockito.when(resultSet.getString(eq("group_id"))).thenReturn(preConfig.getGroup()); Mockito.when(resultSet.getString(eq("tenant_id"))).thenReturn(preConfig.getTenant()); - Mockito.when(resultSet.getString(eq("datum_id"))).thenReturn(preConfig.getDatumId()); + Mockito.when(resultSet.getString(eq("gray_name"))).thenReturn(preConfig.getGrayName()); + Mockito.when(resultSet.getString(eq("app_name"))).thenReturn(preConfig.getAppName()); + + Mockito.when(resultSet.getString(eq("gray_rule"))).thenReturn(preConfig.getGrayRule()); + Mockito.when(resultSet.getTimestamp(eq("gmt_modified"))).thenReturn(timestamp); + Mockito.when(resultSet.getString(eq("content"))).thenReturn(preConfig.getContent()); Mockito.when(resultSet.getString(eq("app"))).thenReturn(preConfig.getAppName()); - ConfigRowMapperInjector.ConfigInfoAggrRowMapper configInfoWrapperRowMapper = new ConfigRowMapperInjector.ConfigInfoAggrRowMapper(); + Mockito.when(resultSet.getString(eq("encrypted_data_key"))).thenReturn(preConfig.getEncryptedDataKey()); + + ConfigRowMapperInjector.ConfigInfoGrayWrapperRowMapper configInfoWrapperRowMapper = + new ConfigRowMapperInjector.ConfigInfoGrayWrapperRowMapper(); - ConfigInfoAggr configInfoWrapper = configInfoWrapperRowMapper.mapRow(resultSet, 10); + ConfigInfoGrayWrapper configInfoWrapper = configInfoWrapperRowMapper.mapRow(resultSet, 10); assertEquals(preConfig, configInfoWrapper); + assertEquals(timestamp.getTime(), configInfoWrapper.getLastModified()); } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoAggrPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoAggrPersistServiceImplTest.java deleted file mode 100644 index 33b45b4ade9..00000000000 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoAggrPersistServiceImplTest.java +++ /dev/null @@ -1,241 +0,0 @@ -/* - * 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.config.server.service.repository.embedded; - -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; -import com.alibaba.nacos.persistence.datasource.DataSourceService; -import com.alibaba.nacos.persistence.datasource.DynamicDataSource; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder; -import com.alibaba.nacos.persistence.repository.embedded.operate.DatabaseOperate; -import com.alibaba.nacos.sys.env.EnvUtil; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.springframework.test.context.junit.jupiter.SpringExtension; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_AGGR_ROW_MAPPER; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_CHANGED_ROW_MAPPER; -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.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -/** - * test for embedded config aggr. - * - * @author shiyiyue - */ -@ExtendWith(SpringExtension.class) -class EmbeddedConfigInfoAggrPersistServiceImplTest { - - MockedStatic envUtilMockedStatic; - - MockedStatic embeddedStorageContextHolderMockedStatic; - - MockedStatic dynamicDataSourceMockedStatic; - - @Mock - DynamicDataSource dynamicDataSource; - - @Mock - DatabaseOperate databaseOperate; - - private EmbeddedConfigInfoAggrPersistServiceImpl embededConfigInfoAggrPersistService; - - @Mock - private DataSourceService dataSourceService; - - @BeforeEach - void before() { - embeddedStorageContextHolderMockedStatic = Mockito.mockStatic(EmbeddedStorageContextHolder.class); - dynamicDataSourceMockedStatic = Mockito.mockStatic(DynamicDataSource.class); - envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); - when(DynamicDataSource.getInstance()).thenReturn(dynamicDataSource); - when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); - when(dataSourceService.getDataSourceType()).thenReturn("derby"); - envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))).thenReturn(false); - embededConfigInfoAggrPersistService = new EmbeddedConfigInfoAggrPersistServiceImpl(databaseOperate); - } - - @AfterEach - void after() { - dynamicDataSourceMockedStatic.close(); - envUtilMockedStatic.close(); - embeddedStorageContextHolderMockedStatic.close(); - } - - @Test - void testAddAggrConfigInfoOfEqualContent() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - String datumId = "datumId"; - String appName = "appname1234"; - String content = "content1234"; - - //mock query datumId and equal with current content param. - String existContent = "content1234"; - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, datumId}), eq(String.class))) - .thenReturn(existContent); - //mock insert success - Mockito.when(databaseOperate.update(any(List.class))).thenReturn(true); - - boolean result = embededConfigInfoAggrPersistService.addAggrConfigInfo(dataId, group, tenant, datumId, appName, content); - assertTrue(result); - } - - @Test - void testAddAggrConfigInfoOfAddNewContent() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - String datumId = "datumId"; - String appName = "appname1234"; - String content = "content1234"; - - //mock query datumId and return null. - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, datumId}), eq(String.class))) - .thenReturn(null); - //mock insert success - Mockito.when(databaseOperate.update(any(List.class))).thenReturn(true); - - //execute - boolean result = embededConfigInfoAggrPersistService.addAggrConfigInfo(dataId, group, tenant, datumId, appName, content); - assertTrue(result); - } - - @Test - void testAddAggrConfigInfoOfUpdateNotEqualContent() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - String datumId = "datumId"; - String appName = "appname1234"; - String content = "content1234"; - - //mock query datumId - String existContent = "existContent111"; - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, datumId}), eq(String.class))) - .thenReturn(existContent); - //mock update success,return 1 - Mockito.when(databaseOperate.update(any(List.class))).thenReturn(true); - - //mock update content - boolean result = embededConfigInfoAggrPersistService.addAggrConfigInfo(dataId, group, tenant, datumId, appName, content); - assertTrue(result); - - } - - @Test - void testBatchPublishAggrSuccess() { - - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - //mock query datumId and equal with current content param. - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, "d1"}), eq(String.class))) - .thenReturn("c1"); - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, "d2"}), eq(String.class))) - .thenReturn("c2"); - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, "d3"}), eq(String.class))) - .thenReturn("c3"); - Mockito.when(databaseOperate.update(any(List.class))).thenReturn(true); - - Map datumMap = new HashMap<>(); - datumMap.put("d1", "c1"); - datumMap.put("d2", "c2"); - datumMap.put("d3", "c3"); - String appName = "appname1234"; - boolean result = embededConfigInfoAggrPersistService.batchPublishAggr(dataId, group, tenant, datumMap, appName); - assertTrue(result); - } - - @Test - void testAggrConfigInfoCount() { - String dataId = "dataId11122"; - String group = "group"; - String tenant = "tenant"; - - //mock select count of aggr. - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))) - .thenReturn(new Integer(101)); - int result = embededConfigInfoAggrPersistService.aggrConfigInfoCount(dataId, group, tenant); - assertEquals(101, result); - - } - - @Test - void testFindConfigInfoAggrByPage() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - - //mock query count. - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))).thenReturn(101); - //mock query page list - List configInfoAggrs = new ArrayList<>(); - configInfoAggrs.add(new ConfigInfoAggr()); - configInfoAggrs.add(new ConfigInfoAggr()); - configInfoAggrs.add(new ConfigInfoAggr()); - - Mockito.when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_AGGR_ROW_MAPPER))) - .thenReturn(configInfoAggrs); - int pageNo = 1; - int pageSize = 120; - Page configInfoAggrByPage = embededConfigInfoAggrPersistService.findConfigInfoAggrByPage(dataId, group, tenant, - pageNo, pageSize); - assertEquals(101, configInfoAggrByPage.getTotalCount()); - assertEquals(configInfoAggrs, configInfoAggrByPage.getPageItems()); - - } - - @Test - void testFindAllAggrGroup() { - List configList = new ArrayList<>(); - configList.add(create("dataId", 0)); - configList.add(create("dataId", 1)); - //mock return list - Mockito.when(databaseOperate.queryMany(anyString(), eq(new Object[] {}), eq(CONFIG_INFO_CHANGED_ROW_MAPPER))) - .thenReturn(configList); - - List allAggrGroup = embededConfigInfoAggrPersistService.findAllAggrGroup(); - assertEquals(configList, allAggrGroup); - - } - - private ConfigInfoChanged create(String dataID, int i) { - ConfigInfoChanged hasDatum = new ConfigInfoChanged(); - hasDatum.setDataId(dataID + i); - hasDatum.setTenant("tenant1"); - hasDatum.setGroup("group1"); - return hasDatum; - } - -} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoGrayPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoGrayPersistServiceImplTest.java new file mode 100644 index 00000000000..0f2698bf8be --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoGrayPersistServiceImplTest.java @@ -0,0 +1,422 @@ +/* + * 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.config.server.service.repository.embedded; + +import com.alibaba.nacos.common.utils.MD5Utils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; +import com.alibaba.nacos.config.server.model.ConfigOperateResult; +import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; +import com.alibaba.nacos.persistence.datasource.DataSourceService; +import com.alibaba.nacos.persistence.datasource.DynamicDataSource; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.persistence.repository.embedded.EmbeddedStorageContextHolder; +import com.alibaba.nacos.persistence.repository.embedded.operate.DatabaseOperate; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.test.context.junit.jupiter.SpringExtension; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER; +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; +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.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +/** + * test for embedded config tag. + * + * @author shiyiyue + */ +@ExtendWith(SpringExtension.class) +public class EmbeddedConfigInfoGrayPersistServiceImplTest { + + private EmbeddedConfigInfoGrayPersistServiceImpl embeddedConfigInfoGrayPersistService; + + @Mock + private DataSourceService dataSourceService; + + @Mock + private IdGeneratorManager idGeneratorManager; + + @Mock + private HistoryConfigInfoPersistService historyConfigInfoPersistService; + + MockedStatic envUtilMockedStatic; + + MockedStatic embeddedStorageContextHolderMockedStatic; + + MockedStatic dynamicDataSourceMockedStatic; + + @Mock + DynamicDataSource dynamicDataSource; + + @Mock + DatabaseOperate databaseOperate; + + /** + * before test. + */ + @BeforeEach + public void before() { + embeddedStorageContextHolderMockedStatic = Mockito.mockStatic(EmbeddedStorageContextHolder.class); + dynamicDataSourceMockedStatic = Mockito.mockStatic(DynamicDataSource.class); + envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); + when(DynamicDataSource.getInstance()).thenReturn(dynamicDataSource); + when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); + when(dataSourceService.getDataSourceType()).thenReturn("derby"); + envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))) + .thenReturn(false); + embeddedConfigInfoGrayPersistService = new EmbeddedConfigInfoGrayPersistServiceImpl(databaseOperate, + idGeneratorManager, historyConfigInfoPersistService); + } + + /** + * after each case. + */ + @AfterEach + public void after() { + dynamicDataSourceMockedStatic.close(); + envUtilMockedStatic.close(); + embeddedStorageContextHolderMockedStatic.close(); + } + + @Test + public void testInsertOrUpdateGrayOfAdd() { + String dataId = "dataId111222"; + String group = "group"; + String tenant = "tenant"; + String appName = "appname1234"; + String content = "c12345"; + + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key23456"); + //mock query config state empty and return obj after insert + ConfigInfoStateWrapper configInfoStateWrapper = new ConfigInfoStateWrapper(); + configInfoStateWrapper.setLastModified(System.currentTimeMillis()); + configInfoStateWrapper.setId(234567890L); + String grayName = "tag123grayName"; + String grayRule = ""; + + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null).thenReturn(configInfoStateWrapper); + + String srcIp = "ip345678"; + String srcUser = "user1234567"; + ConfigOperateResult configOperateResult = embeddedConfigInfoGrayPersistService.insertOrUpdateGray(configInfo, + grayName, grayRule, srcIp, srcUser); + + //mock insert invoked. + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), any(), eq(dataId), eq(group), eq(tenant), + eq(grayName), eq(grayRule), eq(appName), eq(content), + eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), + any(Timestamp.class), any(Timestamp.class)), times(1)); + + Mockito.verify(historyConfigInfoPersistService, times(1)) + .insertConfigHistoryAtomic(eq(configInfo.getId()), eq(configInfo), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("I"), eq("gray"), eq(grayName), anyString()); + assertEquals(configInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(configInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + + } + + @Test + public void testInsertOrUpdateGrayOfUpdate() { + String dataId = "dataId111222"; + String group = "group"; + String tenant = "tenant"; + String appName = "appname1234"; + String content = "c12345"; + + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key23456"); + //mock query config state and return obj after update + ConfigInfoStateWrapper configInfoStateWrapper = new ConfigInfoStateWrapper(); + configInfoStateWrapper.setLastModified(System.currentTimeMillis()); + configInfoStateWrapper.setId(234567890L); + String grayName = "tag123grayName"; + final String grayRule = "tag123grayrule"; + + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(new ConfigInfoStateWrapper()) + .thenReturn(configInfoStateWrapper); + + //mock exist config info + ConfigInfoGrayWrapper configAllInfo4Gray = new ConfigInfoGrayWrapper(); + configAllInfo4Gray.setDataId(dataId); + configAllInfo4Gray.setGroup(group); + configAllInfo4Gray.setTenant(tenant); + configAllInfo4Gray.setMd5("old_md5"); + configAllInfo4Gray.setSrcUser("user"); + when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(configAllInfo4Gray); + + String srcIp = "ip345678"; + String srcUser = "user1234567"; + ConfigOperateResult configOperateResult = embeddedConfigInfoGrayPersistService.insertOrUpdateGray(configInfo, + grayName, grayRule, srcIp, srcUser); + //verify update to be invoked + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(content), + eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq(appName), eq(grayRule), eq(dataId), eq(group), eq(tenant), + eq(grayName)), times(1)); + Mockito.verify(historyConfigInfoPersistService, times(1)) + .insertConfigHistoryAtomic(eq(configAllInfo4Gray.getId()), eq(configAllInfo4Gray), eq(srcIp), + eq(srcUser), any(Timestamp.class), eq("U"), eq("gray"), eq(grayName), anyString()); + assertEquals(configInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(configInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + + } + + @Test + public void testInsertOrUpdateGrayCasOfAdd() { + String dataId = "dataId111222"; + String group = "group"; + String tenant = "tenant"; + String appName = "appname1234"; + String content = "c12345"; + + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key23456"); + configInfo.setMd5("casMd5"); + //mock query config state empty and return obj after insert + ConfigInfoStateWrapper configInfoStateWrapper = new ConfigInfoStateWrapper(); + configInfoStateWrapper.setLastModified(System.currentTimeMillis()); + configInfoStateWrapper.setId(234567890L); + String grayName = "tag123grayName"; + String grayRule = ""; + + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null).thenReturn(configInfoStateWrapper); + + String srcIp = "ip345678"; + String srcUser = "user1234567"; + ConfigOperateResult configOperateResult = embeddedConfigInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, + grayName, grayRule, srcIp, srcUser); + //verify insert to be invoked + //mock insert invoked. + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), any(), eq(dataId), eq(group), eq(tenant), + eq(grayName), eq(grayRule), eq(appName), eq(content), + eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), + any(Timestamp.class), any(Timestamp.class)), times(1)); + Mockito.verify(historyConfigInfoPersistService, times(1)) + .insertConfigHistoryAtomic(eq(configInfo.getId()), eq(configInfo), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("I"), eq("gray"), eq(grayName), anyString()); + assertEquals(configInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(configInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + + } + + @Test + public void testInsertOrUpdateGrayCasOfUpdate() { + String dataId = "dataId111222"; + String group = "group"; + String tenant = "tenant"; + String appName = "appname1234"; + String content = "c12345"; + + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key23456"); + configInfo.setMd5("casMd5"); + //mock query config state and return obj after update + ConfigInfoStateWrapper configInfoStateWrapper = new ConfigInfoStateWrapper(); + configInfoStateWrapper.setLastModified(System.currentTimeMillis()); + configInfoStateWrapper.setId(234567890L); + String grayName = "tag123grayName"; + final String grayRule = ""; + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(new ConfigInfoStateWrapper()) + .thenReturn(configInfoStateWrapper); + + //mock exist config info + ConfigInfoGrayWrapper configAllInfo4Gray = new ConfigInfoGrayWrapper(); + configAllInfo4Gray.setDataId(dataId); + configAllInfo4Gray.setGroup(group); + configAllInfo4Gray.setTenant(tenant); + configAllInfo4Gray.setMd5("old_md5"); + configAllInfo4Gray.setSrcUser("user"); + when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(configAllInfo4Gray); + + String srcIp = "ip345678"; + String srcUser = "user1234567"; + + //mock cas update return 1 + Mockito.when(databaseOperate.blockUpdate()).thenReturn(true); + ConfigOperateResult configOperateResult = embeddedConfigInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, + grayName, grayRule, srcIp, srcUser); + //verify update to be invoked + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(content), + eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), eq(appName), + eq(grayRule), eq(dataId), eq(group), eq(tenant), eq(grayName), eq(configInfo.getMd5())), + times(1)); + Mockito.verify(historyConfigInfoPersistService, times(1)) + .insertConfigHistoryAtomic(eq(configAllInfo4Gray.getId()), eq(configAllInfo4Gray), eq(srcIp), + eq(srcUser), any(Timestamp.class), eq("U"), eq("gray"), eq(grayName), anyString()); + assertEquals(configInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(configInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + } + + @Test + public void testRemoveConfigInfoGrayName() { + String dataId = "dataId1112222"; + String group = "group22"; + String tenant = "tenant2"; + final String srcIp = "ip345678"; + final String srcUser = "user1234567"; + final String grayName = "grayName..."; + + //mock exist config info + ConfigInfoGrayWrapper configAllInfo4Gray = new ConfigInfoGrayWrapper(); + configAllInfo4Gray.setDataId(dataId); + configAllInfo4Gray.setGroup(group); + configAllInfo4Gray.setTenant(tenant); + configAllInfo4Gray.setMd5("old_md5"); + + when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(configAllInfo4Gray); + + embeddedConfigInfoGrayPersistService.removeConfigInfoGray(dataId, group, tenant, grayName, srcIp, srcUser); + + //verify delete sql invoked. + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(dataId), eq(group), eq(tenant), + eq(grayName)), times(1)); + Mockito.verify(historyConfigInfoPersistService, times(1)) + .insertConfigHistoryAtomic(eq(configAllInfo4Gray.getId()), eq(configAllInfo4Gray), eq(srcIp), + eq(srcUser), any(Timestamp.class), eq("D"), eq("gray"), eq(grayName), anyString()); + } + + @Test + public void testFindConfigInfo4Gray() { + String dataId = "dataId1112222"; + String group = "group22"; + String tenant = "tenant2"; + String grayName = "tag123345"; + + //mock query tag return obj + ConfigInfoGrayWrapper configInfoGrayWrapperMocked = new ConfigInfoGrayWrapper(); + configInfoGrayWrapperMocked.setLastModified(System.currentTimeMillis()); + + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(configInfoGrayWrapperMocked); + + ConfigInfoGrayWrapper configInfo4GrayReturn = embeddedConfigInfoGrayPersistService.findConfigInfo4Gray(dataId, + group, tenant, grayName); + assertEquals(configInfoGrayWrapperMocked, configInfo4GrayReturn); + } + + @Test + public void testConfigInfoGrayCount() { + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + + //mock count + Mockito.when(databaseOperate.queryOne(anyString(), eq(Integer.class))).thenReturn(308); + //execute & verify + int count = embeddedConfigInfoGrayPersistService.configInfoGrayCount(); + assertEquals(308, count); + } + + @Test + public void testFindAllConfigInfoGrayForDumpAll() { + + //mock count + Mockito.when(databaseOperate.queryOne(anyString(), eq(Integer.class))).thenReturn(308); + List mockGrayList = new ArrayList<>(); + mockGrayList.add(new ConfigInfoGrayWrapper()); + mockGrayList.add(new ConfigInfoGrayWrapper()); + mockGrayList.add(new ConfigInfoGrayWrapper()); + mockGrayList.get(0).setLastModified(System.currentTimeMillis()); + mockGrayList.get(1).setLastModified(System.currentTimeMillis()); + mockGrayList.get(2).setLastModified(System.currentTimeMillis()); + //mock query list + Mockito.when( + databaseOperate.queryMany(anyString(), eq(new Object[] {}), eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))) + .thenReturn(mockGrayList); + int pageNo = 3; + int pageSize = 100; + //execute & verify + Page returnGrayPage = embeddedConfigInfoGrayPersistService.findAllConfigInfoGrayForDumpAll( + pageNo, pageSize); + assertEquals(308, returnGrayPage.getTotalCount()); + assertEquals(mockGrayList, returnGrayPage.getPageItems()); + } + + @Test + public void testFindConfigInfoGrays() { + String dataId = "dataId1112222"; + String group = "group22"; + String tenant = "tenant2"; + List mockedGrays = Arrays.asList("tags1", "tags11", "tags111"); + Mockito.when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))) + .thenReturn(mockedGrays); + List configInfoGrays = embeddedConfigInfoGrayPersistService.findConfigInfoGrays(dataId, group, tenant); + assertEquals(mockedGrays, configInfoGrays); + } + + @Test + public void testFindChangeConfigInfo4Gray() { + List mockList = new ArrayList<>(); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.get(0).setLastModified(System.currentTimeMillis()); + mockList.get(1).setLastModified(System.currentTimeMillis()); + mockList.get(2).setLastModified(System.currentTimeMillis()); + long lastMaxId = 123; + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {timestamp, lastMaxId, 100}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(mockList) + .thenThrow(new CannotGetJdbcConnectionException("mock exception22")); + + List changeConfig = embeddedConfigInfoGrayPersistService.findChangeConfig(timestamp, + lastMaxId, 100); + assertTrue(changeConfig.get(0).getLastModified() == mockList.get(0).getLastModified()); + assertTrue(changeConfig.get(1).getLastModified() == mockList.get(1).getLastModified()); + assertTrue(changeConfig.get(2).getLastModified() == mockList.get(2).getLastModified()); + try { + embeddedConfigInfoGrayPersistService.findChangeConfig(timestamp, lastMaxId, 100); + assertTrue(false); + } catch (CannotGetJdbcConnectionException exception) { + assertEquals("mock exception22", exception.getMessage()); + } + + } + +} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImplTest.java index b62a44521bb..6208f3e1756 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImplTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedConfigInfoPersistServiceImplTest.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.config.server.service.repository.embedded; +import com.alibaba.nacos.api.config.ConfigType; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.common.utils.MD5Utils; import com.alibaba.nacos.common.utils.StringUtils; @@ -28,6 +29,7 @@ import com.alibaba.nacos.config.server.model.ConfigOperateResult; import com.alibaba.nacos.config.server.model.SameConfigPolicy; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.config.server.utils.ConfigExtInfoUtil; import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; import com.alibaba.nacos.persistence.datasource.DataSourceService; import com.alibaba.nacos.persistence.datasource.DynamicDataSource; @@ -105,7 +107,8 @@ void before() { when(DynamicDataSource.getInstance()).thenReturn(dynamicDataSource); when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); when(dataSourceService.getDataSourceType()).thenReturn("derby"); - envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))).thenReturn(false); + envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))) + .thenReturn(false); embeddedConfigInfoPersistService = new EmbeddedConfigInfoPersistServiceImpl(databaseOperate, idGeneratorManager, historyConfigInfoPersistService); } @@ -146,37 +149,41 @@ void testInsertOrUpdateOfInsertConfigSuccess() { configInfoStateWrapperFinalSelect.setId(insertConfigIndoId); configInfoStateWrapperFinalSelect.setLastModified(System.currentTimeMillis()); //mock get config state - Mockito.when( - databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) - .thenReturn(null, configInfoStateWrapperFinalSelect); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null, configInfoStateWrapperFinalSelect); String srcIp = "srcIp"; String srcUser = "srcUser"; //mock insert config info Mockito.doNothing().when(historyConfigInfoPersistService) - .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I")); + .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I"), + eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser))); - ConfigOperateResult configOperateResult = embeddedConfigInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, - configAdvanceInfo); + ConfigOperateResult configOperateResult = embeddedConfigInfoPersistService.insertOrUpdate(srcIp, srcUser, + configInfo, configAdvanceInfo); assertEquals(configInfoStateWrapperFinalSelect.getId(), configOperateResult.getId()); assertEquals(configInfoStateWrapperFinalSelect.getLastModified(), configOperateResult.getLastModified()); //expect insert config info invoked. embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq(dataId), eq(group), eq(tenant), eq(appName), - eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), eq(desc), - eq(use), eq(effect), eq(type), eq(schema), eq(encryptedDataKey)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq(dataId), eq(group), + eq(tenant), eq(appName), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), + eq(srcIp), eq(srcUser), eq(desc), eq(use), eq(effect), eq(type), eq(schema), + eq(encryptedDataKey)), times(1)); //expect insert config tags embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); //expect insert history info Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I")); + .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), + eq("I"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser))); } @@ -209,31 +216,33 @@ void testInsertOrUpdateCasOfInsertConfigSuccess() { configInfoStateWrapperFinalSelect.setId(insertConfigIndoId); configInfoStateWrapperFinalSelect.setLastModified(System.currentTimeMillis()); //mock get config state - Mockito.when( - databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) - .thenReturn(null, configInfoStateWrapperFinalSelect); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null, configInfoStateWrapperFinalSelect); String srcIp = "iptest"; String srcUser = "users"; - ConfigOperateResult configOperateResult = embeddedConfigInfoPersistService.insertOrUpdateCas(srcIp, srcUser, configInfo, - configAdvanceInfo); + ConfigOperateResult configOperateResult = embeddedConfigInfoPersistService.insertOrUpdateCas(srcIp, srcUser, + configInfo, configAdvanceInfo); assertEquals(configInfoStateWrapperFinalSelect.getId(), configOperateResult.getId()); assertEquals(configInfoStateWrapperFinalSelect.getLastModified(), configOperateResult.getLastModified()); //expect insert config info invoked. embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq(dataId), eq(group), eq(tenant), eq(appName), - eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), - eq(desc), eq(use), eq(effect), eq(type), eq(schema), eq(encryptedDatakey)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq(dataId), eq(group), + eq(tenant), eq(appName), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), + eq(srcIp), eq(srcUser), eq(desc), eq(use), eq(effect), eq(type), eq(schema), + eq(encryptedDatakey)), times(1)); //expect insert config tags embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); //expect insert history info Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I")); + .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), + eq("I"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser))); } @Test @@ -261,41 +270,44 @@ void testInsertOrUpdateOfUpdateConfigSuccess() { String encryptedDataKey = "key34567"; configInfo.setEncryptedDataKey(encryptedDataKey); //mock get config state,first and second is not null - Mockito.when( - databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) .thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper()); //mock select config info before update - ConfigInfoWrapper configInfoWrapperOld = new ConfigInfoWrapper(); - configInfoWrapperOld.setDataId(dataId); - configInfoWrapperOld.setGroup(group); - configInfoWrapperOld.setTenant(tenant); - configInfoWrapperOld.setAppName("old_app"); - configInfoWrapperOld.setMd5("old_md5"); - configInfoWrapperOld.setId(12345678765L); - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapperOld); + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName("old_app"); + configAllInfo.setMd5("old_md5"); + configAllInfo.setId(12345678765L); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo); String srcIp = "srcIp"; String srcUser = "srcUser"; embeddedConfigInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, configAdvanceInfo); //expect update config info invoked. - embeddedStorageContextHolderMockedStatic.verify(() -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(content), - eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), eq(appName), eq(desc), - eq(use), eq(effect), eq(type), eq(schema), eq(encryptedDataKey), eq(dataId), eq(group), eq(tenant)), times(1)); + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(content), + eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), eq(appName), + eq(desc), eq(use), eq(effect), eq(type), eq(schema), eq(encryptedDataKey), eq(dataId), + eq(group), eq(tenant)), times(1)); //expect insert config tags embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); //expect insert history info of U Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), - any(Timestamp.class), eq("U")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("U"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); } @@ -326,20 +338,21 @@ void testInsertOrUpdateCasOfUpdateConfigSuccess() { configInfo.setEncryptedDataKey(encryptedDataKey); //mock get config state,first and second is not null - Mockito.when( - databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) .thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper()); //mock select config info before update - ConfigInfoWrapper configInfoWrapperOld = new ConfigInfoWrapper(); - configInfoWrapperOld.setDataId(dataId); - configInfoWrapperOld.setGroup(group); - configInfoWrapperOld.setTenant(tenant); - configInfoWrapperOld.setAppName("old_app11"); - configInfoWrapperOld.setMd5("old_md5"); - configInfoWrapperOld.setId(123456799L); - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapperOld); + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName("old_app"); + configAllInfo.setMd5("old_md5"); + configAllInfo.setId(12345678765L); + + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo); String srcIp = "srcIp"; String srcUser = "srcUser"; @@ -348,21 +361,22 @@ void testInsertOrUpdateCasOfUpdateConfigSuccess() { embeddedStorageContextHolderMockedStatic.verify( () -> EmbeddedStorageContextHolder.addSqlContext(eq(Boolean.TRUE), anyString(), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), eq(srcUser), eq(appName), - eq(desc), eq(use), eq(effect), eq(type), eq(schema), eq(encryptedDataKey), eq(dataId), eq(group), eq(tenant), - eq(casMd5)), times(1)); + eq(desc), eq(use), eq(effect), eq(type), eq(schema), eq(encryptedDataKey), eq(dataId), + eq(group), eq(tenant), eq(casMd5)), times(1)); //expect insert config tags embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag1"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), - eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), anyLong(), eq("tag2"), + eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)), times(1)); //expect insert history info of U Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), - any(Timestamp.class), eq("U")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("U"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); } @@ -373,17 +387,21 @@ void testRemoveConfigInfo() { String tenant = "tenant4567890"; //mock exist config info - ConfigInfoWrapper configInfoWrapperOld = new ConfigInfoWrapper(); - configInfoWrapperOld.setDataId(dataId); - configInfoWrapperOld.setGroup(group); - configInfoWrapperOld.setTenant(tenant); - configInfoWrapperOld.setAppName("old_app"); - configInfoWrapperOld.setContent("old content"); - configInfoWrapperOld.setMd5("old_md5"); - configInfoWrapperOld.setId(12345678765L); - configInfoWrapperOld.setEncryptedDataKey("key3456"); - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapperOld); + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName("old_app"); + configAllInfo.setMd5("old_md5"); + configAllInfo.setId(12345678765L); + configAllInfo.setType(ConfigType.JSON.getType()); + configAllInfo.setSchema("testschema"); + configAllInfo.setCreateUser("testuser"); + configAllInfo.setEffect("online"); + configAllInfo.setDesc("desc"); + configAllInfo.setUse("use124"); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo); String srcIp = "srcIp1234"; String srcUser = "srcUser"; Mockito.when(databaseOperate.update(any())).thenReturn(true); @@ -391,14 +409,15 @@ void testRemoveConfigInfo() { //expect delete config to be invoked embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(dataId), eq(group), eq(tenant)), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(dataId), eq(group), eq(tenant)), + times(1)); //expect delete config tag to be invoked embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(configInfoWrapperOld.getId())), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(configAllInfo.getId())), times(1)); //expect insert delete history Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), eq(configInfoWrapperOld), eq(srcIp), eq(srcUser), any(), - eq("D")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), eq(configAllInfo), eq(srcIp), eq(srcUser), any(), + eq("D"), eq("formal"), eq(null), eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); } @@ -406,13 +425,24 @@ void testRemoveConfigInfo() { void testRemoveConfigInfoByIds() { //mock exist config info - List configInfos = new ArrayList<>(); - configInfos.add(new ConfigInfo("data1", "group", "tenant", "app", "content")); - configInfos.add(new ConfigInfo("data2", "grou2", "tenan2", "app2", "content2")); + final List configAllInfos = new ArrayList<>(); + final ConfigAllInfo configAllInfo1 = new ConfigAllInfo(); + final ConfigAllInfo configAllInfo2 = new ConfigAllInfo(); + configAllInfo1.setDataId("dataId1"); + configAllInfo1.setGroup("group1"); + configAllInfo1.setTenant("tenant1"); + configAllInfo1.setAppName("app1"); + configAllInfo2.setDataId("dataId2"); + configAllInfo2.setGroup("group2"); + configAllInfo2.setTenant("tenant2"); + configAllInfo2.setAppName("app2"); + configAllInfos.add(configAllInfo1); + configAllInfos.add(configAllInfo2); List deleteIds = Arrays.asList(12344L, 3456789L); - configInfos.get(0).setId(12344L); - configInfos.get(1).setId(3456789L); - Mockito.when(databaseOperate.queryMany(anyString(), eq(deleteIds.toArray()), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(configInfos); + configAllInfos.get(0).setId(12344L); + configAllInfos.get(1).setId(3456789L); + Mockito.when(databaseOperate.queryMany(anyString(), eq(deleteIds.toArray()), eq(CONFIG_ALL_INFO_ROW_MAPPER))) + .thenReturn(configAllInfos); String srcIp = "srcIp1234"; String srcUser = "srcUser"; Mockito.when(databaseOperate.update(any())).thenReturn(true); @@ -425,15 +455,19 @@ void testRemoveConfigInfoByIds() { embeddedStorageContextHolderMockedStatic.verify( () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(deleteId0), eq(deleteId1)), times(1)); //expect delete config tag to be invoked - embeddedStorageContextHolderMockedStatic.verify(() -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(deleteId0)), - times(1)); - embeddedStorageContextHolderMockedStatic.verify(() -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(deleteId1)), - times(1)); + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(deleteId0)), times(1)); + embeddedStorageContextHolderMockedStatic.verify( + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(deleteId1)), times(1)); //expect insert delete history Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfos.get(0).getId()), eq(configInfos.get(0)), eq(srcIp), eq(srcUser), any(), eq("D")); + .insertConfigHistoryAtomic(eq(configAllInfos.get(0).getId()), eq(configAllInfos.get(0)), eq(srcIp), + eq(srcUser), any(), eq("D"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfos.get(0)))); Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfos.get(1).getId()), eq(configInfos.get(1)), eq(srcIp), eq(srcUser), any(), eq("D")); + .insertConfigHistoryAtomic(eq(configAllInfos.get(1).getId()), eq(configAllInfos.get(1)), eq(srcIp), + eq(srcUser), any(), eq("D"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfos.get(1)))); } @@ -452,22 +486,26 @@ void testBatchInsertOrUpdateOverwrite() throws NacosException { //mock add config 1 success,config 2 fail and skip,config 3 success Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(0).getDataId(), configInfoList.get(0).getGroup(), configInfoList.get(0).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + eq(new Object[] {configInfoList.get(0).getDataId(), configInfoList.get(0).getGroup(), + configInfoList.get(0).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(null); Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), configInfoList.get(1).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(new ConfigInfoStateWrapper()); + eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), + configInfoList.get(1).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(new ConfigInfoStateWrapper()); Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(2).getDataId(), configInfoList.get(2).getGroup(), configInfoList.get(1).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + eq(new Object[] {configInfoList.get(2).getDataId(), configInfoList.get(2).getGroup(), + configInfoList.get(1).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(null); //mock query config info during update ConfigInfoWrapper configInfoWrapper = new ConfigInfoWrapper(); Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), configInfoList.get(1).getTenant()}), - eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(configInfoWrapper); + eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), + configInfoList.get(1).getTenant()}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) + .thenReturn(configInfoWrapper); - Map stringObjectMap = embeddedConfigInfoPersistService.batchInsertOrUpdate(configInfoList, srcUser, srcIp, - configAdvanceInfo, SameConfigPolicy.OVERWRITE); + Map stringObjectMap = embeddedConfigInfoPersistService.batchInsertOrUpdate(configInfoList, + srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.OVERWRITE); assertEquals(3, stringObjectMap.get("succCount")); assertEquals(0, stringObjectMap.get("skipCount")); } @@ -487,20 +525,24 @@ void testBatchInsertOrUpdateSkip() throws NacosException { //mock add config 1 success,config 2 fail and skip,config 3 success Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(0).getDataId(), configInfoList.get(0).getGroup(), configInfoList.get(0).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + eq(new Object[] {configInfoList.get(0).getDataId(), configInfoList.get(0).getGroup(), + configInfoList.get(0).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(null); Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), configInfoList.get(1).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(new ConfigInfoStateWrapper()); + eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), + configInfoList.get(1).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(new ConfigInfoStateWrapper()); Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(2).getDataId(), configInfoList.get(2).getGroup(), configInfoList.get(1).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + eq(new Object[] {configInfoList.get(2).getDataId(), configInfoList.get(2).getGroup(), + configInfoList.get(1).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(null); - Map stringObjectMap = embeddedConfigInfoPersistService.batchInsertOrUpdate(configInfoList, srcUser, srcIp, - configAdvanceInfo, SameConfigPolicy.SKIP); + Map stringObjectMap = embeddedConfigInfoPersistService.batchInsertOrUpdate(configInfoList, + srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.SKIP); assertEquals(2, stringObjectMap.get("succCount")); assertEquals(1, stringObjectMap.get("skipCount")); - assertEquals(configInfoList.get(1).getDataId(), ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); + assertEquals(configInfoList.get(1).getDataId(), + ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); } @Test @@ -518,23 +560,28 @@ void testBatchInsertOrUpdateAbort() throws NacosException { //mock add config 1 success,config 2 fail and abort,config 3 not operated Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(0).getDataId(), configInfoList.get(0).getGroup(), configInfoList.get(0).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + eq(new Object[] {configInfoList.get(0).getDataId(), configInfoList.get(0).getGroup(), + configInfoList.get(0).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(null); Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), configInfoList.get(1).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(new ConfigInfoStateWrapper()); + eq(new Object[] {configInfoList.get(1).getDataId(), configInfoList.get(1).getGroup(), + configInfoList.get(1).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(new ConfigInfoStateWrapper()); Mockito.when(databaseOperate.queryOne(anyString(), - eq(new Object[] {configInfoList.get(2).getDataId(), configInfoList.get(2).getGroup(), configInfoList.get(1).getTenant()}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + eq(new Object[] {configInfoList.get(2).getDataId(), configInfoList.get(2).getGroup(), + configInfoList.get(1).getTenant()}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(null); - Map stringObjectMap = embeddedConfigInfoPersistService.batchInsertOrUpdate(configInfoList, srcUser, srcIp, - configAdvanceInfo, SameConfigPolicy.ABORT); + Map stringObjectMap = embeddedConfigInfoPersistService.batchInsertOrUpdate(configInfoList, + srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.ABORT); assertEquals(1, stringObjectMap.get("succCount")); assertEquals(1, stringObjectMap.get("skipCount")); // config 2 failed - assertEquals(configInfoList.get(1).getDataId(), ((List>) stringObjectMap.get("failData")).get(0).get("dataId")); + assertEquals(configInfoList.get(1).getDataId(), + ((List>) stringObjectMap.get("failData")).get(0).get("dataId")); //skip config 3 - assertEquals(configInfoList.get(2).getDataId(), ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); + assertEquals(configInfoList.get(2).getDataId(), + ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); } private ConfigAllInfo createMockConfigAllInfo(long mockId) { @@ -594,7 +641,8 @@ void testFindConfigInfoById() { long id = 1234567890876L; ConfigInfo configInfo = new ConfigInfo(); configInfo.setId(id); - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {id}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(configInfo); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {id}), eq(CONFIG_INFO_ROW_MAPPER))) + .thenReturn(configInfo); ConfigInfo configReturn = embeddedConfigInfoPersistService.findConfigInfo(id); assertEquals(id, configReturn.getId()); } @@ -609,8 +657,8 @@ void testFindConfigInfoByDataId() { configInfoWrapper.setGroup(group); configInfoWrapper.setTenant(tenant); - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapper); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(configInfoWrapper); ConfigInfo configReturn = embeddedConfigInfoPersistService.findConfigInfo(dataId, group, tenant); assertEquals(dataId, configReturn.getDataId()); } @@ -622,17 +670,18 @@ void testFindConfigInfo4Page() { String tenant = "tenant4567890"; //mock total count - when(databaseOperate.queryOne(anyString(), eq(new Object[] {tenant, dataId, group}), eq(Integer.class))).thenReturn(new Integer(9)); + when(databaseOperate.queryOne(anyString(), eq(new Object[] {tenant, dataId, group}), + eq(Integer.class))).thenReturn(new Integer(9)); //mock page list List result = new ArrayList<>(); result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); - when(databaseOperate.queryMany(anyString(), eq(new Object[] {tenant, dataId, group}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn( - result); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {tenant, dataId, group}), + eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); Map configAdvanceInfo = new HashMap<>(); - Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); @@ -657,8 +706,8 @@ void testFindConfigInfo4PageWithTags() { when(databaseOperate.queryMany(anyString(), eq(new Object[] {tenant, dataId, group, "tags1", "tags3"}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); - Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); } @@ -686,7 +735,8 @@ void testConfigInfoCountByTenant() { String tenant = "tenant124"; //mock total count - when(databaseOperate.queryOne(anyString(), eq(new Object[] {tenant}), eq(Integer.class))).thenReturn(new Integer(90)); + when(databaseOperate.queryOne(anyString(), eq(new Object[] {tenant}), eq(Integer.class))).thenReturn( + new Integer(90)); int count = embeddedConfigInfoPersistService.configInfoCount(tenant); assertEquals(90, count); @@ -712,19 +762,19 @@ void testFindConfigInfoLike4Page() { configAdvanceInfo.put("content", content); //mock total count when(databaseOperate.queryOne(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content}), - eq(Integer.class))).thenReturn(new Integer(9)); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, + content}), eq(Integer.class))).thenReturn(new Integer(9)); //mock page list List result = new ArrayList<>(); result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); when(databaseOperate.queryMany(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content}), - eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, + content}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); - Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); @@ -744,19 +794,19 @@ void testFindConfigInfoLike4PageWithTags() { String tenant = "tenant4567890"; //mock total count when(databaseOperate.queryOne(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, "tags", "tag2"}), - eq(Integer.class))).thenReturn(new Integer(9)); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, + "tags", "tag2"}), eq(Integer.class))).thenReturn(new Integer(9)); //mock page list List result = new ArrayList<>(); result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); when(databaseOperate.queryMany(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, "tags", "tag2"}), - eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, + "tags", "tag2"}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); - Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = embeddedConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); @@ -776,7 +826,8 @@ void testFindChangeConfig() { when(databaseOperate.queryMany(anyString(), eq(new Object[] {startTime, lastMaxId, pageSize}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(result); - List configInfo4List = embeddedConfigInfoPersistService.findChangeConfig(startTime, lastMaxId, pageSize); + List configInfo4List = embeddedConfigInfoPersistService.findChangeConfig(startTime, + lastMaxId, pageSize); assertEquals(result.size(), configInfo4List.size()); } @@ -788,7 +839,8 @@ void testSelectTagByConfig() { //mock page list List tagStrings = Arrays.asList("", "", ""); - when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenReturn(tagStrings); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenReturn(tagStrings); List configTags = embeddedConfigInfoPersistService.selectTagByConfig(dataId, group, tenant); assertEquals(tagStrings, configTags); } @@ -801,7 +853,8 @@ void testFindConfigInfosByIds() { result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); - when(databaseOperate.queryMany(anyString(), eq(new Object[] {123L, 1232345L}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {123L, 1232345L}), + eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); String ids = "123,1232345"; List configInfosByIds = embeddedConfigInfoPersistService.findConfigInfosByIds(ids); assertEquals(result.size(), configInfosByIds.size()); @@ -821,7 +874,8 @@ void testFindConfigAdvanceInfo() { String tenant = "tenant13245"; //mock select tags List mockTags = Arrays.asList("tag1", "tag2", "tag3"); - when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenReturn(mockTags); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenReturn(mockTags); String schema = "schema12345654"; //mock select config advance @@ -831,7 +885,8 @@ void testFindConfigAdvanceInfo() { eq(CONFIG_ADVANCE_INFO_ROW_MAPPER))).thenReturn(mockedAdvance); //execute return mock obj - ConfigAdvanceInfo configAdvanceInfo = embeddedConfigInfoPersistService.findConfigAdvanceInfo(dataId, group, tenant); + ConfigAdvanceInfo configAdvanceInfo = embeddedConfigInfoPersistService.findConfigAdvanceInfo(dataId, group, + tenant); //expect check schema & tags. assertEquals(mockedAdvance.getSchema(), configAdvanceInfo.getSchema()); assertEquals(String.join(",", mockTags), configAdvanceInfo.getConfigTags()); @@ -845,14 +900,15 @@ void testFindConfigAllInfo() { String tenant = "tenant13245"; //mock select tags List mockTags = Arrays.asList("tag1", "tag2", "tag3"); - when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenReturn(mockTags); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenReturn(mockTags); String schema = "schema12345654"; //mock select config advance ConfigAllInfo mockedConfig = new ConfigAllInfo(); mockedConfig.setSchema(schema); - when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn( - mockedConfig); + when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockedConfig); //execute return mock obj ConfigAllInfo configAllInfo = embeddedConfigInfoPersistService.findConfigAllInfo(dataId, group, tenant); @@ -877,7 +933,8 @@ void testFindConfigInfoState() { eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfig); //execute return mock obj - ConfigInfoStateWrapper configInfoStateWrapper = embeddedConfigInfoPersistService.findConfigInfoState(dataId, group, tenant); + ConfigInfoStateWrapper configInfoStateWrapper = embeddedConfigInfoPersistService.findConfigInfoState(dataId, + group, tenant); //expect check schema & tags. assertEquals(mockedConfig.getId(), configInfoStateWrapper.getId()); assertEquals(mockedConfig.getLastModified(), configInfoStateWrapper.getLastModified()); @@ -898,19 +955,19 @@ void testFindAllConfigInfo4Export() { String appName = "appName1243"; List ids = Arrays.asList(132L, 1343L, 245L); - when(databaseOperate.queryMany(anyString(), eq(new Object[] {132L, 1343L, 245L}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn( - mockConfigs); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {132L, 1343L, 245L}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockConfigs); //execute return mock obj - List configAllInfosIds = embeddedConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, tenant, appName, - ids); + List configAllInfosIds = embeddedConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, + tenant, appName, ids); //expect check assertEquals(mockConfigs, configAllInfosIds); when(databaseOperate.queryMany(anyString(), eq(new Object[] {tenant, dataId, group, appName}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockConfigs); //execute return mock obj - List configAllInfosWithDataId = embeddedConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, tenant, - appName, null); + List configAllInfosWithDataId = embeddedConfigInfoPersistService.findAllConfigInfo4Export(dataId, + group, tenant, appName, null); //expect check assertEquals(mockConfigs, configAllInfosWithDataId); @@ -925,9 +982,11 @@ void testQueryConfigInfoByNamespace() { mockConfigs.add(createMockConfigInfoWrapper(1)); mockConfigs.add(createMockConfigInfoWrapper(2)); String tenant = "tenant13245"; - when(databaseOperate.queryMany(anyString(), eq(new Object[] {tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(mockConfigs); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {tenant}), + eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(mockConfigs); //execute return mock obj - List configInfoWrappers = embeddedConfigInfoPersistService.queryConfigInfoByNamespace(tenant); + List configInfoWrappers = embeddedConfigInfoPersistService.queryConfigInfoByNamespace( + tenant); //expect check assertEquals(mockConfigs, configInfoWrappers); } @@ -987,10 +1046,12 @@ void testFindAllConfigInfoFragment() { mockConfigs.add(createMockConfigInfoWrapper(1)); mockConfigs.add(createMockConfigInfoWrapper(2)); long lastId = 10111L; - when(databaseOperate.queryMany(anyString(), eq(new Object[] {lastId}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(mockConfigs); + when(databaseOperate.queryMany(anyString(), eq(new Object[] {lastId}), + eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(mockConfigs); int pageSize = 100; //execute return mock obj - Page returnConfigPage = embeddedConfigInfoPersistService.findAllConfigInfoFragment(lastId, pageSize, true); + Page returnConfigPage = embeddedConfigInfoPersistService.findAllConfigInfoFragment(lastId, + pageSize, true); //expect check assertEquals(mockConfigs, returnConfigPage.getPageItems()); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImplTest.java index 47a33bf16a2..4252664e02b 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImplTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/embedded/EmbeddedHistoryConfigInfoPersistServiceImplTest.java @@ -38,7 +38,6 @@ import java.util.ArrayList; import java.util.List; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_DETAIL_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_LIST_ROW_MAPPER; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -80,7 +79,8 @@ void before() { when(DynamicDataSource.getInstance()).thenReturn(dynamicDataSource); when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); when(dataSourceService.getDataSourceType()).thenReturn("derby"); - envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))).thenReturn(false); + envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))) + .thenReturn(false); embeddedHistoryConfigInfoPersistService = new EmbeddedHistoryConfigInfoPersistServiceImpl(databaseOperate); } @@ -102,17 +102,21 @@ void testInsertConfigHistoryAtomic() { String srcUser = "user12345"; String srcIp = "ip1234"; String ops = "D"; + String publishType = "formal"; + String extraInfo = "{\"type\":\"properties\"}"; Timestamp timestamp = new Timestamp(System.currentTimeMillis()); ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); configInfo.setEncryptedDataKey("key23456"); //expect insert success,verify insert invoked - embeddedHistoryConfigInfoPersistService.insertConfigHistoryAtomic(id, configInfo, srcIp, srcUser, timestamp, ops); + embeddedHistoryConfigInfoPersistService.insertConfigHistoryAtomic(id, configInfo, srcIp, srcUser, timestamp, + ops, publishType, null, extraInfo); //verify insert to be invoked embeddedStorageContextHolderMockedStatic.verify( - () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(id), eq(dataId), eq(group), eq(tenant), eq(appName), - eq(content), eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(timestamp), eq(ops), - eq(configInfo.getEncryptedDataKey())), times(1)); + () -> EmbeddedStorageContextHolder.addSqlContext(anyString(), eq(id), eq(dataId), eq(group), eq(tenant), + eq(appName), eq(content), eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(timestamp), + eq(ops), eq(publishType), eq(""), eq(extraInfo), eq(configInfo.getEncryptedDataKey())), + times(1)); } @Test @@ -129,38 +133,41 @@ void testRemoveConfigHistory() { void testFindDeletedConfig() { //mock query list return - ConfigInfoStateWrapper mockObj1 = new ConfigInfoStateWrapper(); + ConfigHistoryInfo mockObj1 = new ConfigHistoryInfo(); mockObj1.setDataId("data_id1"); mockObj1.setGroup("group_id1"); mockObj1.setTenant("tenant_id1"); mockObj1.setMd5("md51"); - mockObj1.setLastModified(System.currentTimeMillis()); + mockObj1.setLastModifiedTime(new Timestamp(System.currentTimeMillis())); - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(mockObj1); - ConfigInfoStateWrapper mockObj2 = new ConfigInfoStateWrapper(); + ConfigHistoryInfo mockObj2 = new ConfigHistoryInfo(); mockObj2.setDataId("data_id2"); mockObj2.setGroup("group_id2"); mockObj2.setTenant("tenant_id2"); mockObj2.setMd5("md52"); + mockObj2.setLastModifiedTime(new Timestamp(System.currentTimeMillis())); list.add(mockObj2); int pageSize = 1233; long startId = 23456; Timestamp timestamp = new Timestamp(System.currentTimeMillis()); - Mockito.when(databaseOperate.queryMany(anyString(), eq(new Object[] {timestamp, startId, pageSize}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(list); + String publishType = "formal"; + Mockito.when( + databaseOperate.queryMany(anyString(), eq(new Object[] {publishType, timestamp, startId, pageSize}), + eq(HISTORY_DETAIL_ROW_MAPPER))).thenReturn(list); //execute - List deletedConfig = embeddedHistoryConfigInfoPersistService.findDeletedConfig(timestamp, startId, - pageSize); + List deletedConfig = embeddedHistoryConfigInfoPersistService.findDeletedConfig( + timestamp, startId, pageSize, "formal"); //expect verify assertEquals("data_id1", deletedConfig.get(0).getDataId()); assertEquals("group_id1", deletedConfig.get(0).getGroup()); assertEquals("tenant_id1", deletedConfig.get(0).getTenant()); - assertEquals(mockObj1.getLastModified(), deletedConfig.get(0).getLastModified()); + assertEquals(mockObj1.getLastModifiedTime(), new Timestamp(deletedConfig.get(0).getLastModified())); assertEquals("data_id2", deletedConfig.get(1).getDataId()); assertEquals("group_id2", deletedConfig.get(1).getGroup()); assertEquals("tenant_id2", deletedConfig.get(1).getTenant()); - assertEquals(mockObj2.getLastModified(), deletedConfig.get(1).getLastModified()); + assertEquals(mockObj2.getLastModifiedTime(), new Timestamp(deletedConfig.get(1).getLastModified())); } @Test @@ -170,19 +177,20 @@ void testFindConfigHistory() { String tenant = "tenant34567"; //mock count - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))).thenReturn(300); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))) + .thenReturn(300); //mock list List mockList = new ArrayList<>(); mockList.add(createMockConfigHistoryInfo(0)); mockList.add(createMockConfigHistoryInfo(1)); mockList.add(createMockConfigHistoryInfo(2)); - Mockito.when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), eq(HISTORY_LIST_ROW_MAPPER))) - .thenReturn(mockList); + Mockito.when(databaseOperate.queryMany(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(HISTORY_LIST_ROW_MAPPER))).thenReturn(mockList); int pageSize = 100; int pageNo = 2; //execute & verify - Page historyReturn = embeddedHistoryConfigInfoPersistService.findConfigHistory(dataId, group, tenant, pageNo, - pageSize); + Page historyReturn = embeddedHistoryConfigInfoPersistService.findConfigHistory(dataId, group, + tenant, pageNo, pageSize); assertEquals(mockList, historyReturn.getPageItems()); assertEquals(300, historyReturn.getTotalCount()); @@ -218,7 +226,8 @@ void testFindConfigHistoryCountByTime() { Timestamp timestamp = new Timestamp(System.currentTimeMillis()); //mock count - Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {timestamp}), eq(Integer.class))).thenReturn(308); + Mockito.when(databaseOperate.queryOne(anyString(), eq(new Object[] {timestamp}), eq(Integer.class))) + .thenReturn(308); //execute & verify int count = embeddedHistoryConfigInfoPersistService.findConfigHistoryCountByTime(timestamp); assertEquals(308, count); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoAggrPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoAggrPersistServiceImplTest.java deleted file mode 100644 index 93209fb85fa..00000000000 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoAggrPersistServiceImplTest.java +++ /dev/null @@ -1,330 +0,0 @@ -/* - * 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.config.server.service.repository.extrnal; - -import com.alibaba.nacos.config.server.model.ConfigInfoAggr; -import com.alibaba.nacos.config.server.model.ConfigInfoChanged; -import com.alibaba.nacos.config.server.service.sql.ExternalStorageUtils; -import com.alibaba.nacos.config.server.utils.TestCaseUtils; -import com.alibaba.nacos.persistence.datasource.DataSourceService; -import com.alibaba.nacos.persistence.datasource.DynamicDataSource; -import com.alibaba.nacos.persistence.model.Page; -import com.alibaba.nacos.sys.env.EnvUtil; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.Mock; -import org.mockito.MockedStatic; -import org.mockito.Mockito; -import org.springframework.dao.EmptyResultDataAccessException; -import org.springframework.jdbc.CannotGetJdbcConnectionException; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.test.context.junit.jupiter.SpringExtension; -import org.springframework.transaction.TransactionSystemException; -import org.springframework.transaction.support.TransactionTemplate; - -import java.sql.Timestamp; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_AGGR_ROW_MAPPER; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_CHANGED_ROW_MAPPER; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.when; - -@ExtendWith(SpringExtension.class) -class ExternalConfigInfoAggrPersistServiceImplTest { - - MockedStatic envUtilMockedStatic; - - MockedStatic externalStorageUtilsMockedStatic; - - MockedStatic dynamicDataSourceMockedStatic; - - @Mock - DynamicDataSource dynamicDataSource; - - private ExternalConfigInfoAggrPersistServiceImpl externalConfigInfoAggrPersistService; - - @Mock - private DataSourceService dataSourceService; - - @Mock - private JdbcTemplate jdbcTemplate; - - private TransactionTemplate transactionTemplate = TestCaseUtils.createMockTransactionTemplate(); - - @BeforeEach - void before() { - dynamicDataSourceMockedStatic = Mockito.mockStatic(DynamicDataSource.class); - envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); - externalStorageUtilsMockedStatic = Mockito.mockStatic(ExternalStorageUtils.class); - when(DynamicDataSource.getInstance()).thenReturn(dynamicDataSource); - when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); - when(dataSourceService.getTransactionTemplate()).thenReturn(transactionTemplate); - when(dataSourceService.getJdbcTemplate()).thenReturn(jdbcTemplate); - when(dataSourceService.getDataSourceType()).thenReturn("mysql"); - envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))).thenReturn(false); - externalConfigInfoAggrPersistService = new ExternalConfigInfoAggrPersistServiceImpl(); - - } - - @AfterEach - void after() { - dynamicDataSourceMockedStatic.close(); - envUtilMockedStatic.close(); - externalStorageUtilsMockedStatic.close(); - } - - @Test - void testAddAggrConfigInfoOfEqualContent() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - String datumId = "datumId"; - String appName = "appname1234"; - String content = "content1234"; - - //mock query datumId and equal with current content param. - String existContent = "content1234"; - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, datumId}), eq(String.class))).thenReturn( - existContent); - - boolean result = externalConfigInfoAggrPersistService.addAggrConfigInfo(dataId, group, tenant, datumId, appName, content); - assertTrue(result); - } - - @Test - void testAddAggrConfigInfoOfAddNewContent() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - String datumId = "datumId"; - String appName = "appname1234"; - String content = "content1234"; - - //mock query datumId and throw EmptyResultDataAccessException. - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, datumId}), eq(String.class))).thenThrow( - new EmptyResultDataAccessException(1)); - //mock insert success - when(jdbcTemplate.update(anyString(), eq(dataId), eq(group), eq(tenant), eq(datumId), eq(appName), eq(content), - any(Timestamp.class))).thenReturn(1); - - //execute - boolean result = externalConfigInfoAggrPersistService.addAggrConfigInfo(dataId, group, tenant, datumId, appName, content); - assertTrue(result); - } - - @Test - void testAddAggrConfigInfoOfUpdateNotEqualContent() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - String datumId = "datumId"; - String appName = "appname1234"; - String content = "content1234"; - - //mock query datumId - String existContent = "existContent111"; - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, datumId}), eq(String.class))).thenReturn( - existContent); - //mock update success,return 1 - when(jdbcTemplate.update(anyString(), eq(content), any(Timestamp.class), eq(dataId), eq(group), eq(tenant), - eq(datumId))).thenReturn(1); - //mock update content - boolean result = externalConfigInfoAggrPersistService.addAggrConfigInfo(dataId, group, tenant, datumId, appName, content); - assertTrue(result); - - } - - @Test - void testAddAggrConfigInfoOfException() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - String datumId = "datumId"; - String appName = "appname1234"; - String content = "content1234"; - - //mock query datumId and throw EmptyResultDataAccessException. - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, datumId}), eq(String.class))).thenThrow( - new CannotGetJdbcConnectionException("mock exp")); - try { - externalConfigInfoAggrPersistService.addAggrConfigInfo(dataId, group, tenant, datumId, appName, content); - assertTrue(false); - } catch (Exception exp) { - assertEquals("mock exp", exp.getMessage()); - } - } - - @Test - void testBatchPublishAggrSuccess() { - - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - //mock query datumId and equal with current content param. - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, "d1"}), eq(String.class))).thenReturn("c1"); - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, "d2"}), eq(String.class))).thenReturn("c2"); - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, "d3"}), eq(String.class))).thenReturn("c3"); - Map datumMap = new HashMap<>(); - datumMap.put("d1", "c1"); - datumMap.put("d2", "c2"); - datumMap.put("d3", "c3"); - String appName = "appname1234"; - boolean result = externalConfigInfoAggrPersistService.batchPublishAggr(dataId, group, tenant, datumMap, appName); - assertTrue(result); - } - - @Test - void testBatchPublishAggrException() { - - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - //mock query datumId and equal with current content param. - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, "d1"}), eq(String.class))).thenThrow( - new TransactionSystemException("c1t fail")); - Map datumMap = new HashMap<>(); - datumMap.put("d1", "c1"); - datumMap.put("d2", "c2"); - datumMap.put("d3", "c3"); - String appName = "appname1234"; - boolean result = externalConfigInfoAggrPersistService.batchPublishAggr(dataId, group, tenant, datumMap, appName); - assertFalse(result); - } - - @Test - void testAggrConfigInfoCount() { - String dataId = "dataId11122"; - String group = "group"; - String tenant = "tenant"; - - //mock select count of aggr. - when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class), eq(dataId), eq(group), eq(tenant))).thenReturn(new Integer(101)); - int result = externalConfigInfoAggrPersistService.aggrConfigInfoCount(dataId, group, tenant); - assertEquals(101, result); - - } - - @Test - void testFindConfigInfoAggrByPage() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - - //mock query count. - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))).thenReturn(101); - //mock query page list - List configInfoAggrs = new ArrayList<>(); - configInfoAggrs.add(new ConfigInfoAggr()); - configInfoAggrs.add(new ConfigInfoAggr()); - configInfoAggrs.add(new ConfigInfoAggr()); - - when(jdbcTemplate.query(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_AGGR_ROW_MAPPER))).thenReturn( - configInfoAggrs); - int pageNo = 1; - int pageSize = 120; - Page configInfoAggrByPage = externalConfigInfoAggrPersistService.findConfigInfoAggrByPage(dataId, group, tenant, - pageNo, pageSize); - assertEquals(101, configInfoAggrByPage.getTotalCount()); - assertEquals(configInfoAggrs, configInfoAggrByPage.getPageItems()); - - } - - @Test - void testFindConfigInfoAggrByPageOfException() { - String dataId = "dataId111"; - String group = "group"; - String tenant = "tenant"; - - //mock query count exception. - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))).thenThrow( - new CannotGetJdbcConnectionException("mock fail222")); - - try { - int pageNo = 1; - int pageSize = 120; - externalConfigInfoAggrPersistService.findConfigInfoAggrByPage(dataId, group, tenant, pageNo, pageSize); - assertTrue(false); - } catch (Throwable throwable) { - assertEquals("mock fail222", throwable.getMessage()); - } - } - - @Test - void testFindAllAggrGroup() { - List configList = new ArrayList<>(); - configList.add(create("dataId", 0)); - configList.add(create("dataId", 1)); - //mock return list - when(jdbcTemplate.query(anyString(), eq(new Object[] {}), eq(CONFIG_INFO_CHANGED_ROW_MAPPER))).thenReturn(configList); - - List allAggrGroup = externalConfigInfoAggrPersistService.findAllAggrGroup(); - assertEquals(configList, allAggrGroup); - - } - - @Test - void testFindAllAggrGroupException() { - - //mock throw CannotGetJdbcConnectionException - when(jdbcTemplate.query(anyString(), eq(new Object[] {}), eq(CONFIG_INFO_CHANGED_ROW_MAPPER))).thenThrow( - new CannotGetJdbcConnectionException("mock fail")); - try { - externalConfigInfoAggrPersistService.findAllAggrGroup(); - assertTrue(false); - } catch (Throwable throwable) { - assertEquals("mock fail", throwable.getMessage()); - } - - //mock throw EmptyResultDataAccessException - when(jdbcTemplate.query(anyString(), eq(new Object[] {}), eq(CONFIG_INFO_CHANGED_ROW_MAPPER))).thenThrow( - new EmptyResultDataAccessException(1)); - List allAggrGroup = externalConfigInfoAggrPersistService.findAllAggrGroup(); - assertNull(allAggrGroup); - - //mock Exception - when(jdbcTemplate.query(anyString(), eq(new Object[] {}), eq(CONFIG_INFO_CHANGED_ROW_MAPPER))).thenThrow( - new RuntimeException("789")); - try { - externalConfigInfoAggrPersistService.findAllAggrGroup(); - assertTrue(false); - } catch (Throwable throwable) { - assertEquals("789", throwable.getCause().getMessage()); - } - - } - - private ConfigInfoChanged create(String dataID, int i) { - ConfigInfoChanged hasDatum = new ConfigInfoChanged(); - hasDatum.setDataId(dataID + i); - hasDatum.setTenant("tenant1"); - hasDatum.setGroup("group1"); - return hasDatum; - } - -} diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoGrayPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoGrayPersistServiceImplTest.java new file mode 100644 index 00000000000..0e7d093b910 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoGrayPersistServiceImplTest.java @@ -0,0 +1,586 @@ +/* + * 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.config.server.service.repository.extrnal; + +import com.alibaba.nacos.common.utils.MD5Utils; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoGrayWrapper; +import com.alibaba.nacos.config.server.model.ConfigInfoStateWrapper; +import com.alibaba.nacos.config.server.model.ConfigOperateResult; +import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; +import com.alibaba.nacos.config.server.service.sql.ExternalStorageUtils; +import com.alibaba.nacos.config.server.utils.TestCaseUtils; +import com.alibaba.nacos.persistence.datasource.DataSourceService; +import com.alibaba.nacos.persistence.datasource.DynamicDataSource; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.persistence.repository.embedded.operate.DatabaseOperate; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.springframework.dao.EmptyResultDataAccessException; +import org.springframework.jdbc.CannotGetJdbcConnectionException; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.transaction.support.TransactionTemplate; + +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.List; + +import static com.alibaba.nacos.config.server.constant.Constants.ENCODE; +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER; +import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.when; + +@ExtendWith(SpringExtension.class) +public class ExternalConfigInfoGrayPersistServiceImplTest { + + private ExternalConfigInfoGrayPersistServiceImpl externalConfigInfoGrayPersistService; + + @Mock + private DataSourceService dataSourceService; + + @Mock + private JdbcTemplate jdbcTemplate; + + @Mock + private HistoryConfigInfoPersistService historyConfigInfoPersistService; + + @Mock + DatabaseOperate databaseOperate; + + private TransactionTemplate transactionTemplate = TestCaseUtils.createMockTransactionTemplate(); + + MockedStatic envUtilMockedStatic; + + MockedStatic externalStorageUtilsMockedStatic; + + MockedStatic dynamicDataSourceMockedStatic; + + @Mock + DynamicDataSource dynamicDataSource; + + /** + * before each tet case. + */ + @BeforeEach + public void before() { + dynamicDataSourceMockedStatic = Mockito.mockStatic(DynamicDataSource.class); + envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); + externalStorageUtilsMockedStatic = Mockito.mockStatic(ExternalStorageUtils.class); + when(DynamicDataSource.getInstance()).thenReturn(dynamicDataSource); + when(dynamicDataSource.getDataSource()).thenReturn(dataSourceService); + when(dataSourceService.getTransactionTemplate()).thenReturn(transactionTemplate); + when(dataSourceService.getJdbcTemplate()).thenReturn(jdbcTemplate); + when(dataSourceService.getDataSourceType()).thenReturn("mysql"); + envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))) + .thenReturn(false); + externalConfigInfoGrayPersistService = new ExternalConfigInfoGrayPersistServiceImpl( + historyConfigInfoPersistService); + } + + /** + * after each test case. + */ + @AfterEach + public void after() { + dynamicDataSourceMockedStatic.close(); + envUtilMockedStatic.close(); + externalStorageUtilsMockedStatic.close(); + } + + @Test + public void testInsertOrUpdateGrayOfUpdate() { + String dataId = "grayDataId113"; + String group = "group"; + String tenant = "tenant"; + + //mock exist gray + ConfigInfoStateWrapper mockedConfigInfoStateWrapper = new ConfigInfoStateWrapper(); + mockedConfigInfoStateWrapper.setDataId(dataId); + mockedConfigInfoStateWrapper.setGroup(group); + mockedConfigInfoStateWrapper.setTenant(tenant); + mockedConfigInfoStateWrapper.setId(123456L); + mockedConfigInfoStateWrapper.setLastModified(System.currentTimeMillis()); + + //mock exist config info + ConfigInfoGrayWrapper configAllInfo4Gray = new ConfigInfoGrayWrapper(); + configAllInfo4Gray.setDataId(dataId); + configAllInfo4Gray.setGroup(group); + configAllInfo4Gray.setTenant(tenant); + configAllInfo4Gray.setMd5("old_md5"); + String grayName = "grayName..."; + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfigInfoStateWrapper); + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(configAllInfo4Gray); + + String srcIp = "srcUp..."; + String srcUser = "srcUser..."; + String appName = "appName"; + String content = "content111"; + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key34567"); + String grayRule = "grayRule..."; + ConfigOperateResult configOperateResult = externalConfigInfoGrayPersistService.insertOrUpdateGray(configInfo, + grayName, grayRule, srcIp, srcUser); + //expect return obj + assertEquals(mockedConfigInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(mockedConfigInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + //verify update to be invoked + Mockito.verify(jdbcTemplate, times(1)) + .update(anyString(), eq(configInfo.getContent()), eq(configInfo.getEncryptedDataKey()), + eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(configInfo.getAppName()), eq(grayRule), + eq(dataId), eq(group), eq(tenant), eq(grayName)); + + } + + @Test + public void testInsertOrUpdateGrayOfAdd() { + String dataId = "betaDataId113"; + String group = "group113"; + String tenant = "tenant113"; + //mock exist beta + ConfigInfoStateWrapper mockedConfigInfoStateWrapper = new ConfigInfoStateWrapper(); + mockedConfigInfoStateWrapper.setDataId(dataId); + mockedConfigInfoStateWrapper.setGroup(group); + mockedConfigInfoStateWrapper.setTenant(tenant); + mockedConfigInfoStateWrapper.setId(123456L); + mockedConfigInfoStateWrapper.setLastModified(System.currentTimeMillis()); + String grayName = "grayName..."; + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1)) + .thenReturn(mockedConfigInfoStateWrapper); + + String srcIp = "srcUp..."; + String srcUser = "srcUser..."; + String appName = "appname"; + String content = "content111"; + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key34567"); + String grayRule = "grayRule..."; + + //execute + ConfigOperateResult configOperateResult = externalConfigInfoGrayPersistService.insertOrUpdateGray(configInfo, + grayName, grayRule, srcIp, srcUser); + //expect return obj + assertEquals(mockedConfigInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(mockedConfigInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + //verify add to be invoked + Mockito.verify(jdbcTemplate, times(1)) + .update(anyString(), eq(dataId), eq(group), eq(tenant), eq(grayName), eq(grayRule), + eq(configInfo.getAppName()), eq(configInfo.getContent()), eq(configInfo.getEncryptedDataKey()), + eq(configInfo.getMd5()), eq(srcIp), eq(srcUser)); + } + + @Test + public void testInsertOrUpdateGrayOfException() { + String dataId = "grapDataId113"; + String group = "group113"; + String tenant = "tenant113"; + + //mock exist gray + ConfigInfoStateWrapper mockedConfigInfoStateWrapper = new ConfigInfoStateWrapper(); + mockedConfigInfoStateWrapper.setDataId(dataId); + mockedConfigInfoStateWrapper.setGroup(group); + mockedConfigInfoStateWrapper.setTenant(tenant); + mockedConfigInfoStateWrapper.setId(123456L); + mockedConfigInfoStateWrapper.setLastModified(System.currentTimeMillis()); + String grayName = "grayName..."; + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfigInfoStateWrapper); + + String srcIp = "srcUp..."; + String srcUser = "srcUser..."; + String appName = "appname"; + String content = "content111"; + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key34567"); + configInfo.setMd5("casMd5"); + String grayRule = "grayRule..."; + // mock update throw CannotGetJdbcConnectionException + when(jdbcTemplate.update(anyString(), eq(configInfo.getContent()), eq(configInfo.getEncryptedDataKey()), + eq(MD5Utils.md5Hex(content, ENCODE)), eq(srcIp), eq(srcUser), eq(configInfo.getAppName()), eq(grayRule), + eq(dataId), eq(group), eq(tenant), eq(grayName))).thenThrow( + new CannotGetJdbcConnectionException("mock fail")); + //execute of update& expect. + try { + externalConfigInfoGrayPersistService.insertOrUpdateGray(configInfo, grayName, grayRule, srcIp, srcUser); + assertTrue(false); + } catch (Exception exception) { + assertEquals("mock fail", exception.getMessage()); + } + + //mock query return null + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + //mock add throw CannotGetJdbcConnectionException + when(jdbcTemplate.update(anyString(), eq(dataId), eq(group), eq(tenant), eq(grayName), eq(grayRule), + eq(configInfo.getAppName()), eq(configInfo.getContent()), eq(configInfo.getEncryptedDataKey()), + eq(MD5Utils.md5Hex(content, ENCODE)), eq(srcIp), eq(srcUser))).thenThrow( + new CannotGetJdbcConnectionException("mock fail add")); + + //execute of add& expect. + try { + externalConfigInfoGrayPersistService.insertOrUpdateGray(configInfo, grayName, grayRule, srcIp, srcUser); + assertTrue(false); + } catch (Exception exception) { + assertEquals("mock fail add", exception.getMessage()); + } + + //mock query throw CannotGetJdbcConnectionException + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow( + new CannotGetJdbcConnectionException("get c fail")); + //execute of add& expect. + try { + externalConfigInfoGrayPersistService.insertOrUpdateGray(configInfo, grayName, grayRule, srcIp, srcUser); + assertTrue(false); + } catch (Exception exception) { + assertEquals("get c fail", exception.getMessage()); + } + + } + + @Test + public void testInsertOrUpdateGrayCasOfUpdate() { + String dataId = "grayDataId113"; + String group = "group"; + String tenant = "tenant"; + + //mock exist gray + ConfigInfoStateWrapper mockedConfigInfoStateWrapper = new ConfigInfoStateWrapper(); + mockedConfigInfoStateWrapper.setDataId(dataId); + mockedConfigInfoStateWrapper.setGroup(group); + mockedConfigInfoStateWrapper.setTenant(tenant); + mockedConfigInfoStateWrapper.setId(123456L); + mockedConfigInfoStateWrapper.setLastModified(System.currentTimeMillis()); + String grayName = "grayName..."; + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfigInfoStateWrapper, + mockedConfigInfoStateWrapper); + + //execute + String srcIp = "srcUp..."; + String srcUser = "srcUser..."; + String appName = "appname"; + String content = "content111"; + + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key34567"); + configInfo.setMd5("casMd5"); + String grayRule = "grayRule..."; + //mock cas update + when(jdbcTemplate.update(anyString(), eq(configInfo.getContent()), eq(MD5Utils.md5Hex(content, ENCODE)), + eq(srcIp), eq(srcUser), eq(configInfo.getAppName()), eq(grayRule), eq(dataId), eq(group), eq(tenant), + eq(grayName), eq(configInfo.getMd5()))).thenReturn(1); + + //mock exist config info + ConfigInfoGrayWrapper configAllInfo4Gray = new ConfigInfoGrayWrapper(); + configAllInfo4Gray.setDataId(dataId); + configAllInfo4Gray.setGroup(group); + configAllInfo4Gray.setTenant(tenant); + configAllInfo4Gray.setMd5("old_md5"); + String grayName1 = "grayName1..."; + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(configAllInfo4Gray); + + ConfigOperateResult configOperateResult = externalConfigInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, + grayName, grayRule, srcIp, srcUser); + //expect return obj + assertEquals(mockedConfigInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(mockedConfigInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + //verify cas update to be invoked + Mockito.verify(jdbcTemplate, times(1)) + .update(anyString(), eq(configInfo.getContent()), eq(MD5Utils.md5Hex(content, ENCODE)), eq(srcIp), + eq(srcUser), eq(configInfo.getAppName()), eq(grayRule), eq(dataId), eq(group), eq(tenant), + eq(grayName), eq(configInfo.getMd5())); + + } + + @Test + public void testInsertOrUpdateGrayCasOfAdd() { + String dataId = "betaDataId113"; + String group = "group113"; + String tenant = "tenant113"; + + //mock exist beta + ConfigInfoStateWrapper mockedConfigInfoStateWrapper = new ConfigInfoStateWrapper(); + mockedConfigInfoStateWrapper.setDataId(dataId); + mockedConfigInfoStateWrapper.setGroup(group); + mockedConfigInfoStateWrapper.setTenant(tenant); + mockedConfigInfoStateWrapper.setId(123456L); + mockedConfigInfoStateWrapper.setLastModified(System.currentTimeMillis()); + String grayName = "grayName..."; + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1)) + .thenReturn(mockedConfigInfoStateWrapper); + + String srcIp = "srcUp..."; + String srcUser = "srcUser..."; + String appName = "appname"; + String content = "content111"; + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key34567"); + configInfo.setMd5("csMd5"); + String grayRule = "grayRule..."; + //execute + ConfigOperateResult configOperateResult = externalConfigInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, + grayName, grayRule, srcIp, srcUser); + //expect return obj + assertEquals(mockedConfigInfoStateWrapper.getId(), configOperateResult.getId()); + assertEquals(mockedConfigInfoStateWrapper.getLastModified(), configOperateResult.getLastModified()); + //verify add to be invoked + Mockito.verify(jdbcTemplate, times(1)) + .update(anyString(), eq(dataId), eq(group), eq(tenant), eq(grayName), eq(grayRule), + eq(configInfo.getAppName()), eq(configInfo.getContent()), eq(configInfo.getEncryptedDataKey()), + eq(MD5Utils.md5Hex(content, ENCODE)), eq(srcIp), eq(srcUser)); + + } + + @Test + public void testInsertOrUpdateGrayCasOfException() { + String dataId = "betaDataId113"; + String group = "group113"; + String tenant = "tenant113"; + + //mock exist beta + ConfigInfoStateWrapper mockedConfigInfoStateWrapper = new ConfigInfoStateWrapper(); + mockedConfigInfoStateWrapper.setDataId(dataId); + mockedConfigInfoStateWrapper.setGroup(group); + mockedConfigInfoStateWrapper.setTenant(tenant); + mockedConfigInfoStateWrapper.setId(123456L); + mockedConfigInfoStateWrapper.setLastModified(System.currentTimeMillis()); + String grayName = "grayName..."; + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfigInfoStateWrapper); + + String srcIp = "srcUp..."; + String srcUser = "srcUser..."; + String appName = "appname"; + String content = "content111"; + ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); + configInfo.setEncryptedDataKey("key34567"); + configInfo.setMd5("casMd5"); + String grayRule = "grayRule..."; + // mock update throw CannotGetJdbcConnectionException + when(jdbcTemplate.update(anyString(), eq(configInfo.getContent()), eq(MD5Utils.md5Hex(content, ENCODE)), + eq(srcIp), eq(srcUser), eq(configInfo.getAppName()), eq(grayRule), eq(dataId), eq(group), eq(tenant), + eq(grayName), eq(configInfo.getMd5()))).thenThrow( + new CannotGetJdbcConnectionException("updat mock fail")); + + //execute of update& expect. + try { + externalConfigInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, grayName, grayRule, srcIp, srcUser); + assertTrue(false); + } catch (Exception exception) { + assertEquals("updat mock fail", exception.getMessage()); + } + + //mock query return null + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null); + //mock add throw CannotGetJdbcConnectionException + when(jdbcTemplate.update(anyString(), eq(dataId), eq(group), eq(tenant), eq(grayName), eq(grayRule), + eq(configInfo.getAppName()), eq(configInfo.getContent()), eq(configInfo.getEncryptedDataKey()), + eq(MD5Utils.md5Hex(content, ENCODE)), eq(srcIp), eq(srcUser))).thenThrow( + new CannotGetJdbcConnectionException("mock fail add")); + + //execute of add& expect. + try { + externalConfigInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, grayName, grayRule, srcIp, srcUser); + assertTrue(false); + } catch (Exception exception) { + assertEquals("mock fail add", exception.getMessage()); + } + + //mock query throw CannotGetJdbcConnectionException + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow( + new CannotGetJdbcConnectionException("get c fail")); + //execute of add& expect. + try { + externalConfigInfoGrayPersistService.insertOrUpdateGrayCas(configInfo, grayName, grayRule, srcIp, srcUser); + assertTrue(false); + } catch (Exception exception) { + assertEquals("get c fail", exception.getMessage()); + } + + } + + @Test + void testRemoveConfigInfo() { + String dataId = "dataId4567"; + String group = "group3456789"; + String tenant = "tenant4567890"; + final String grayName = "grayName1"; + + //mock exist config info + ConfigInfoGrayWrapper configAllInfo4Gray = new ConfigInfoGrayWrapper(); + configAllInfo4Gray.setDataId(dataId); + configAllInfo4Gray.setGroup(group); + configAllInfo4Gray.setTenant(tenant); + configAllInfo4Gray.setMd5("old_md5"); + + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(configAllInfo4Gray); + Mockito.when(databaseOperate.update(any())).thenReturn(true); + + String srcIp = "srcIp1234"; + String srcUser = "srcUser"; + externalConfigInfoGrayPersistService.removeConfigInfoGray(dataId, group, tenant, grayName, srcIp, srcUser); + + Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(dataId), eq(group), eq(tenant), eq(grayName)); + Mockito.verify(historyConfigInfoPersistService, times(1)) + .insertConfigHistoryAtomic(eq(configAllInfo4Gray.getId()), eq(configAllInfo4Gray), eq(srcIp), + eq(srcUser), any(Timestamp.class), eq("D"), eq("gray"), eq(grayName), anyString()); + + // Test the exception handling for CannotGetJdbcConnectionException + when(jdbcTemplate.update(anyString(), eq(dataId), eq(group), eq(tenant), eq(grayName))).thenThrow( + new CannotGetJdbcConnectionException("mock fail11111")); + + } + + @Test + public void testFindChangeConfigInfo4Gray() { + List mockList = new ArrayList<>(); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.get(0).setLastModified(System.currentTimeMillis()); + mockList.get(1).setLastModified(System.currentTimeMillis()); + mockList.get(2).setLastModified(System.currentTimeMillis()); + + Timestamp timestamp = new Timestamp(System.currentTimeMillis()); + + long lastMaxId = 123; + when(jdbcTemplate.query(anyString(), eq(new Object[] {timestamp, lastMaxId, 100}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(mockList) + .thenThrow(new CannotGetJdbcConnectionException("mock exception22")); + + List changeConfig = externalConfigInfoGrayPersistService.findChangeConfig(timestamp, + lastMaxId, 100); + assertTrue(changeConfig.get(0).getLastModified() == mockList.get(0).getLastModified()); + assertTrue(changeConfig.get(1).getLastModified() == mockList.get(1).getLastModified()); + assertTrue(changeConfig.get(2).getLastModified() == mockList.get(2).getLastModified()); + try { + externalConfigInfoGrayPersistService.findChangeConfig(timestamp, lastMaxId, 100); + assertTrue(false); + } catch (CannotGetJdbcConnectionException exception) { + assertEquals("mock exception22", exception.getMessage()); + } + + } + + @Test + public void testFindConfigInfo4Gray() { + String dataId = "dataId456789"; + String group = "group4567"; + String tenant = "tenant56789o0"; + String grayName = "gray12"; + //mock exist gray + ConfigInfoGrayWrapper mockedConfigInfoStateWrapper = new ConfigInfoGrayWrapper(); + mockedConfigInfoStateWrapper.setDataId(dataId); + mockedConfigInfoStateWrapper.setGroup(group); + mockedConfigInfoStateWrapper.setGrayName(grayName); + mockedConfigInfoStateWrapper.setTenant(tenant); + mockedConfigInfoStateWrapper.setId(123456L); + mockedConfigInfoStateWrapper.setLastModified(System.currentTimeMillis()); + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfigInfoStateWrapper); + ConfigInfoGrayWrapper configInfo4GrayReturn = externalConfigInfoGrayPersistService.findConfigInfo4Gray(dataId, + group, tenant, grayName); + assertEquals(mockedConfigInfoStateWrapper, configInfo4GrayReturn); + + //mock query throw CannotGetJdbcConnectionException + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenThrow( + new CannotGetJdbcConnectionException("mock fail11111")); + try { + externalConfigInfoGrayPersistService.findConfigInfo4Gray(dataId, group, tenant, grayName); + assertTrue(false); + } catch (Exception exception) { + assertEquals("mock fail11111", exception.getMessage()); + } + + //mock query throw EmptyResultDataAccessException + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant, grayName}), + eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1)); + ConfigInfoGrayWrapper configInfo4GrayNull = externalConfigInfoGrayPersistService.findConfigInfo4Gray(dataId, + group, tenant, grayName); + assertNull(configInfo4GrayNull); + } + + @Test + public void testConfigInfoGrayCount() { + when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(101); + int returnCount = externalConfigInfoGrayPersistService.configInfoGrayCount(); + assertEquals(101, returnCount); + } + + @Test + public void testFindAllConfigInfoGrayForDumpAll() { + //mock count + when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(12345); + + //mock page list + List mockList = new ArrayList<>(); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.add(new ConfigInfoGrayWrapper()); + mockList.get(0).setLastModified(System.currentTimeMillis()); + mockList.get(1).setLastModified(System.currentTimeMillis()); + mockList.get(2).setLastModified(System.currentTimeMillis()); + + when(jdbcTemplate.query(anyString(), eq(new Object[] {}), eq(CONFIG_INFO_GRAY_WRAPPER_ROW_MAPPER))).thenReturn( + mockList); + + int pageNo = 1; + int pageSize = 101; + when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenReturn(101); + //execute & expect + Page pageReturn = externalConfigInfoGrayPersistService.findAllConfigInfoGrayForDumpAll( + pageNo, pageSize); + assertEquals(mockList, pageReturn.getPageItems()); + assertEquals(101, pageReturn.getTotalCount()); + + //mock count throw CannotGetJdbcConnectionException + when(jdbcTemplate.queryForObject(anyString(), eq(Integer.class))).thenThrow( + new CannotGetJdbcConnectionException("345678909fail")); + //execute &expect + try { + externalConfigInfoGrayPersistService.findAllConfigInfoGrayForDumpAll(pageNo, pageSize); + assertTrue(false); + } catch (Exception exception) { + assertEquals("345678909fail", exception.getMessage()); + } + } + +} + diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImplTest.java index bab2bc8cf48..42a9d1e8ff8 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImplTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalConfigInfoPersistServiceImplTest.java @@ -29,10 +29,12 @@ import com.alibaba.nacos.config.server.model.SameConfigPolicy; import com.alibaba.nacos.config.server.service.repository.HistoryConfigInfoPersistService; import com.alibaba.nacos.config.server.service.sql.ExternalStorageUtils; +import com.alibaba.nacos.config.server.utils.ConfigExtInfoUtil; import com.alibaba.nacos.config.server.utils.TestCaseUtils; import com.alibaba.nacos.persistence.datasource.DataSourceService; import com.alibaba.nacos.persistence.datasource.DynamicDataSource; import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.datasource.MapperManager; import com.alibaba.nacos.plugin.datasource.constants.TableConstant; import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoMapper; import com.alibaba.nacos.sys.env.EnvUtil; @@ -119,7 +121,8 @@ void before() { when(dataSourceService.getDataSourceType()).thenReturn("mysql"); /*when(EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))).thenReturn(false);*/ - envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))).thenReturn(false); + envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))) + .thenReturn(false); externalConfigInfoPersistService = new ExternalConfigInfoPersistServiceImpl(historyConfigInfoPersistService); } @@ -149,19 +152,25 @@ void testInsertOrUpdateOfInsertConfigSuccess() { eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null, new ConfigInfoStateWrapper()); //mock insert config info Mockito.when(jdbcTemplate.update(any(PreparedStatementCreator.class), eq(generatedKeyHolder))).thenReturn(1); - Mockito.when(jdbcTemplate.update(eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1); - Mockito.when(jdbcTemplate.update(eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1); + Mockito.when(jdbcTemplate.update( + eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_TAGS_RELATION) + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))) + .thenReturn(1); + Mockito.when(jdbcTemplate.update( + eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_TAGS_RELATION) + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))) + .thenReturn(1); String srcIp = "srcIp"; String srcUser = "srcUser"; //mock insert config info Mockito.doNothing().when(historyConfigInfoPersistService) - .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I")); + .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I"), + eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser))); externalConfigInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, configAdvanceInfo); //expect insert config info @@ -170,17 +179,19 @@ void testInsertOrUpdateOfInsertConfigSuccess() { Mockito.verify(jdbcTemplate, times(1)).update(eq( externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); Mockito.verify(jdbcTemplate, times(1)).update(eq( externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); //expect insert history info Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I")); + .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), + eq("I"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser))); } @@ -203,19 +214,25 @@ void testInsertOrUpdateCasOfInsertConfigSuccess() { eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(null, new ConfigInfoStateWrapper()); //mock insert config info Mockito.when(jdbcTemplate.update(any(PreparedStatementCreator.class), eq(generatedKeyHolder))).thenReturn(1); - Mockito.when(jdbcTemplate.update(eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1); - Mockito.when(jdbcTemplate.update(eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1); + Mockito.when(jdbcTemplate.update( + eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_TAGS_RELATION) + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))) + .thenReturn(1); + Mockito.when(jdbcTemplate.update( + eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_TAGS_RELATION) + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))) + .thenReturn(1); String srcIp = "srcIp"; String srcUser = "srcUser"; //mock insert config info Mockito.doNothing().when(historyConfigInfoPersistService) - .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I")); + .insertConfigHistoryAtomic(eq(0), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I"), + eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser))); externalConfigInfoPersistService.insertOrUpdateCas(srcIp, srcUser, configInfo, configAdvanceInfo); //expect insert config info @@ -224,17 +241,19 @@ void testInsertOrUpdateCasOfInsertConfigSuccess() { Mockito.verify(jdbcTemplate, times(1)).update(eq( externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); Mockito.verify(jdbcTemplate, times(1)).update(eq( externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(insertConfigIndoId), - eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(insertConfigIndoId), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); //expect insert history info Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), eq("I")); + .insertConfigHistoryAtomic(eq(0L), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), + eq("I"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtraInfoFromAdvanceInfoMap(configAdvanceInfo, srcUser))); } @@ -285,40 +304,47 @@ void testInsertOrUpdateOfUpdateConfigSuccess() { configInfo.setEncryptedDataKey(encryptedDataKey); //mock get config state,first and second is not null Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper()); - - //mock select config info before update - ConfigInfoWrapper configInfoWrapperOld = new ConfigInfoWrapper(); - configInfoWrapperOld.setDataId(dataId); - configInfoWrapperOld.setGroup(group); - configInfoWrapperOld.setTenant(tenant); - configInfoWrapperOld.setAppName("old_app"); - configInfoWrapperOld.setMd5("old_md5"); - configInfoWrapperOld.setId(12345678765L); - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapperOld); + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper()); + + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName("old_app"); + configAllInfo.setMd5("old_md5"); + configAllInfo.setId(12345678765L); + //mock get all config info + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo); + String srcIp = "srcIp"; String srcUser = "srcUser"; //mock update config info - Mockito.when(jdbcTemplate.update(eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), + Mockito.when(jdbcTemplate.update( + eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO) - .update(Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified@NOW()", "app_name", "c_desc", "c_use", - "effect", "type", "c_schema", "encrypted_data_key"), Arrays.asList("data_id", "group_id", "tenant_id"))), - eq(configInfo.getContent()), eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(configInfoWrapperOld.getAppName()), - eq(configAdvanceInfo.get("desc")), eq(configAdvanceInfo.get("use")), eq(configAdvanceInfo.get("effect")), - eq(configAdvanceInfo.get("type")), eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey), eq(configInfo.getDataId()), + .update(Arrays.asList("content", "md5", "src_ip", "src_user", "gmt_modified@NOW()", "app_name", + "c_desc", "c_use", "effect", "type", "c_schema", "encrypted_data_key"), + Arrays.asList("data_id", "group_id", "tenant_id"))), eq(configInfo.getContent()), + eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(configAllInfo.getAppName()), + eq(configAdvanceInfo.get("desc")), eq(configAdvanceInfo.get("use")), + eq(configAdvanceInfo.get("effect")), eq(configAdvanceInfo.get("type")), + eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey), eq(configInfo.getDataId()), eq(configInfo.getGroup()), eq(tenant))).thenReturn(1); //mock insert config tags. - Mockito.when(jdbcTemplate.update(eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), + Mockito.when(jdbcTemplate.update( + eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(12345678765L), anyString(), - eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1); + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(12345678765L), anyString(), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1); //mock insert his config info Mockito.doNothing().when(historyConfigInfoPersistService) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), - eq("I")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), eq(configInfo), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("I"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); externalConfigInfoPersistService.insertOrUpdate(srcIp, srcUser, configInfo, configAdvanceInfo); @@ -327,17 +353,18 @@ void testInsertOrUpdateOfUpdateConfigSuccess() { externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), - eq(configInfoWrapperOld.getId()), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + eq(configAllInfo.getId()), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); Mockito.verify(jdbcTemplate, times(1)).update(eq( externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), - eq(configInfoWrapperOld.getId()), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + eq(configAllInfo.getId()), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); //expect insert history info Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), - any(Timestamp.class), eq("U")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("U"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); } @@ -362,61 +389,70 @@ void testInsertOrUpdateCasOfUpdateConfigSuccess() { //mock get config state,first and second is not null Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), - eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper()); - - //mock select config info before update - ConfigInfoWrapper configInfoWrapperOld = new ConfigInfoWrapper(); - configInfoWrapperOld.setDataId(dataId); - configInfoWrapperOld.setGroup(group); - configInfoWrapperOld.setTenant(tenant); - configInfoWrapperOld.setAppName("old_app11"); - configInfoWrapperOld.setMd5("old_md5"); - configInfoWrapperOld.setId(123456799L); - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[]{dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapperOld); + eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) + .thenReturn(new ConfigInfoStateWrapper(), new ConfigInfoStateWrapper()); + + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName("old_app"); + configAllInfo.setMd5("old_md5"); + configAllInfo.setId(12345678765L); + //mock get all config info + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo); + String srcIp = "srcIp"; String srcUser = "srcUser"; //mock update config info cas - Mockito.when(jdbcTemplate.update(anyString(), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), - eq(srcIp), eq(srcUser), eq(configInfoWrapperOld.getAppName()), eq(configAdvanceInfo.get("desc")), - eq(configAdvanceInfo.get("use")), eq(configAdvanceInfo.get("effect")), eq(configAdvanceInfo.get("type")), - eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey), eq(dataId), eq(group), eq(tenant), eq(casMd5))).thenReturn(1); - + Mockito.when( + jdbcTemplate.update(anyString(), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), + eq(srcIp), eq(srcUser), eq(configAllInfo.getAppName()), eq(configAdvanceInfo.get("desc")), + eq(configAdvanceInfo.get("use")), eq(configAdvanceInfo.get("effect")), + eq(configAdvanceInfo.get("type")), eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey), + eq(dataId), eq(group), eq(tenant), eq(casMd5))).thenReturn(1); + //mock insert config tags. - Mockito.when(jdbcTemplate.update(eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_TAGS_RELATION) - .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), eq(configInfoWrapperOld.getId()), - anyString(), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))).thenReturn(1); - + Mockito.when(jdbcTemplate.update( + eq(externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_TAGS_RELATION) + .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), + eq(configAllInfo.getId()), anyString(), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant))) + .thenReturn(1); + //mock insert his config info Mockito.doNothing().when(historyConfigInfoPersistService) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), eq(configInfo), eq(srcIp), eq(srcUser), any(Timestamp.class), - eq("I")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), eq(configInfo), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("I"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); externalConfigInfoPersistService.insertOrUpdateCas(srcIp, srcUser, configInfo, configAdvanceInfo); //expect update config cas Mockito.verify(jdbcTemplate, times(1)) .update(anyString(), eq(content), eq(MD5Utils.md5Hex(content, Constants.PERSIST_ENCODE)), eq(srcIp), - eq(srcUser), eq(configInfoWrapperOld.getAppName()), eq(configAdvanceInfo.get("desc")), - eq(configAdvanceInfo.get("use")), eq(configAdvanceInfo.get("effect")), eq(configAdvanceInfo.get("type")), - eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey), eq(dataId), eq(group), eq(tenant), eq(casMd5)); + eq(srcUser), eq(configAllInfo.getAppName()), eq(configAdvanceInfo.get("desc")), + eq(configAdvanceInfo.get("use")), eq(configAdvanceInfo.get("effect")), + eq(configAdvanceInfo.get("type")), eq(configAdvanceInfo.get("schema")), eq(encryptedDataKey), + eq(dataId), eq(group), eq(tenant), eq(casMd5)); //expect update config tags Mockito.verify(jdbcTemplate, times(1)).update(eq( externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), - eq(configInfoWrapperOld.getId()), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + eq(configAllInfo.getId()), eq("tag1"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); Mockito.verify(jdbcTemplate, times(1)).update(eq( externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), TableConstant.CONFIG_TAGS_RELATION) .insert(Arrays.asList("id", "tag_name", "tag_type", "data_id", "group_id", "tenant_id"))), - eq(configInfoWrapperOld.getId()), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); + eq(configAllInfo.getId()), eq("tag2"), eq(StringUtils.EMPTY), eq(dataId), eq(group), eq(tenant)); //expect insert history info Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), - any(Timestamp.class), eq("U")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), any(ConfigInfo.class), eq(srcIp), eq(srcUser), + any(Timestamp.class), eq("U"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); } @@ -438,14 +474,14 @@ void testCreatePsForInsertConfigInfo() throws SQLException { Connection mockConnection = Mockito.mock(Connection.class); PreparedStatement preparedStatement = Mockito.mock(PreparedStatement.class); - ConfigInfoMapper configInfoMapper = externalConfigInfoPersistService.mapperManager.findMapper(dataSourceService.getDataSourceType(), - TableConstant.CONFIG_INFO); + ConfigInfoMapper configInfoMapper = externalConfigInfoPersistService.mapperManager.findMapper( + dataSourceService.getDataSourceType(), TableConstant.CONFIG_INFO); Mockito.when(mockConnection.prepareStatement(anyString(), any(String[].class))).thenReturn(preparedStatement); String srcIp = "srcIp"; String srcUser = "srcUser"; - externalConfigInfoPersistService.createPsForInsertConfigInfo(srcIp, srcUser, configInfo, configAdvanceInfo, mockConnection, - configInfoMapper); + externalConfigInfoPersistService.createPsForInsertConfigInfo(srcIp, srcUser, configInfo, configAdvanceInfo, + mockConnection, configInfoMapper); Mockito.verify(preparedStatement, times(14)).setString(anyInt(), anyString()); } @@ -455,18 +491,18 @@ void testRemoveConfigInfo() { String group = "group3456789"; String tenant = "tenant4567890"; - //mock exist config info - ConfigInfoWrapper configInfoWrapperOld = new ConfigInfoWrapper(); - configInfoWrapperOld.setDataId(dataId); - configInfoWrapperOld.setGroup(group); - configInfoWrapperOld.setTenant(tenant); - configInfoWrapperOld.setAppName("old_app"); - configInfoWrapperOld.setContent("old content"); - configInfoWrapperOld.setMd5("old_md5"); - configInfoWrapperOld.setId(12345678765L); - configInfoWrapperOld.setEncryptedDataKey("key3456"); - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapperOld); + //mock exist all config info + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName("old_app"); + configAllInfo.setMd5("old_md5"); + configAllInfo.setId(12345678765L); + + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(configAllInfo); + String srcIp = "srcIp1234"; String srcUser = "srcUser"; externalConfigInfoPersistService.removeConfigInfo(dataId, group, tenant, srcIp, srcUser); @@ -474,11 +510,11 @@ void testRemoveConfigInfo() { //expect delete to be invoked Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(dataId), eq(group), eq(tenant)); //expect delete tags to be invoked - Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(configInfoWrapperOld.getId())); + Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(configAllInfo.getId())); //expect insert delete history Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfoWrapperOld.getId()), eq(configInfoWrapperOld), eq(srcIp), eq(srcUser), any(), - eq("D")); + .insertConfigHistoryAtomic(eq(configAllInfo.getId()), eq(configAllInfo), eq(srcIp), eq(srcUser), any(), + eq("D"), eq("formal"), eq(null), eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo))); } @@ -486,13 +522,24 @@ void testRemoveConfigInfo() { void testRemoveConfigInfoByIds() { //mock exist config info - List configInfos = new ArrayList<>(); - configInfos.add(new ConfigInfo("data1", "group", "tenant", "app", "content")); - configInfos.add(new ConfigInfo("data2", "grou2", "tenan2", "app2", "content2")); + final List configAllInfos = new ArrayList<>(); + final ConfigAllInfo configAllInfo1 = new ConfigAllInfo(); + final ConfigAllInfo configAllInfo2 = new ConfigAllInfo(); + configAllInfo1.setDataId("dataId1"); + configAllInfo1.setGroup("group1"); + configAllInfo1.setTenant("tenant1"); + configAllInfo1.setAppName("app1"); + configAllInfo2.setDataId("dataId2"); + configAllInfo2.setGroup("group2"); + configAllInfo2.setTenant("tenant2"); + configAllInfo2.setAppName("app2"); + configAllInfos.add(configAllInfo1); + configAllInfos.add(configAllInfo2); List deleteIds = Arrays.asList(12344L, 3456789L); - configInfos.get(0).setId(12344L); - configInfos.get(1).setId(3456789L); - Mockito.when(jdbcTemplate.query(anyString(), eq(deleteIds.toArray()), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(configInfos); + configAllInfos.get(0).setId(12344L); + configAllInfos.get(1).setId(3456789L); + Mockito.when(jdbcTemplate.query(anyString(), eq(deleteIds.toArray()), eq(CONFIG_ALL_INFO_ROW_MAPPER))) + .thenReturn(configAllInfos); String srcIp = "srcIp1234"; String srcUser = "srcUser"; externalConfigInfoPersistService.removeConfigInfoByIds(deleteIds, srcIp, srcUser); @@ -504,9 +551,13 @@ void testRemoveConfigInfoByIds() { Mockito.verify(jdbcTemplate, times(1)).update(anyString(), eq(deleteIds.get(1))); //expect insert delete history Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfos.get(0).getId()), eq(configInfos.get(0)), eq(srcIp), eq(srcUser), any(), eq("D")); + .insertConfigHistoryAtomic(eq(configAllInfos.get(0).getId()), eq(configAllInfos.get(0)), eq(srcIp), + eq(srcUser), any(), eq("D"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfos.get(0)))); Mockito.verify(historyConfigInfoPersistService, times(1)) - .insertConfigHistoryAtomic(eq(configInfos.get(1).getId()), eq(configInfos.get(1)), eq(srcIp), eq(srcUser), any(), eq("D")); + .insertConfigHistoryAtomic(eq(configAllInfos.get(1).getId()), eq(configAllInfos.get(1)), eq(srcIp), + eq(srcUser), any(), eq("D"), eq("formal"), eq(null), + eq(ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfos.get(0)))); } @@ -527,11 +578,11 @@ void testBatchInsertOrUpdateOverwrite() throws NacosException { ReflectionTestUtils.setField(externalConfigInfoPersistService, "tjt", transactionTemplateCurrent); //mock add config 1 success,config 2 fail and update success,config 3 success Mockito.when(transactionTemplateCurrent.execute(any())) - .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false), new ConfigOperateResult(true), - new ConfigOperateResult(true)); + .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false), + new ConfigOperateResult(true), new ConfigOperateResult(true)); - Map stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList, srcUser, srcIp, - configAdvanceInfo, SameConfigPolicy.OVERWRITE); + Map stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList, + srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.OVERWRITE); assertEquals(3, stringObjectMap.get("succCount")); assertEquals(0, stringObjectMap.get("skipCount")); } @@ -553,13 +604,15 @@ void testBatchInsertOrUpdateSkip() throws NacosException { ReflectionTestUtils.setField(externalConfigInfoPersistService, "tjt", transactionTemplateCurrent); //mock add config 1 success,config 2 fail and skip,config 3 success Mockito.when(transactionTemplateCurrent.execute(any())) - .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false), new ConfigOperateResult(true)); + .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false), + new ConfigOperateResult(true)); - Map stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList, srcUser, srcIp, - configAdvanceInfo, SameConfigPolicy.SKIP); + Map stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList, + srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.SKIP); assertEquals(2, stringObjectMap.get("succCount")); assertEquals(1, stringObjectMap.get("skipCount")); - assertEquals(configInfoList.get(1).getDataId(), ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); + assertEquals(configInfoList.get(1).getDataId(), + ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); } @Test @@ -578,16 +631,19 @@ void testBatchInsertOrUpdateAbort() throws NacosException { TransactionTemplate transactionTemplateCurrent = Mockito.mock(TransactionTemplate.class); ReflectionTestUtils.setField(externalConfigInfoPersistService, "tjt", transactionTemplateCurrent); //mock add config 1 success,config 2 fail and abort,config 3 not operated - Mockito.when(transactionTemplateCurrent.execute(any())).thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false)); + Mockito.when(transactionTemplateCurrent.execute(any())) + .thenReturn(new ConfigOperateResult(true), new ConfigOperateResult(false)); - Map stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList, srcUser, srcIp, - configAdvanceInfo, SameConfigPolicy.ABORT); + Map stringObjectMap = externalConfigInfoPersistService.batchInsertOrUpdate(configInfoList, + srcUser, srcIp, configAdvanceInfo, SameConfigPolicy.ABORT); assertEquals(1, stringObjectMap.get("succCount")); assertEquals(1, stringObjectMap.get("skipCount")); // config 2 failed - assertEquals(configInfoList.get(1).getDataId(), ((List>) stringObjectMap.get("failData")).get(0).get("dataId")); + assertEquals(configInfoList.get(1).getDataId(), + ((List>) stringObjectMap.get("failData")).get(0).get("dataId")); //skip config 3 - assertEquals(configInfoList.get(2).getDataId(), ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); + assertEquals(configInfoList.get(2).getDataId(), + ((List>) stringObjectMap.get("skipData")).get(0).get("dataId")); } private ConfigAllInfo createMockConfigAllInfo(long mockId) { @@ -646,7 +702,8 @@ void testFindConfigInfoById() { long id = 1234567890876L; ConfigInfo configInfo = new ConfigInfo(); configInfo.setId(id); - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {id}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(configInfo); + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {id}), eq(CONFIG_INFO_ROW_MAPPER))) + .thenReturn(configInfo); ConfigInfo configReturn = externalConfigInfoPersistService.findConfigInfo(id); assertEquals(id, configReturn.getId()); } @@ -687,8 +744,8 @@ void testFindConfigInfoByDataId() { configInfoWrapper.setGroup(group); configInfoWrapper.setTenant(tenant); - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenReturn(configInfoWrapper); + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(configInfoWrapper); ConfigInfo configReturn = externalConfigInfoPersistService.findConfigInfo(dataId, group, tenant); assertEquals(dataId, configReturn.getDataId()); } @@ -698,8 +755,8 @@ void testFindConfigInfoByDataIdNull() { String dataId = "dataId4567"; String group = "group3456789"; String tenant = "tenant4567890"; - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenThrow(new EmptyResultDataAccessException(1)); + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1)); ConfigInfoWrapper configReturn = externalConfigInfoPersistService.findConfigInfo(dataId, group, tenant); assertNull(configReturn); } @@ -710,8 +767,8 @@ void testFindConfigInfoByDataIdGetConFail() { String group = "group3456789"; String tenant = "tenant4567890"; - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))) - .thenThrow(new CannotGetJdbcConnectionException("mocked exp")); + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mocked exp")); try { externalConfigInfoPersistService.findConfigInfo(dataId, group, tenant); assertTrue(false); @@ -727,17 +784,18 @@ void testFindConfigInfo4Page() { String tenant = "tenant4567890"; //mock total count - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant, dataId, group}), eq(Integer.class))).thenReturn( - new Integer(9)); + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant, dataId, group}), + eq(Integer.class))).thenReturn(new Integer(9)); //mock page list List result = new ArrayList<>(); result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); - when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); + when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group}), + eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); Map configAdvanceInfo = new HashMap<>(); - Page configInfo4Page = externalConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = externalConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); @@ -762,8 +820,8 @@ void testFindConfigInfo4PageWithTags() { when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group, "tags1", "tags3"}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); - Page configInfo4Page = externalConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = externalConfigInfoPersistService.findConfigInfo4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); } @@ -791,7 +849,8 @@ void testConfigInfoCountByTenant() { String tenant = "tenant124"; //mock total count - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant}), eq(Integer.class))).thenReturn(new Integer(90)); + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {tenant}), eq(Integer.class))).thenReturn( + new Integer(90)); int count = externalConfigInfoPersistService.configInfoCount(tenant); assertEquals(90, count); @@ -817,19 +876,19 @@ void testFindConfigInfoLike4Page() { configAdvanceInfo.put("content", content); //mock total count when(jdbcTemplate.queryForObject(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content}), - eq(Integer.class))).thenReturn(new Integer(9)); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, + content}), eq(Integer.class))).thenReturn(new Integer(9)); //mock page list List result = new ArrayList<>(); result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); when(jdbcTemplate.query(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content}), - eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, + content}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); - Page configInfo4Page = externalConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = externalConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); @@ -849,19 +908,19 @@ void testFindConfigInfoLike4PageWithTags() { String tenant = "tenant4567890"; //mock total count when(jdbcTemplate.queryForObject(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, "tags", "tag2"}), - eq(Integer.class))).thenReturn(new Integer(9)); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, + "tags", "tag2"}), eq(Integer.class))).thenReturn(new Integer(9)); //mock page list List result = new ArrayList<>(); result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); when(jdbcTemplate.query(anyString(), - eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, "tags", "tag2"}), - eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); + eq(new Object[] {tenant, dataId.replaceAll("\\*", "%"), group.replaceAll("\\*", "%"), appName, content, + "tags", "tag2"}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); - Page configInfo4Page = externalConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, tenant, - configAdvanceInfo); + Page configInfo4Page = externalConfigInfoPersistService.findConfigInfoLike4Page(1, 3, dataId, group, + tenant, configAdvanceInfo); assertEquals(result.size(), configInfo4Page.getPageItems().size()); assertEquals(9, configInfo4Page.getTotalCount()); @@ -881,7 +940,8 @@ void testFindChangeConfig() { when(jdbcTemplate.query(anyString(), eq(new Object[] {startTime, lastMaxId, pageSize}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(result); - List configInfo4List = externalConfigInfoPersistService.findChangeConfig(startTime, lastMaxId, pageSize); + List configInfo4List = externalConfigInfoPersistService.findChangeConfig(startTime, + lastMaxId, pageSize); assertEquals(result.size(), configInfo4List.size()); } @@ -894,8 +954,8 @@ void testFindChangeConfigError() { when(jdbcTemplate.query(anyString(), eq(new Object[] {startTime, lastMaxId, pageSize}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenThrow(new CannotAcquireLockException("mock ex")); try { - List configInfo4List = externalConfigInfoPersistService.findChangeConfig(startTime, lastMaxId, - pageSize); + List configInfo4List = externalConfigInfoPersistService.findChangeConfig(startTime, + lastMaxId, pageSize); assertTrue(false); } catch (Exception e) { assertTrue(e instanceof CannotAcquireLockException); @@ -910,24 +970,25 @@ void testSelectTagByConfig() { //mock page list List tagStrings = Arrays.asList("", "", ""); - when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenReturn(tagStrings); + when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenReturn(tagStrings); List configTags = externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant); assertEquals(tagStrings, configTags); //mock EmptyResultDataAccessException - when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenThrow( - new EmptyResultDataAccessException(3)); + when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenThrow(new EmptyResultDataAccessException(3)); List nullResult = externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant); assertTrue(nullResult == null); //mock IncorrectResultSizeDataAccessException - when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenThrow( - new IncorrectResultSizeDataAccessException(3)); + when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenThrow(new IncorrectResultSizeDataAccessException(3)); List nullResult2 = externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant); assertTrue(nullResult2 == null); //mock IncorrectResultSizeDataAccessException - when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenThrow( - new CannotGetJdbcConnectionException("mock exp")); + when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenThrow(new CannotGetJdbcConnectionException("mock exp")); try { externalConfigInfoPersistService.selectTagByConfig(dataId, group, tenant); assertFalse(true); @@ -944,7 +1005,8 @@ void testFindConfigInfosByIds() { result.add(createMockConfigInfo(0)); result.add(createMockConfigInfo(1)); result.add(createMockConfigInfo(2)); - when(jdbcTemplate.query(anyString(), eq(new Object[] {123L, 1232345L}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn(result); + when(jdbcTemplate.query(anyString(), eq(new Object[] {123L, 1232345L}), eq(CONFIG_INFO_ROW_MAPPER))).thenReturn( + result); String ids = "123,1232345"; List configInfosByIds = externalConfigInfoPersistService.findConfigInfosByIds(ids); assertEquals(result.size(), configInfosByIds.size()); @@ -979,7 +1041,8 @@ void testFindConfigAdvanceInfo() { String tenant = "tenant13245"; //mock select tags List mockTags = Arrays.asList("tag1", "tag2", "tag3"); - when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenReturn(mockTags); + when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenReturn(mockTags); String schema = "schema12345654"; //mock select config advance @@ -989,7 +1052,8 @@ void testFindConfigAdvanceInfo() { eq(CONFIG_ADVANCE_INFO_ROW_MAPPER))).thenReturn(mockedAdvance); //execute return mock obj - ConfigAdvanceInfo configAdvanceInfo = externalConfigInfoPersistService.findConfigAdvanceInfo(dataId, group, tenant); + ConfigAdvanceInfo configAdvanceInfo = externalConfigInfoPersistService.findConfigAdvanceInfo(dataId, group, + tenant); //expect check schema & tags. assertEquals(mockedAdvance.getSchema(), configAdvanceInfo.getSchema()); assertEquals(String.join(",", mockTags), configAdvanceInfo.getConfigTags()); @@ -1021,14 +1085,15 @@ void testFindConfigAllInfo() { String tenant = "tenant13245"; //mock select tags List mockTags = Arrays.asList("tag1", "tag2", "tag3"); - when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), eq(String.class))).thenReturn(mockTags); + when(jdbcTemplate.queryForList(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(String.class))).thenReturn(mockTags); String schema = "schema12345654"; //mock select config advance ConfigAllInfo mockedConfig = new ConfigAllInfo(); mockedConfig.setSchema(schema); - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn( - mockedConfig); + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockedConfig); //execute return mock obj ConfigAllInfo configAllInfo = externalConfigInfoPersistService.findConfigAllInfo(dataId, group, tenant); @@ -1037,14 +1102,14 @@ void testFindConfigAllInfo() { assertEquals(String.join(",", mockTags), configAllInfo.getConfigTags()); //mock EmptyResultDataAccessException - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow( - new EmptyResultDataAccessException(1)); + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow(new EmptyResultDataAccessException(1)); //expect return null. assertNull(externalConfigInfoPersistService.findConfigAllInfo(dataId, group, tenant)); //mock CannotGetJdbcConnectionException - when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow( - new CannotGetJdbcConnectionException("mock exp")); + when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mock exp")); //expect throw exception. try { externalConfigInfoPersistService.findConfigAllInfo(dataId, group, tenant); @@ -1070,7 +1135,8 @@ void testFindConfigInfoState() { eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))).thenReturn(mockedConfig); //execute return mock obj - ConfigInfoStateWrapper configInfoStateWrapper = externalConfigInfoPersistService.findConfigInfoState(dataId, group, tenant); + ConfigInfoStateWrapper configInfoStateWrapper = externalConfigInfoPersistService.findConfigInfoState(dataId, + group, tenant); //expect check schema & tags. assertEquals(mockedConfig.getId(), configInfoStateWrapper.getId()); assertEquals(mockedConfig.getLastModified(), configInfoStateWrapper.getLastModified()); @@ -1109,24 +1175,25 @@ void testFindAllConfigInfo4Export() { String appName = "appName1243"; List ids = Arrays.asList(132L, 1343L, 245L); - when(jdbcTemplate.query(anyString(), eq(new Object[] {132L, 1343L, 245L}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockConfigs); + when(jdbcTemplate.query(anyString(), eq(new Object[] {132L, 1343L, 245L}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockConfigs); //execute return mock obj - List configAllInfosIds = externalConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, tenant, appName, - ids); + List configAllInfosIds = externalConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, + tenant, appName, ids); //expect check assertEquals(mockConfigs, configAllInfosIds); - when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group, appName}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn( - mockConfigs); + when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant, dataId, group, appName}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenReturn(mockConfigs); //execute return mock obj - List configAllInfosWithDataId = externalConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, tenant, - appName, null); + List configAllInfosWithDataId = externalConfigInfoPersistService.findAllConfigInfo4Export(dataId, + group, tenant, appName, null); //expect check assertEquals(mockConfigs, configAllInfosWithDataId); //mock CannotGetJdbcConnectionException - when(jdbcTemplate.query(anyString(), eq(new Object[] {132L, 1343L, 245L}), eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow( - new CannotGetJdbcConnectionException("mock exp11")); + when(jdbcTemplate.query(anyString(), eq(new Object[] {132L, 1343L, 245L}), + eq(CONFIG_ALL_INFO_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("mock exp11")); //expect throw exception. try { externalConfigInfoPersistService.findAllConfigInfo4Export(dataId, group, tenant, appName, ids); @@ -1146,9 +1213,11 @@ void testQueryConfigInfoByNamespace() { mockConfigs.add(createMockConfigInfoWrapper(1)); mockConfigs.add(createMockConfigInfoWrapper(2)); String tenant = "tenant13245"; - when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(mockConfigs); + when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn( + mockConfigs); //execute return mock obj - List configInfoWrappers = externalConfigInfoPersistService.queryConfigInfoByNamespace(tenant); + List configInfoWrappers = externalConfigInfoPersistService.queryConfigInfoByNamespace( + tenant); //expect check assertEquals(mockConfigs, configInfoWrappers); @@ -1156,7 +1225,8 @@ void testQueryConfigInfoByNamespace() { when(jdbcTemplate.query(anyString(), eq(new Object[] {tenant}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenThrow( new EmptyResultDataAccessException(2)); //execute return mock obj - List configInfoWrapperNull = externalConfigInfoPersistService.queryConfigInfoByNamespace(tenant); + List configInfoWrapperNull = externalConfigInfoPersistService.queryConfigInfoByNamespace( + tenant); //expect check assertEquals(Collections.EMPTY_LIST, configInfoWrapperNull); @@ -1211,10 +1281,12 @@ void testFindAllConfigInfoFragment() { mockConfigs.add(createMockConfigInfoWrapper(1)); mockConfigs.add(createMockConfigInfoWrapper(2)); long lastId = 10111L; - when(jdbcTemplate.query(anyString(), eq(new Object[] {lastId}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn(mockConfigs); + when(jdbcTemplate.query(anyString(), eq(new Object[] {lastId}), eq(CONFIG_INFO_WRAPPER_ROW_MAPPER))).thenReturn( + mockConfigs); int pageSize = 100; //execute return mock obj - Page returnConfigPage = externalConfigInfoPersistService.findAllConfigInfoFragment(lastId, pageSize, true); + Page returnConfigPage = externalConfigInfoPersistService.findAllConfigInfoFragment(lastId, + pageSize, true); //expect check assertEquals(mockConfigs, returnConfigPage.getPageItems()); @@ -1230,4 +1302,16 @@ void testFindAllConfigInfoFragment() { } + @Test + void testBuildFindConfigInfoStateSql() { + MapperManager mapperManager = MapperManager.instance(false); + ConfigInfoMapper configInfoMapper = mapperManager.findMapper(dataSourceService.getDataSourceType(), + TableConstant.CONFIG_INFO); + String select = configInfoMapper.select(Arrays.asList("id", "data_id", "group_id", "tenant_id", "gmt_modified"), + Arrays.asList("data_id", "group_id", "tenant_id")); + assertEquals( + "SELECT id,data_id,group_id,tenant_id,gmt_modified FROM config_info WHERE data_id = ? AND group_id = ? AND tenant_id = ?", + select); + } + } diff --git a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImplTest.java b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImplTest.java index e7509fd31ba..e3ef562b99b 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImplTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/service/repository/extrnal/ExternalHistoryConfigInfoPersistServiceImplTest.java @@ -42,7 +42,6 @@ import java.util.ArrayList; import java.util.List; -import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_DETAIL_ROW_MAPPER; import static com.alibaba.nacos.config.server.service.repository.ConfigRowMapperInjector.HISTORY_LIST_ROW_MAPPER; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -85,7 +84,8 @@ void before() { when(dataSourceService.getTransactionTemplate()).thenReturn(transactionTemplate); when(dataSourceService.getJdbcTemplate()).thenReturn(jdbcTemplate); when(dataSourceService.getDataSourceType()).thenReturn("mysql"); - envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))).thenReturn(false); + envUtilMockedStatic.when(() -> EnvUtil.getProperty(anyString(), eq(Boolean.class), eq(false))) + .thenReturn(false); externalHistoryConfigInfoPersistService = new ExternalHistoryConfigInfoPersistServiceImpl(); } @@ -107,20 +107,26 @@ void testInsertConfigHistoryAtomic() { String srcUser = "user12345"; String srcIp = "ip1234"; String ops = "D"; + String extraInfo = "type\":\"properties"; Timestamp timestamp = new Timestamp(System.currentTimeMillis()); ConfigInfo configInfo = new ConfigInfo(dataId, group, tenant, appName, content); configInfo.setEncryptedDataKey("key23456"); //expect insert success,verify insert invoked - externalHistoryConfigInfoPersistService.insertConfigHistoryAtomic(id, configInfo, srcIp, srcUser, timestamp, ops); + externalHistoryConfigInfoPersistService.insertConfigHistoryAtomic(id, configInfo, srcIp, srcUser, timestamp, + ops, "formal", null, extraInfo); Mockito.verify(jdbcTemplate, times(1)) - .update(anyString(), eq(id), eq(dataId), eq(group), eq(tenant), eq(appName), eq(content), eq(configInfo.getMd5()), - eq(srcIp), eq(srcUser), eq(timestamp), eq(ops), eq(configInfo.getEncryptedDataKey())); + .update(anyString(), eq(id), eq(dataId), eq(group), eq(tenant), eq(appName), eq(content), + eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(timestamp), eq(ops), eq("formal"), eq(""), + eq(extraInfo), eq(configInfo.getEncryptedDataKey())); - Mockito.when(jdbcTemplate.update(anyString(), eq(id), eq(dataId), eq(group), eq(tenant), eq(appName), eq(content), - eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(timestamp), eq(ops), eq(configInfo.getEncryptedDataKey()))) + Mockito.when( + jdbcTemplate.update(anyString(), eq(id), eq(dataId), eq(group), eq(tenant), eq(appName), eq(content), + eq(configInfo.getMd5()), eq(srcIp), eq(srcUser), eq(timestamp), eq(ops), eq("formal"), eq(""), + eq(extraInfo), eq(configInfo.getEncryptedDataKey()))) .thenThrow(new CannotGetJdbcConnectionException("mock ex...")); try { - externalHistoryConfigInfoPersistService.insertConfigHistoryAtomic(id, configInfo, srcIp, srcUser, timestamp, ops); + externalHistoryConfigInfoPersistService.insertConfigHistoryAtomic(id, configInfo, srcIp, srcUser, timestamp, + ops, "formal", null, extraInfo); assertTrue(false); } catch (Exception e) { assertEquals("mock ex...", e.getMessage()); @@ -140,47 +146,47 @@ void testRemoveConfigHistory() { void testFindDeletedConfig() { //mock query list return - ConfigInfoStateWrapper mockObj1 = new ConfigInfoStateWrapper(); + ConfigHistoryInfo mockObj1 = new ConfigHistoryInfo(); mockObj1.setDataId("data_id1"); mockObj1.setGroup("group_id1"); mockObj1.setTenant("tenant_id1"); mockObj1.setMd5("md51"); - mockObj1.setLastModified(System.currentTimeMillis()); + mockObj1.setLastModifiedTime(new Timestamp(System.currentTimeMillis())); - List list = new ArrayList<>(); + List list = new ArrayList<>(); list.add(mockObj1); - ConfigInfoStateWrapper mockObj2 = new ConfigInfoStateWrapper(); + ConfigHistoryInfo mockObj2 = new ConfigHistoryInfo(); mockObj2.setDataId("data_id2"); mockObj2.setGroup("group_id2"); mockObj2.setTenant("tenant_id2"); mockObj2.setMd5("md52"); + mockObj2.setLastModifiedTime(new Timestamp(System.currentTimeMillis())); list.add(mockObj2); int pageSize = 1233; long startId = 23456; Timestamp timestamp = new Timestamp(System.currentTimeMillis()); - Mockito.when( - jdbcTemplate.query(anyString(), eq(new Object[] {timestamp, startId, pageSize}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) - .thenReturn(list); + String publishType = "formal"; + Mockito.when(jdbcTemplate.query(anyString(), eq(new Object[] {publishType, timestamp, startId, pageSize}), + eq(HISTORY_DETAIL_ROW_MAPPER))).thenReturn(list); //execute - List deletedConfig = externalHistoryConfigInfoPersistService.findDeletedConfig(timestamp, startId, - pageSize); + List deletedConfig = externalHistoryConfigInfoPersistService.findDeletedConfig( + timestamp, startId, pageSize, "formal"); //expect verify assertEquals("data_id1", deletedConfig.get(0).getDataId()); assertEquals("group_id1", deletedConfig.get(0).getGroup()); assertEquals("tenant_id1", deletedConfig.get(0).getTenant()); - assertEquals(mockObj1.getLastModified(), deletedConfig.get(0).getLastModified()); + assertEquals(mockObj1.getLastModifiedTime(), new Timestamp(deletedConfig.get(0).getLastModified())); assertEquals("data_id2", deletedConfig.get(1).getDataId()); assertEquals("group_id2", deletedConfig.get(1).getGroup()); assertEquals("tenant_id2", deletedConfig.get(1).getTenant()); - assertEquals(mockObj2.getLastModified(), deletedConfig.get(1).getLastModified()); + assertEquals(mockObj2.getLastModifiedTime(), new Timestamp(deletedConfig.get(1).getLastModified())); //mock exception - Mockito.when( - jdbcTemplate.query(anyString(), eq(new Object[] {timestamp, startId, pageSize}), eq(CONFIG_INFO_STATE_WRAPPER_ROW_MAPPER))) - .thenThrow(new CannotGetJdbcConnectionException("conn error")); + Mockito.when(jdbcTemplate.query(anyString(), eq(new Object[] {publishType, timestamp, startId, pageSize}), + eq(HISTORY_DETAIL_ROW_MAPPER))).thenThrow(new CannotGetJdbcConnectionException("conn error")); try { - externalHistoryConfigInfoPersistService.findDeletedConfig(timestamp, startId, pageSize); + externalHistoryConfigInfoPersistService.findDeletedConfig(timestamp, startId, pageSize, "formal"); assertTrue(false); } catch (Exception e) { assertEquals("conn error", e.getMessage()); @@ -195,24 +201,28 @@ void testFindConfigHistory() { String tenant = "tenant34567"; //mock count - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))).thenReturn(300); + Mockito.when( + jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))) + .thenReturn(300); //mock list List mockList = new ArrayList<>(); mockList.add(createMockConfigHistoryInfo(0)); mockList.add(createMockConfigHistoryInfo(1)); mockList.add(createMockConfigHistoryInfo(2)); - Mockito.when(jdbcTemplate.query(anyString(), eq(new Object[] {dataId, group, tenant}), eq(HISTORY_LIST_ROW_MAPPER))) + Mockito.when( + jdbcTemplate.query(anyString(), eq(new Object[] {dataId, group, tenant}), eq(HISTORY_LIST_ROW_MAPPER))) .thenReturn(mockList); int pageSize = 100; int pageNo = 2; //execute & verify - Page historyReturn = externalHistoryConfigInfoPersistService.findConfigHistory(dataId, group, tenant, pageNo, - pageSize); + Page historyReturn = externalHistoryConfigInfoPersistService.findConfigHistory(dataId, group, + tenant, pageNo, pageSize); assertEquals(mockList, historyReturn.getPageItems()); assertEquals(300, historyReturn.getTotalCount()); //mock exception - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))) + Mockito.when( + jdbcTemplate.queryForObject(anyString(), eq(new Object[] {dataId, group, tenant}), eq(Integer.class))) .thenThrow(new CannotGetJdbcConnectionException("conn error111")); try { externalHistoryConfigInfoPersistService.findConfigHistory(dataId, group, tenant, pageNo, pageSize); @@ -284,13 +294,15 @@ void testFindConfigHistoryCountByTime() { Timestamp timestamp = new Timestamp(System.currentTimeMillis()); //mock count - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {timestamp}), eq(Integer.class))).thenReturn(308); + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {timestamp}), eq(Integer.class))) + .thenReturn(308); //execute & verify int count = externalHistoryConfigInfoPersistService.findConfigHistoryCountByTime(timestamp); assertEquals(308, count); //mock count is null - Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {timestamp}), eq(Integer.class))).thenReturn(null); + Mockito.when(jdbcTemplate.queryForObject(anyString(), eq(new Object[] {timestamp}), eq(Integer.class))) + .thenReturn(null); //execute & verify try { externalHistoryConfigInfoPersistService.findConfigHistoryCountByTime(timestamp); diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/ConfigExtInfoUtilTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/ConfigExtInfoUtilTest.java new file mode 100644 index 00000000000..d9d6ff0c1f8 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/ConfigExtInfoUtilTest.java @@ -0,0 +1,70 @@ +/* + * 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.config.server.utils; + +import com.alibaba.nacos.api.config.ConfigType; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.model.gray.ConfigGrayPersistInfo; +import com.alibaba.nacos.config.server.model.gray.GrayRuleManager; +import org.junit.jupiter.api.Test; + +import static com.alibaba.nacos.config.server.model.gray.BetaGrayRule.PRIORITY; + +public class ConfigExtInfoUtilTest { + + @Test + void testExt4Formal() { + + String dataId = "dataId4567"; + String group = "group3456789"; + String tenant = "tenant4567890"; + + //mock exist config info + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName("old_app"); + configAllInfo.setMd5("old_md5"); + configAllInfo.setId(12345678765L); + configAllInfo.setType(ConfigType.JSON.getType()); + configAllInfo.setSchema("testschema"); + configAllInfo.setCreateUser("testuser"); + configAllInfo.setEffect("online"); + configAllInfo.setDesc("desc"); + configAllInfo.setUse("use124"); + configAllInfo.setConfigTags("ctag1,ctag2"); + String extraInfoFromAllInfo = ConfigExtInfoUtil.getExtInfoFromAllInfo(configAllInfo); + System.out.println(extraInfoFromAllInfo); + + } + + @Test + void testExt4Gray() { + String grayName = "gray124"; + ConfigGrayPersistInfo configGrayPersistInfo = new ConfigGrayPersistInfo(BetaGrayRule.TYPE_BETA, + BetaGrayRule.VERSION, "127.0.0.1,127.0.0.2", PRIORITY); + + String grayRule = GrayRuleManager.serializeConfigGrayPersistInfo(configGrayPersistInfo); + String oldSrcUser = "user132"; + String extraInfoFromAllInfo = ConfigExtInfoUtil.getExtInfoFromGrayInfo(grayName, grayRule, oldSrcUser); + System.out.println(extraInfoFromAllInfo); + + } +} + diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/MD5UtilTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/MD5UtilTest.java index 75ba6c1a82e..1ecdec56130 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/utils/MD5UtilTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/MD5UtilTest.java @@ -17,7 +17,10 @@ package com.alibaba.nacos.config.server.utils; import com.alibaba.nacos.config.server.service.ConfigCacheService; +import com.alibaba.nacos.sys.env.EnvUtil; import org.apache.commons.io.IOUtils; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.MockedStatic; import org.mockito.Mockito; @@ -38,14 +41,36 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; class MD5UtilTest { + MockedStatic envUtilMockedStatic; + + MockedStatic configCacheServiceMockedStatic; + + MockedStatic md5ComparatorDelegateMockedStatic; + + @BeforeEach + void setUp() { + envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); + configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class); + md5ComparatorDelegateMockedStatic = Mockito.mockStatic(Md5ComparatorDelegate.class); + } + + @AfterEach + void tearDown() { + envUtilMockedStatic.close(); + configCacheServiceMockedStatic.close(); + md5ComparatorDelegateMockedStatic.close(); + } + @Test void testCompareMd5() { - - final MockedStatic configCacheServiceMockedStatic = Mockito.mockStatic(ConfigCacheService.class); + Md5ComparatorDelegate md5ComparatorDelegate = Mockito.mock(Md5ComparatorDelegate.class); + when(Md5ComparatorDelegate.getInstance()).thenReturn(md5ComparatorDelegate); when(ConfigCacheService.isUptodate(anyString(), anyString(), anyString(), anyString())).thenReturn(false); @@ -56,12 +81,12 @@ void testCompareMd5() { request.addHeader("Vipserver-Tag", "test"); MockHttpServletResponse response = new MockHttpServletResponse(); - List changedGroupKeys = MD5Util.compareMd5(request, response, clientMd5Map); + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")).thenReturn("nacos"); + when(md5ComparatorDelegate.compareMd5(request, response, clientMd5Map)).thenReturn(new ArrayList<>()); + MD5Util.compareMd5(request, response, clientMd5Map); - assertEquals(1, changedGroupKeys.size()); - assertEquals("test", changedGroupKeys.get(0)); + verify(md5ComparatorDelegate, times(1)).compareMd5(request, response, clientMd5Map); - configCacheServiceMockedStatic.close(); } @Test diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/Md5ComparatorDelegateTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/Md5ComparatorDelegateTest.java new file mode 100644 index 00000000000..640aad0f184 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/Md5ComparatorDelegateTest.java @@ -0,0 +1,105 @@ +/* + * Copyright 1999-2024 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.config.server.utils; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedConstruction; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.lang.reflect.Constructor; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collections; +import java.util.HashMap; + +import static org.mockito.Mockito.mockConstruction; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class Md5ComparatorDelegateTest { + + public MockedStatic envUtilMockedStatic; + + public MockedStatic nacosServiceLoaderMockedStatic; + + public MockedConstruction nacosMd5ComparatorMockedConstruction; + + @Mock + public NacosMd5Comparator nacosMd5Comparator; + + @BeforeEach + void setUp() { + envUtilMockedStatic = mockStatic(EnvUtil.class); + nacosServiceLoaderMockedStatic = mockStatic(NacosServiceLoader.class); + } + + @AfterEach + void tearDown() { + envUtilMockedStatic.close(); + nacosServiceLoaderMockedStatic.close(); + } + + @Test + public void test() { + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")).thenReturn("lalala"); + nacosServiceLoaderMockedStatic.when(() -> NacosServiceLoader.load(Md5Comparator.class)) + .thenReturn(Collections.singletonList(nacosMd5Comparator)); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + HashMap clientMd5Map = new HashMap<>(); + nacosMd5ComparatorMockedConstruction = mockConstruction(NacosMd5Comparator.class, (mock, context) -> { + when(mock.compareMd5(request, response, clientMd5Map)).thenReturn(null); + }); + Md5ComparatorDelegate.getInstance().compareMd5(request, response, clientMd5Map); + verify(nacosMd5Comparator, times(0)).compareMd5(request, response, clientMd5Map); + nacosMd5ComparatorMockedConstruction.close(); + } + + @Test + public void test2() throws Exception { + when(nacosMd5Comparator.getName()).thenReturn("nacos"); + envUtilMockedStatic.when(() -> EnvUtil.getProperty("nacos.config.cache.type", "nacos")).thenReturn("nacos"); + nacosServiceLoaderMockedStatic.when(() -> NacosServiceLoader.load(Md5Comparator.class)) + .thenReturn(Collections.singletonList(nacosMd5Comparator)); + Constructor constructor = Md5ComparatorDelegate.class.getDeclaredConstructor(); + constructor.setAccessible(true); + Field field = Md5ComparatorDelegate.class.getDeclaredField("INSTANCE"); + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + field.setAccessible(true); + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + Md5ComparatorDelegate delegate = (Md5ComparatorDelegate) constructor.newInstance(); + field.set(null, delegate); + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + HashMap clientMd5Map = new HashMap<>(); + Md5ComparatorDelegate.getInstance().compareMd5(request, response, clientMd5Map); + verify(nacosMd5Comparator, times(1)).compareMd5(request, response, clientMd5Map); + } +} \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/NacosMd5ComparatorTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/NacosMd5ComparatorTest.java new file mode 100644 index 00000000000..29c33f08092 --- /dev/null +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/NacosMd5ComparatorTest.java @@ -0,0 +1,114 @@ +/* + * Copyright 1999-2024 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.config.server.utils; + +import com.alibaba.nacos.config.server.service.ConfigCacheService; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.util.HashMap; +import java.util.List; + +import static com.alibaba.nacos.api.common.Constants.VIPSERVER_TAG; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NacosMd5ComparatorTest { + + MockedStatic mockRequestUtil; + + MockedStatic configCacheServiceMockedStatic; + + @Mock + HttpServletRequest request; + + @Mock + HttpServletResponse response; + + @BeforeEach + void setUp() { + mockRequestUtil = mockStatic(RequestUtil.class); + configCacheServiceMockedStatic = mockStatic(ConfigCacheService.class); + } + + @AfterEach + void tearDown() { + mockRequestUtil.close(); + configCacheServiceMockedStatic.close(); + } + + @Test + void getName() { + NacosMd5Comparator nacosMd5Comparator = new NacosMd5Comparator(); + assertEquals("nacos", nacosMd5Comparator.getName()); + } + + @Test + void compareMd5NoChange() { + String ip = "127.0.0.1"; + String tag = "tag"; + when(request.getHeader(VIPSERVER_TAG)).thenReturn(tag); + mockRequestUtil.when(() -> RequestUtil.getRemoteIp(request)).thenReturn(ip); + + String groupKey1 = "groupKey1"; + String groupKey2 = "groupKey2"; + String clientMd5 = "clientMd5"; + HashMap clientMd5Map = new HashMap<>(); + clientMd5Map.put(groupKey1, clientMd5); + clientMd5Map.put(groupKey2, clientMd5); + + NacosMd5Comparator nacosMd5Comparator = new NacosMd5Comparator(); + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.isUptodate(anyString(), eq(clientMd5), eq(ip), eq(tag))).thenReturn(true); + + List changedGroupKeys = nacosMd5Comparator.compareMd5(request, response, clientMd5Map); + assertEquals(0, changedGroupKeys.size()); + } + + @Test + void compareMd5Change() { + String ip = "127.0.0.1"; + String tag = "tag"; + when(request.getHeader(VIPSERVER_TAG)).thenReturn(tag); + mockRequestUtil.when(() -> RequestUtil.getRemoteIp(request)).thenReturn(ip); + + String groupKey1 = "groupKey1"; + String groupKey2 = "groupKey2"; + String clientMd5 = "clientMd5"; + HashMap clientMd5Map = new HashMap<>(); + clientMd5Map.put(groupKey1, clientMd5); + clientMd5Map.put(groupKey2, clientMd5); + + NacosMd5Comparator nacosMd5Comparator = new NacosMd5Comparator(); + configCacheServiceMockedStatic.when( + () -> ConfigCacheService.isUptodate(anyString(), eq(clientMd5), eq(ip), eq(tag))).thenReturn(false); + + List changedGroupKeys = nacosMd5Comparator.compareMd5(request, response, clientMd5Map); + assertEquals(2, changedGroupKeys.size()); + } +} \ No newline at end of file diff --git a/config/src/test/java/com/alibaba/nacos/config/server/utils/PropertyUtilTest.java b/config/src/test/java/com/alibaba/nacos/config/server/utils/PropertyUtilTest.java index 5224b7c62ea..49df4cf84e5 100644 --- a/config/src/test/java/com/alibaba/nacos/config/server/utils/PropertyUtilTest.java +++ b/config/src/test/java/com/alibaba/nacos/config/server/utils/PropertyUtilTest.java @@ -43,8 +43,8 @@ class PropertyUtilTest { @BeforeEach void setUp() { envUtilMockedStatic = Mockito.mockStatic(EnvUtil.class); - envUtilMockedStatic.when(() -> EnvUtil.getProperty(eq("memory_limit_file_path"), eq("/sys/fs/cgroup/memory/memory.limit_in_bytes"))) - .thenReturn(mockMem); + envUtilMockedStatic.when(() -> EnvUtil.getProperty(eq("memory_limit_file_path"), + eq("/sys/fs/cgroup/memory/memory.limit_in_bytes"))).thenReturn(mockMem); } @@ -76,6 +76,10 @@ private void clearAllDumpFiled() throws Exception { Field allDumpPageSizeFiled = FieldUtils.getField(PropertyUtil.class, "allDumpPageSize"); allDumpPageSizeFiled.setAccessible(true); allDumpPageSizeFiled.set(null, null); + + Field limitMemoryFileFiled = FieldUtils.getField(PropertyUtil.class, "limitMemoryFile"); + limitMemoryFileFiled.setAccessible(true); + limitMemoryFileFiled.set(null, null); } @Test diff --git a/console-ui/README.md b/console-ui/README.md index 236227a6413..ad651dbfbbf 100644 --- a/console-ui/README.md +++ b/console-ui/README.md @@ -1,13 +1,13 @@ # 开始项目 国内访问 npm 比较慢,我们可以使用阿里的镜像, 在 npm 或者 yarn 命令后面加参数: -> --registry=https://registry.npm.taobao.org +> --registry=https://registry.npmmirror.com 例: ``` -npm install --registry=https://registry.npm.taobao.org -yarn --registry=https://registry.npm.taobao.org +npm install --registry=https://registry.npmmirror.com +yarn --registry=https://registry.npmmirror.com ``` -[详情地址: http://npm.taobao.org/](http://npm.taobao.org/) +[详情地址: https://npmmirror.com/](http://npm.taobao.org/) ## Node安装 diff --git a/console-ui/build/webpack.dev.conf.js b/console-ui/build/webpack.dev.conf.js index 9928e21af35..044298343de 100644 --- a/console-ui/build/webpack.dev.conf.js +++ b/console-ui/build/webpack.dev.conf.js @@ -40,7 +40,7 @@ module.exports = Object.assign({}, base, { changeOrigin: true, secure: false, target: 'http://localhost:8848', - pathRewrite: {'^/v1' : '/nacos/v1', '^/v2' : '/nacos/v2'} + pathRewrite: {'^/v1' : '/nacos/v1', '^/v2' : '/nacos/v2', '^/v3' : '/nacos/v3'} }], disableHostCheck: true, open: true, diff --git a/console-ui/package-lock.json b/console-ui/package-lock.json index 16022d7e032..76c10db2f2d 100644 --- a/console-ui/package-lock.json +++ b/console-ui/package-lock.json @@ -2895,19 +2895,6 @@ "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "dev": true }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -2917,25 +2904,6 @@ "ms": "2.0.0" } }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -2943,9 +2911,9 @@ "dev": true }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true }, "qs": { @@ -2958,15 +2926,16 @@ } }, "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "requires": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" } } } @@ -3218,6 +3187,72 @@ "get-intrinsic": "^1.0.2" } }, + "call-bind-apply-helpers": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", + "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + } + } + }, + "call-bound": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", + "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "get-intrinsic": "^1.2.6" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true + } + } + }, "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", @@ -3972,9 +4007,9 @@ } }, "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", "dev": true, "requires": { "path-key": "^3.1.0", @@ -4611,6 +4646,25 @@ } } }, + "dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "dependencies": { + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + } + } + }, "duplexify": { "version": "3.7.1", "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-3.7.1.tgz", @@ -4642,9 +4696,9 @@ "dev": true }, "elliptic": { - "version": "6.5.7", - "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.7.tgz", - "integrity": "sha512-ESVCtTwiA+XhY3wyh24QqRGBoP3rEdDUl3EDUUo9tft074fi19IrdpH7hLCMMP3CIj7jb3W96rn8lt/BqIlt5Q==", + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", "dev": true, "requires": { "bn.js": "^4.11.9", @@ -4657,9 +4711,9 @@ }, "dependencies": { "bn.js": { - "version": "4.12.0", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz", - "integrity": "sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==", + "version": "4.12.1", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.1.tgz", + "integrity": "sha512-k8TVBiPkPJT9uHLdOKfFpqcfprwBFOAAXXozRubr7R7PfIuKvQlzcI4M0pALeqXN09vdaMbUdUj+pass+uULAg==", "dev": true } } @@ -4792,34 +4846,10 @@ "dev": true }, "es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", - "dev": true, - "requires": { - "get-intrinsic": "^1.2.4" - }, - "dependencies": { - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - } - } + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "dev": true }, "es-errors": { "version": "1.3.0", @@ -4849,6 +4879,15 @@ "safe-array-concat": "^1.0.1" } }, + "es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "dev": true, + "requires": { + "es-errors": "^1.3.0" + } + }, "es-set-tostringtag": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", @@ -5472,9 +5511,9 @@ } }, "express": { - "version": "4.21.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", - "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", + "version": "4.21.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", + "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", "dev": true, "requires": { "accepts": "~1.3.8", @@ -5496,7 +5535,7 @@ "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", + "path-to-regexp": "0.1.12", "proxy-addr": "~2.0.7", "qs": "6.13.0", "range-parser": "~1.2.1", @@ -5516,19 +5555,6 @@ "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", "dev": true }, - "call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - } - }, "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -5538,25 +5564,6 @@ "ms": "2.0.0" } }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -5564,15 +5571,15 @@ "dev": true }, "object-inspect": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", - "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", "dev": true }, "path-to-regexp": { - "version": "0.1.10", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", - "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==", + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", "dev": true }, "qs": { @@ -5591,15 +5598,16 @@ "dev": true }, "side-channel": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, "requires": { - "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.4", - "object-inspect": "^1.13.1" + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" } } } @@ -8414,6 +8422,12 @@ "object-visit": "^1.0.0" } }, + "math-intrinsics": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.0.0.tgz", + "integrity": "sha512-4MqMiKP90ybymYvsut0CH2g4XWbfLtmlCkXmtmdcDCxNB+mQcu1w/1+L/VD7vi/PSv7X2JYV7SCcR+jiPXnQtA==", + "dev": true + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -10952,61 +10966,6 @@ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "dev": true }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "dependencies": { - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } - }, - "function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true - }, - "get-intrinsic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", - "dev": true, - "requires": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "has-proto": "^1.0.1", - "has-symbols": "^1.0.3", - "hasown": "^2.0.0" - } - }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "requires": { - "es-define-property": "^1.0.0" - } - } - } - }, "set-function-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.1.tgz", @@ -11105,6 +11064,137 @@ "object-inspect": "^1.9.0" } }, + "side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "dev": true, + "requires": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "dependencies": { + "object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true + } + } + }, + "side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true + }, + "object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true + } + } + }, + "side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "dev": true, + "requires": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "dependencies": { + "function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true + }, + "get-intrinsic": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.6.tgz", + "integrity": "sha512-qxsEs+9A+u85HhllWJJFicJfPDhRmjzoYdl64aMWW9yRIJmSyxdn8IEkuIM530/7T+lv0TIHd8L6Q/ra0tEoeA==", + "dev": true, + "requires": { + "call-bind-apply-helpers": "^1.0.1", + "dunder-proto": "^1.0.0", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "function-bind": "^1.1.2", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.0.0" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "dev": true + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "dev": true + }, + "object-inspect": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", + "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "dev": true + } + } + }, "signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", diff --git a/console-ui/src/components/BatchHandle/BatchHandle.js b/console-ui/src/components/BatchHandle/BatchHandle.js index e6bc9057faa..7f840c74745 100644 --- a/console-ui/src/components/BatchHandle/BatchHandle.js +++ b/console-ui/src/components/BatchHandle/BatchHandle.js @@ -80,7 +80,7 @@ class BatchHandle extends React.Component { dataSource.pageSize }`, success: res => { - if (res.code === 200) { + if (res.code === 0) { this.setState({ dataSourceList: res.data.map(obj => ({ diff --git a/console-ui/src/components/CloneDialog/CloneDialog.js b/console-ui/src/components/CloneDialog/CloneDialog.js index 5d7903c05f8..809cf73a91d 100644 --- a/console-ui/src/components/CloneDialog/CloneDialog.js +++ b/console-ui/src/components/CloneDialog/CloneDialog.js @@ -80,7 +80,7 @@ class CloneDialog extends React.Component { type: 'get', url: `/diamond-ops/service/serverId/${serverId}/namespaceInfo`, success: res => { - if (res.code === 200) { + if (res.code === 0) { const dataSource = []; res.data.forEach(value => { if (value.namespace !== payload.tenantFrom.id) { diff --git a/console-ui/src/components/EditorNameSpace/EditorNameSpace.js b/console-ui/src/components/EditorNameSpace/EditorNameSpace.js index 5e1af4a796d..249fb788d18 100644 --- a/console-ui/src/components/EditorNameSpace/EditorNameSpace.js +++ b/console-ui/src/components/EditorNameSpace/EditorNameSpace.js @@ -72,8 +72,9 @@ class EditorNameSpace extends React.Component { this.field.setValues(record); request({ type: 'get', - url: `v1/console/namespaces?show=all&namespaceId=${record.namespace}`, + url: `v3/console/core/namespace?namespaceId=${record.namespace}`, success: res => { + res = res.data; if (res !== null) { this.field.setValue('namespaceDesc', res.namespaceDesc); } else { @@ -101,15 +102,15 @@ class EditorNameSpace extends React.Component { beforeSend: () => { this.openLoading(); }, - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', contentType: 'application/x-www-form-urlencoded', data: { - namespace: values.namespace, - namespaceShowName: values.namespaceShowName, + namespaceId: values.namespace, + namespaceName: values.namespaceShowName, namespaceDesc: values.namespaceDesc, }, success: res => { - if (res === true) { + if (res.data === true) { this.closeDialog(); this.props.getNameSpaces(); this.refreshNameSpace(); // 刷新全局namespace @@ -131,9 +132,9 @@ class EditorNameSpace extends React.Component { setTimeout(() => { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', success: res => { - if (res.code === 200) { + if (res.code === 0) { window.namespaceList = res.data; } }, diff --git a/console-ui/src/components/ImportDialog/ImportDialog.js b/console-ui/src/components/ImportDialog/ImportDialog.js index 0086dd9b2d3..726dde6f5fe 100644 --- a/console-ui/src/components/ImportDialog/ImportDialog.js +++ b/console-ui/src/components/ImportDialog/ImportDialog.js @@ -85,7 +85,7 @@ class ImportDialog extends React.Component { }; formatter = res => { - if (res.code === 200) { + if (res.code === 0) { return { code: '0', retData: res, diff --git a/console-ui/src/components/NameSpaceList/NameSpaceList.js b/console-ui/src/components/NameSpaceList/NameSpaceList.js index 0b2355a4972..fbd77e55045 100644 --- a/console-ui/src/components/NameSpaceList/NameSpaceList.js +++ b/console-ui/src/components/NameSpaceList/NameSpaceList.js @@ -63,7 +63,7 @@ class NameSpaceList extends React.Component { linkKey, }, success: res => { - if (res.code === 200) { + if (res.code === 0) { window[keyName] = res.data; this.setState({ [keyName]: res.data, @@ -118,9 +118,9 @@ class NameSpaceList extends React.Component { } else { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace/list', success: res => { - if (res.code === 200) { + if (res.code === 0) { this.handleNameSpaces(res.data); } else { Dialog.alert({ @@ -138,7 +138,7 @@ class NameSpaceList extends React.Component { } handleNameSpaces(data) { - const nownamespace = getParams('namespace') || ''; + const nownamespace = getParams('namespace') || 'public'; // let namespaceShowName = this._namespaceShowName || data[0].namespaceShowName || ''; window.namespaceList = data; @@ -154,7 +154,7 @@ class NameSpaceList extends React.Component { } window.namespaceShowName = namespaceShowName; window.namespaceDesc = namespaceDesc; - setParams('namespace', nownamespace || ''); + setParams('namespace', nownamespace || 'public'); localStorage.setItem('namespace', nownamespace); // setParams('namespaceShowName', namespaceShowName); this.props.setNowNameSpace && diff --git a/console-ui/src/components/NewNameSpace/NewNameSpace.js b/console-ui/src/components/NewNameSpace/NewNameSpace.js index 4f50e0c07d7..40b628e58e5 100644 --- a/console-ui/src/components/NewNameSpace/NewNameSpace.js +++ b/console-ui/src/components/NewNameSpace/NewNameSpace.js @@ -111,13 +111,14 @@ class NewNameSpace extends React.Component { } request({ type: 'get', - url: 'v1/console/namespaces?checkNamespaceIdExist=true', + url: 'v3/console/core/namespace/exist', contentType: 'application/x-www-form-urlencoded', beforeSend: () => this.openLoading(), data: { customNamespaceId, }, success: res => { + res = res.data; this.disabled = false; this.setState({ disabled: false, @@ -130,7 +131,7 @@ class NewNameSpace extends React.Component { } else { request({ type: 'post', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', contentType: 'application/x-www-form-urlencoded', beforeSend: () => this.openLoading(), data: { @@ -139,6 +140,7 @@ class NewNameSpace extends React.Component { namespaceDesc: values.namespaceDesc, }, success: res => { + res = res.data; this.disabled = false; this.setState({ disabled: false, @@ -167,9 +169,9 @@ class NewNameSpace extends React.Component { setTimeout(() => { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', success: res => { - if (res.code === 200) { + if (res.code === 0) { window.namespaceList = res.data; } }, diff --git a/console-ui/src/config.js b/console-ui/src/config.js index 705d54cd788..6c7959760cb 100644 --- a/console-ui/src/config.js +++ b/console-ui/src/config.js @@ -27,12 +27,12 @@ module.exports = { { key: 'home', // 用作顶部菜单的选中 text: 'HOME', - link: 'https://nacos.io/en-us/index.html', + link: 'https://https://nacos.io/en/', }, { key: 'docs', text: 'DOCS', - link: 'https://nacos.io/en/docs/latest/what-is-nacos/', + link: 'https://nacos.io/en/docs/latest/overview/', }, { key: 'blog', @@ -42,7 +42,7 @@ module.exports = { { key: 'community', text: 'COMMUNITY', - link: 'https://nacos.io/en/news/', + link: 'https://nacos.io/en/blog/community/', }, { key: 'enterprise', @@ -92,12 +92,12 @@ module.exports = { { key: 'home', text: '首页', - link: 'https://nacos.io/zh-cn/', + link: 'https://nacos.io/', }, { key: 'docs', text: '文档', - link: 'https://nacos.io/docs/latest/what-is-nacos/', + link: 'https://nacos.io/docs/latest/overview/', }, { key: 'blog', @@ -107,7 +107,7 @@ module.exports = { { key: 'community', text: '社区', - link: 'https://nacos.io/news/', + link: 'https://nacos.io/blog/community/', }, { key: 'enterprise', diff --git a/console-ui/src/locales/en-US.js b/console-ui/src/locales/en-US.js index 2421ff01f1e..e67a84b665c 100644 --- a/console-ui/src/locales/en-US.js +++ b/console-ui/src/locales/en-US.js @@ -258,6 +258,9 @@ const I18N_CONF = { historyCompareTitle: 'History Compare', historyCompareLastVersion: 'Lasted Release Version', historyCompareSelectedVersion: 'Selected Version', + publishType: 'Publish Type', + formal: 'Formal Version', + gray: 'Gray Version', }, HistoryDetail: { historyDetails: 'History Details', @@ -273,6 +276,10 @@ const I18N_CONF = { configureContent: 'Configuration Content', back: 'Back', namespace: 'Namespace', + publishType: 'Publish Type', + formal: 'Formal Version', + gray: 'Gray Version', + grayRule: 'Gray Rule', }, DashboardCard: { importantReminder0: 'Important reminder', @@ -304,9 +311,9 @@ const I18N_CONF = { queryResults: 'Found', articleMeetRequirements: 'configuration items', fuzzydMode: 'Default fuzzy query mode', - fuzzyd: "Add wildcard '*' for fuzzy query", + fuzzyd: 'Accurate query mode opened', defaultFuzzyd: 'Default fuzzy query mode opened', - fuzzyg: "Add wildcard '*' for fuzzy query", + fuzzyg: 'Accurate query mode opened', defaultFuzzyg: 'Default fuzzy query mode opened', query: 'Search', advancedQuery9: 'Advanced Query', @@ -665,6 +672,7 @@ const I18N_CONF = { defaultFuzzyd: 'Default fuzzy query mode opened', fuzzyd: "Add wildcard '*' for fuzzy query", query: 'Search', + checkPermission: 'This role permission already exists!', }, NewPermissions: { addPermission: 'Add Permission', diff --git a/console-ui/src/locales/zh-CN.js b/console-ui/src/locales/zh-CN.js index ab719a1169e..1a44a04c379 100644 --- a/console-ui/src/locales/zh-CN.js +++ b/console-ui/src/locales/zh-CN.js @@ -256,6 +256,9 @@ const I18N_CONF = { historyCompareTitle: '历史版本比较', historyCompareLastVersion: '最新版本', historyCompareSelectedVersion: '当前选中版本', + publishType: '发布类型', + formal: '正式版本', + gray: '灰度版本', }, HistoryDetail: { historyDetails: '历史详情', @@ -271,6 +274,10 @@ const I18N_CONF = { sourceIp: '来源 IP', back: '返回', namespace: '命名空间', + publishType: '发布类型', + formal: '正式版本', + gray: '灰度版本', + grayRule: '灰度规则', }, DashboardCard: { importantReminder0: '重要提醒', @@ -301,9 +308,9 @@ const I18N_CONF = { queryResults: '查询到', articleMeetRequirements: '条满足要求的配置。', fuzzydMode: '默认模糊匹配', - fuzzyd: "添加通配符'*'进行模糊查询", + fuzzyd: '已关闭模糊搜索', defaultFuzzyd: '已开启默认模糊查询', - fuzzyg: "添加通配符'*'进行模糊查询", + fuzzyg: '已关闭模糊搜索', defaultFuzzyg: '已开启默认模糊查询', query: '查询', advancedQuery9: '高级查询', @@ -660,6 +667,7 @@ const I18N_CONF = { defaultFuzzyd: '已开启默认模糊查询', fuzzyd: "添加通配符'*'进行模糊查询", query: '查询', + checkPermission: '此角色权限已存在!', }, NewPermissions: { addPermission: '添加权限', diff --git a/console-ui/src/pages/AuthorityControl/PermissionsManagement/PermissionsManagement.js b/console-ui/src/pages/AuthorityControl/PermissionsManagement/PermissionsManagement.js index 74c1a71a56d..ebe5c3091a7 100644 --- a/console-ui/src/pages/AuthorityControl/PermissionsManagement/PermissionsManagement.js +++ b/console-ui/src/pages/AuthorityControl/PermissionsManagement/PermissionsManagement.js @@ -25,9 +25,15 @@ import { Form, Input, Switch, + Message, } from '@alifd/next'; import { connect } from 'react-redux'; -import { getPermissions, createPermission, deletePermission } from '../../../reducers/authority'; +import { + getPermissions, + checkPermission, + createPermission, + deletePermission, +} from '../../../reducers/authority'; import { getNamespaces } from '../../../reducers/namespace'; import RegionGroup from '../../../components/RegionGroup'; import NewPermissions from './NewPermissions'; @@ -217,9 +223,17 @@ class PermissionsManagement extends React.Component { - createPermission(permission).then(res => { - this.setState({ pageNo: 1 }, () => this.getPermissions()); - return res; + checkPermission(permission).then(res => { + if (res) { + Message.error({ + content: locale.checkPermission, + }); + } else { + createPermission(permission).then(res => { + this.setState({ pageNo: 1 }, () => this.getPermissions()); + return res; + }); + } }) } onCancel={() => this.colseCreatePermission()} diff --git a/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js b/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js index 51092c9fa1a..a0dd0b3b0c9 100644 --- a/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js +++ b/console-ui/src/pages/ClusterManagement/ClusterNodeList/ClusterNodeList.js @@ -84,7 +84,7 @@ class ClusterNodeList extends React.Component { `keyword=${keyword}`, ]; request({ - url: `v1/core/cluster/nodes?${parameter.join('&')}`, + url: `v3/console/core/cluster/nodes?${parameter.join('&')}`, beforeSend: () => this.openLoading(), success: ({ count = 0, data = [] } = {}) => { this.setState({ @@ -107,12 +107,13 @@ class ClusterNodeList extends React.Component { const accessToken = JSON.parse(localStorage.token || '{}').accessToken; this.openLoading(); axios - .post(`v1/core/cluster/server/leave?accessToken=${accessToken}`, nodes) + .post(`v3/console/core/cluster/server/leave?accessToken=${accessToken}`, nodes) .then(response => { - if (response.data.code === 200) { + if (response.data.code === 0) { Message.success(locale.leaveSucc); } else { - const errorMessage = response.data.message || locale.leaveFail; + // const errorMessage = response.data.message || locale.leaveFail; + const errorMessage = '此操作暂不可用'; this.showErrorDialog(locale.leavePrompt, errorMessage); } @@ -120,7 +121,8 @@ class ClusterNodeList extends React.Component { this.closeLoading(); }) .catch(error => { - const errorMessage = error.response?.data?.message || locale.leaveFail; + // const errorMessage = error.response?.data?.message || locale.leaveFail; + const errorMessage = '此操作暂不可用'; this.showErrorDialog(locale.leavePrompt, errorMessage); this.queryClusterStateList(); diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js index 21c97ae2235..bf876e5423b 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigCompared.js @@ -58,9 +58,9 @@ class ConfigCompared extends React.Component { getNamespaces() { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace/list', success: res => { - if (res.code === 200) { + if (res.code === 0) { const { namespacesDataSource } = this.state; this.setState({ namespacesDataSource: res.data }); } else { diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js index 59db9acfa39..3ca1f77fe71 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigDetail/ConfigDetail.js @@ -66,7 +66,7 @@ class ConfigDetail extends React.Component { this.group = getParams('group') || 'DEFAULT_GROUP'; this.ips = ''; this.valueMap = {}; // 存储不同版本的数据 - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || 'public'; this.searchDataId = getParams('searchDataId') || ''; this.searchGroup = getParams('searchGroup') || ''; this.pageSize = getParams('pageSize'); @@ -147,18 +147,18 @@ class ConfigDetail extends React.Component { const { locale = {} } = this.props; const self = this; this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || 'public'; this.edasAppName = getParams('edasAppName') || ''; this.inApp = this.edasAppName; - const url = `v1/cs/configs?show=all&dataId=${this.dataId}&group=${this.group}`; + const url = `v3/console/cs/config?&dataId=${this.dataId}&groupName=${this.group}`; request({ url, beforeSend() { self.openLoading(); }, success(result) { - if (result != null) { - const data = result; + if (result != null && result.code === 0) { + const data = result.data; self.valueMap.normal = data; self.field.setValue('dataId', data.dataId); self.field.setValue('content', data.content); @@ -186,7 +186,7 @@ class ConfigDetail extends React.Component { serverId: this.serverId, group: this.searchGroup, dataId: this.searchDataId, - namespace: this.tenant, + namespace: this.namespaceId, pageNo: this.pageNo, pageSize: this.pageSize, }) @@ -226,20 +226,20 @@ class ConfigDetail extends React.Component { let self = this; const { locale = {} } = this.props; let leftvalue = this.monacoEditor.getValue(); - let url = `v1/cs/history/previous?id=${this.valueMap.normal.id}&dataId=${this.dataId}&group=${this.group}`; + let url = `v3/console/cs/history/previous?id=${this.valueMap.normal.id}&dataId=${this.dataId}&groupName=${this.group}`; request({ url, beforeSend() { self.openLoading(); }, success(result) { - if (result != null) { - let rightvalue = result.content; + if (result.code === 0 && result.data != null) { + let rightvalue = result.data.content; leftvalue = leftvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); rightvalue = rightvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); self.diffEditorDialog.current.getInstance().openDialog(leftvalue, rightvalue); } else { - Dialog.alert({ title: locale.error, content: result.message }); + Dialog.alert({ title: locale.error, content: locale.configNotFind }); } }, complete() { @@ -248,19 +248,19 @@ class ConfigDetail extends React.Component { }); } - openCompare = ([dataId, group, tenant]) => { + openCompare = ([dataId, group, namespaceId]) => { let self = this; const { locale = {} } = this.props; let leftvalue = this.monacoEditor.getValue(); const params = { - show: 'all', - group, + // show: 'all', + groupName: group, dataId, - tenant, + namespaceId, }; - requestUtils.get('v1/cs/configs', { params }).then(res => { - if (res != null && res !== '') { - let rightvalue = res.content; + requestUtils.get('v3/console/cs/config', { params }).then(res => { + if (res.code === 0 && res.data != null && res.data !== '') { + let rightvalue = res.data.content; leftvalue = leftvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); rightvalue = rightvalue.replace(/\r\n/g, '\n').replace(/\n/g, '\r\n'); self.compareEditorDialog.current.getInstance().openDialog(leftvalue, rightvalue); @@ -319,7 +319,7 @@ class ConfigDetail extends React.Component { )}
-

{this.tenant}

+

{this.namespaceId}

diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js index e1d181a14f7..4a8a9c67a54 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/ConfigEditor.js @@ -62,7 +62,7 @@ class ConfigEditor extends React.Component { this.field = new Field(this); this.dataId = getParams('dataId') || 'yanlin'; this.group = getParams('group') || 'DEFAULT_GROUP'; - this.tenant = getParams('namespace') || ''; + this.tenant = getParams('namespace') || 'public'; this.state = { configType: 'text', codeValue: '', @@ -159,11 +159,11 @@ class ConfigEditor extends React.Component { navTo(url) { this.serverId = getParams('serverId') || ''; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 this.props.history.push( - `${url}?serverId=${this.serverId || ''}&dataId=${this.dataId}&group=${this.group}&namespace=${ - this.tenant - }` + `${url}?serverId=${this.serverId || ''}&dataId=${this.dataId}&groupName=${ + this.group + }&namespace=${this.tenant}` ); } @@ -182,17 +182,17 @@ class ConfigEditor extends React.Component { getDataDetail() { const { locale = {} } = this.props; const self = this; - this.tenant = getParams('namespace') || ''; + this.tenant = getParams('namespace') || 'public'; this.serverId = getParams('serverId') || 'center'; - const url = `v1/cs/configs?show=all&dataId=${this.dataId}&group=${this.group}`; + const url = `v3/console/cs/config?dataId=${this.dataId}&groupName=${this.group}`; request({ url, beforeSend() { self.openLoading(); }, success(result) { - if (result != null) { - const data = result; + if (result != null && result.code === 0) { + const data = result.data; self.valueMap.normal = data; self.field.setValue('dataId', data.dataId); // self.field.setValue('content', data.content); @@ -361,20 +361,20 @@ class ConfigEditor extends React.Component { const { locale = {} } = this.props; const self = this; this.codeValue = content; - this.tenant = getParams('namespace') || ''; + this.tenant = getParams('namespace') || 'public'; this.serverId = getParams('serverId') || 'center'; const payload = { dataId: this.field.getValue('dataId'), appName: this.inApp ? this.edasAppId : this.field.getValue('appName'), - group: this.field.getValue('group'), + groupName: this.field.getValue('group'), desc: this.field.getValue('desc'), - config_tags: this.state.config_tags.join(','), + configTags: this.state.config_tags.join(','), type: this.state.configType, content, - tenant: this.tenant, + namespaceId: this.tenant, }; - const url = 'v1/cs/configs'; + const url = 'v3/console/cs/config'; request({ type: 'post', contentType: 'application/x-www-form-urlencoded', diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js index 4455ae7f62b..4b58b2e354a 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigEditor/NewConfigEditor.js @@ -94,7 +94,7 @@ class ConfigEditor extends React.Component { componentDidMount() { const isNewConfig = !getParams('dataId'); const group = getParams('group').trim(); - this.tenant = getParams('namespace') || ''; + this.tenant = getParams('namespace') || 'public'; this.setState({ isNewConfig }, () => { if (!isNewConfig) { this.changeForm( @@ -104,7 +104,7 @@ class ConfigEditor extends React.Component { }, () => { this.getConfig(true).then(res => { - if (!res) { + if (res.code !== 0 || !res.data) { this.getConfig(); return; } @@ -257,22 +257,24 @@ class ConfigEditor extends React.Component { Object.keys(form).forEach(key => { payload[key] = form[key]; }); + payload.namespaceId = form.tenant; + payload.groupName = form.group; let configTags = this.state.form.config_tags; if (configTags.length > 0) { - payload.config_tags = configTags.join(','); + payload.configTags = configTags.join(','); } // #12046 console-ui should not offer encryptedDataKey field to API payload.encryptedDataKey = ''; const stringify = require('qs/lib/stringify'); this.setState({ loading: true }); return request({ - url: 'v1/cs/configs', + url: 'v3/console/cs/config', method: 'post', data: stringify(payload), headers, }).then( res => { - if (res) { + if (res.data) { if (isNewConfig) { this.setState({ isNewConfig: false }); } @@ -314,14 +316,13 @@ class ConfigEditor extends React.Component { stopBeta() { const { dataId, group } = this.state.form; - const tenant = getParams('namespace'); + const namespaceId = getParams('namespace'); return request - .delete('v1/cs/configs', { + .delete('v3/console/cs/config/beta', { params: { - beta: true, dataId, - group, - tenant, + groupName: group, + namespaceId, }, }) .then(res => { @@ -391,17 +392,13 @@ class ConfigEditor extends React.Component { const { dataId, group } = this.state.form; const params = { dataId, - group, + groupName: group, namespaceId: namespace, tenant: namespace, }; - if (beta) { - params.beta = true; - } else { - params.show = 'all'; - } - return request.get('v1/cs/configs', { params }).then(res => { - const form = beta ? res.data : res; + const url = beta ? 'v3/console/cs/config/beta' : 'v3/console/cs/config'; + return request.get(url, { params }).then(res => { + const form = res.data; if (!form) return false; const { type, content, configTags, betaIps, md5 } = form; this.setState({ betaIps }); @@ -421,20 +418,20 @@ class ConfigEditor extends React.Component { const { dataId, group } = this.state.form; const params = { dataId, - group, + groupName: group, namespaceId: namespace, tenant: namespace, }; // get subscribes of the namespace - return request.get('v1/cs/configs/listener', { params }).then(res => { + return request.get('v3/console/cs/config/listener', { params }).then(res => { const { subscriberDataSource } = this.state; - const lisentersGroupkeyIpMap = res.lisentersGroupkeyStatus; + const lisentersGroupkeyIpMap = res.data.lisentersGroupkeyStatus; if (lisentersGroupkeyIpMap) { this.setState({ subscriberDataSource: subscriberDataSource.concat(Object.keys(lisentersGroupkeyIpMap)), }); } - return res; + return res.data; }); } diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js b/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js index 9d058df05e5..bfa0cee2e9d 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigRollback/ConfigRollback.js @@ -39,6 +39,7 @@ class ConfigRollback extends React.Component { envName: '', visible: false, showmore: false, + extInfo: {}, }; // this.params = window.location.hash.split('?')[1]||''; } @@ -73,14 +74,14 @@ class ConfigRollback extends React.Component { getDataDetail() { const self = this; - this.tenant = getParams('namespace') || ''; + this.namespaceId = getParams('namespace') || 'public'; this.serverId = getParams('serverId') || 'center'; - const url = `v1/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`; + const url = `v3/console/cs/history?dataId=${this.dataId}&groupName=${this.group}&nid=${this.nid}`; request({ url, success(result) { if (result != null) { - const data = result; + const data = result.data; const envName = self.serverId; self.id = data.id; // 详情的id self.field.setValue('dataId', data.dataId); @@ -93,6 +94,7 @@ class ConfigRollback extends React.Component { self.field.setValue('envName', envName); self.setState({ envName, + extInfo: data.extInfo ? JSON.parse(data.extInfo) : {}, }); } }, @@ -134,21 +136,28 @@ class ConfigRollback extends React.Component { ), onOk() { - self.tenant = getParams('namespace') || ''; + self.tenant = getParams('namespace') || 'public'; self.serverId = getParams('serverId') || 'center'; self.dataId = self.field.getValue('dataId'); self.group = self.field.getValue('group'); + const { extInfo } = self.state; let postData = { appName: self.field.getValue('appName'), dataId: self.dataId, - group: self.group, + groupName: self.group, content: self.field.getValue('content'), - tenant: self.tenant, + namespaceId: self.tenant, + ...(extInfo.type ? { type: extInfo.type } : {}), + ...(extInfo.config_tags ? { config_tags: extInfo.config_tags } : {}), + ...(extInfo.effect ? { effect: extInfo.effect } : {}), + ...(extInfo.c_desc ? { desc: extInfo.c_desc } : {}), + ...(extInfo.c_use ? { use: extInfo.c_use } : {}), + ...(extInfo.c_schema ? { schema: extInfo.c_schema } : {}), }; - let url = 'v1/cs/configs'; + let url = 'v3/console/cs/config'; if (self.opType.trim() === 'I') { - url = `v1/cs/configs?dataId=${self.dataId}&group=${self.group}`; + url = `v3/console/cs/config?dataId=${self.dataId}&groupName=${self.group}`; postData = {}; } @@ -158,8 +167,8 @@ class ConfigRollback extends React.Component { contentType: 'application/x-www-form-urlencoded', url, data: postData, - success(data) { - if (data === true) { + success(res) { + if (res.data === true) { Dialog.alert({ content: locale.rollbackSuccessful }); } }, diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigSync/ConfigSync.js b/console-ui/src/pages/ConfigurationManagement/ConfigSync/ConfigSync.js index 4ebc89b7ab2..33842922d14 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigSync/ConfigSync.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigSync/ConfigSync.js @@ -79,7 +79,7 @@ class ConfigSync extends React.Component { request({ url: '/diamond-ops/env/domain', success(data) { - if (data.code === 200) { + if (data.code === 0) { const { envGroups } = data.data; self.setState({ @@ -93,7 +93,7 @@ class ConfigSync extends React.Component { getDataDetail() { const self = this; const { locale = {} } = this.props; - this.tenant = getParams('namespace') || ''; + this.tenant = getParams('namespace') || 'public'; this.serverId = getParams('serverId') || 'center'; let url = `/diamond-ops/configList/detail/serverId/${this.serverId}/dataId/${this.dataId}/group/${this.group}/tenant/${this.tenant}?id=`; if (this.tenant === 'global' || !this.tenant) { @@ -105,7 +105,7 @@ class ConfigSync extends React.Component { self.openLoading(); }, success(result) { - if (result.code === 200) { + if (result.code === 0) { const { data = {} } = result; self.field.setValue('dataId', data.dataId); @@ -177,7 +177,7 @@ class ConfigSync extends React.Component { _payload.content = ''; _payload.dataId = payload.dataId; _payload.group = payload.group; - _payload.isok = res.code === 200; + _payload.isok = res.code === 0; if (!_payload.isok) { _payload.isok = false; _payload.message = res.message; diff --git a/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js b/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js index 9b1dc2bbdd3..4b6285ea9aa 100644 --- a/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js +++ b/console-ui/src/pages/ConfigurationManagement/ConfigurationManagement/ConfigurationManagement.js @@ -41,39 +41,35 @@ import RegionGroup from 'components/RegionGroup'; import ShowCodeing from 'components/ShowCodeing'; import DeleteDialog from 'components/DeleteDialog'; import DashboardCard from './DashboardCard'; -import {getParams, request, setParams} from '@/globalLib'; -import {goLogin} from '../../../globalLib'; -import {connect} from 'react-redux'; -import {getConfigs, getConfigsV2} from '../../../reducers/configuration'; +import { getParams, request, setParams } from '@/globalLib'; +import { goLogin } from '../../../globalLib'; +import { connect } from 'react-redux'; +import { getConfigs, getConfigsV2 } from '../../../reducers/configuration'; import PageTitle from '../../../components/PageTitle'; import QueryResult from '../../../components/QueryResult'; import './index.scss'; -import { - GLOBAL_PAGE_SIZE_LIST, - LANGUAGE_KEY, - LOGINPAGE_ENABLED -} from '../../../constants'; +import { GLOBAL_PAGE_SIZE_LIST, LANGUAGE_KEY, LOGINPAGE_ENABLED } from '../../../constants'; import TotalRender from '../../../components/Page/TotalRender'; -const {Item} = MenuButton; -const {Panel} = Collapse; +const { Item } = MenuButton; +const { Panel } = Collapse; const configsTableSelected = new Map(); const typeMapping = [ - {value: 'text', label: 'TEXT'}, - {value: 'json', label: 'JSON'}, - {value: 'xml', label: 'XML'}, - {value: 'yaml', label: 'YAML'}, - {value: 'html', label: 'HTML'}, - {value: 'properties', label: 'Properties'}, - {value: 'toml', label: 'TOML'}, + { value: 'text', label: 'TEXT' }, + { value: 'json', label: 'JSON' }, + { value: 'xml', label: 'XML' }, + { value: 'yaml', label: 'YAML' }, + { value: 'html', label: 'HTML' }, + { value: 'properties', label: 'Properties' }, + { value: 'toml', label: 'TOML' }, ]; @connect( - state => ({ - configurations: state.configuration.configurations, - }), - {getConfigs, getConfigsV2} + state => ({ + configurations: state.configuration.configurations, + }), + { getConfigs, getConfigsV2 } ) @ConfigProvider.config class ConfigurationManagement extends React.Component { @@ -114,10 +110,8 @@ class ConfigurationManagement extends React.Component { group: this.group, appName: this.appName, config_detail: getParams('configDetail') || '', - config_tags: getParams('configTags') ? getParams('configTags').split(',') - : [], - types: getParams('types') ? getParams('types').split( - ',') : [], + config_tags: getParams('configTags') ? getParams('configTags').split(',') : [], + types: getParams('types') ? getParams('types').split(',') : [], tagLst: getParams('tagList') ? getParams('tagList').split(',') : [], selectValue: [], loading: false, @@ -152,7 +146,7 @@ class ConfigurationManagement extends React.Component { } componentDidMount() { - const {locale = {}} = this.props; + const { locale = {} } = this.props; // this.getGroup(); this.setIsCn(); if (window._getLink && window._getLink('isCn') === 'true') { @@ -164,20 +158,19 @@ class ConfigurationManagement extends React.Component { width: '60%', }, content: ( -
-
- {locale.ad} - {/* eslint-disable */} - - {locale.questionnaire2} - -
-
- {locale.noLongerDisplay4} - -
+
+
+ {locale.ad} + {/* eslint-disable */} + + {locale.questionnaire2} + +
+
+ {locale.noLongerDisplay4} +
+
), }); } @@ -186,7 +179,7 @@ class ConfigurationManagement extends React.Component { } setIsCn() { - this.setState({isCn: localStorage.getItem(LANGUAGE_KEY) === 'zh-CN'}); + this.setState({ isCn: localStorage.getItem(LANGUAGE_KEY) === 'zh-CN' }); } toggleShowQuestionnaire(value) { @@ -208,23 +201,21 @@ class ConfigurationManagement extends React.Component { navTo(url, record) { this.serverId = getParams('serverId') || ''; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 switch (url) { case '/historyRollback': url = `${url}?historyServerId=${this.serverId || ''}&historyDataId=${ - record.dataId + record.dataId }&historyGroup=${record.group}&namespace=${this.tenant}`; break; case '/listeningToQuery': - url = `${url}?listeningServerId=${this.serverId - || ''}&listeningDataId=${ - record.dataId + url = `${url}?listeningServerId=${this.serverId || ''}&listeningDataId=${ + record.dataId }&listeningGroup=${record.group}&namespace=${this.tenant}`; break; case '/pushTrajectory': - url = `${url}?serverId=${this.serverId - || ''}&dataId=${record.dataId}&group=${ - record.group + url = `${url}?serverId=${this.serverId || ''}&dataId=${record.dataId}&group=${ + record.group }&namespace=${this.tenant}`; break; default: @@ -259,9 +250,9 @@ class ConfigurationManagement extends React.Component { } this.getData(); configsTableSelected.clear(); - const {rowSelection} = this.state; + const { rowSelection } = this.state; rowSelection.selectedRowKeys = []; - this.setState({rowSelection}); + this.setState({ rowSelection }); } changeParamsBySearchType(params) { @@ -284,8 +275,8 @@ class ConfigurationManagement extends React.Component { if (this.state.loading) { return; } - const {locale = {}, configurations = {}} = this.props; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + const { locale = {}, configurations = {} } = this.props; + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 this.serverId = getParams('serverId') || ''; const prePageNo = getParams('pageNo'); const prePageSize = getParams('pageSize'); @@ -293,54 +284,54 @@ class ConfigurationManagement extends React.Component { this.pageSize = prePageSize ? prePageSize : this.state.pageSize; const params = { dataId: this.dataId, - group: this.group, + groupName: this.group, appName: this.appName, - config_tags: this.state.config_tags.join(','), + configTags: this.state.config_tags.join(','), pageNo: prePageNo ? prePageNo : pageNo, pageSize: prePageSize ? prePageSize : this.state.pageSize, - tenant: this.tenant, - types: this.state.types.join(','), + namespaceId: this.tenant, + type: this.state.types.join(','), }; setParams('pageSize', null); setParams('pageNo', null); this.changeParamsBySearchType(params); - this.setState({loading: true}); + this.setState({ loading: true }); let props = null; if (this.state.config_detail && this.state.config_detail !== '') { if (this.state.defaultFuzzySearch) { - params.config_detail = '*' + this.state.config_detail + '*'; + params.configDetail = '*' + this.state.config_detail + '*'; } else { - params.config_detail = this.state.config_detail; + params.configDetail = this.state.config_detail; } props = this.props.getConfigsV2(params); } else { props = this.props.getConfigs(params); } props - .then(() => + .then(() => this.setState({ loading: false, selectedRecord: [], selectedKeys: [], tenant: this.tenant, }) - ) - .catch(res => { - configurations.pageItems = []; - configurations.totalCount = 0; - this.setState({ - loading: false, - }); - if (res && [401, 403].includes(res.status)) { - Dialog.alert({ - title: locale.authFail, - content: locale.getNamespace403.replace( + ) + .catch(res => { + configurations.pageItems = []; + configurations.totalCount = 0; + this.setState({ + loading: false, + }); + if (res && [401, 403].includes(res.status) && localStorage.token) { + Dialog.alert({ + title: locale.authFail, + content: locale.getNamespace403.replace( '${namespaceName}', this.state.nownamespace_name - ), - }); - } - }); + ), + }); + } + }); } chooseNav(record, key) { @@ -360,32 +351,36 @@ class ConfigurationManagement extends React.Component { } removeConfig(record) { - const {locale = {}} = this.props; + const { locale = {} } = this.props; const self = this; Dialog.confirm({ title: locale.removeConfiguration, content: ( -
-

{locale.sureDelete}

-

- Data ID - {record.dataId} -

-

- Group - {record.group} -

-

- +

{locale.sureDelete}

+

+ Data ID + {record.dataId} +

+

+ Group + {record.group} +

+

+ {locale.environment} - {self.serverId || ''} -

-
+ marginRight: 5, + }} + > + {locale.environment} + + {self.serverId || ''} +

+
), onOk: () => { - const url = `v1/cs/configs?dataId=${record.dataId}&group=${record.group}`; + const url = `v3/console/cs/config?dataId=${record.dataId}&groupName=${record.group}`; request({ url, type: 'delete', @@ -396,7 +391,7 @@ class ConfigurationManagement extends React.Component { _payload.content = ''; _payload.dataId = record.dataId; _payload.group = record.group; - if (res === true) { + if (res.data === true) { _payload.isok = true; } else { _payload.isok = false; @@ -415,61 +410,56 @@ class ConfigurationManagement extends React.Component { } renderCol(value, index, record) { - const {locale = {}} = this.props; + const { locale = {} } = this.props; return ( -
- - {locale.details} - - | - - {locale.sampleCode} - - | - - {locale.edit} - - | - - {locale.deleteAction} - - | +
+ + {locale.details} + + | + + {locale.sampleCode} + + | + + {locale.edit} + + | + + {locale.deleteAction} + + | - - - - } - triggerType={'click'} - > - - {locale.version} - {locale.listenerQuery} - - -
+ + + + } + triggerType={'click'} + > + + {locale.version} + {locale.listenerQuery} + + +
); } changePage(value, e) { this.setState( - { - isPageEnter: e && e.keyCode && e.keyCode === 13, - currentPage: value, - }, - () => this.getData(value, false) + { + isPageEnter: e && e.keyCode && e.keyCode === 13, + currentPage: value, + }, + () => this.getData(value, false) ); } onChangeSort(dataIndex, order) { - const {configurations = {}} = this.props; - configurations.pageItems.sort(function (a, b) { + const { configurations = {} } = this.props; + configurations.pageItems.sort(function(a, b) { if (order === 'asc') { return (a[dataIndex] + '').localeCompare(b[dataIndex] + ''); } @@ -479,7 +469,7 @@ class ConfigurationManagement extends React.Component { } handlePageSizeChange(pageSize) { - this.setState({pageSize}, () => this.changePage(1)); + this.setState({ pageSize }, () => this.changePage(1)); } setConfigDetail(value) { @@ -534,12 +524,11 @@ class ConfigurationManagement extends React.Component { chooseEnv(value) { this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 this.props.history.push( - `/newconfig?serverId=${this.serverId - || ''}&namespace=${this.tenant}&edasAppName=${ - this.edasAppName - }&edasAppId=${this.edasAppId}&searchDataId=${this.dataId}&searchGroup=${this.group}` + `/newconfig?serverId=${this.serverId || ''}&namespace=${this.tenant}&edasAppName=${ + this.edasAppName + }&edasAppId=${this.edasAppId}&searchDataId=${this.dataId}&searchGroup=${this.group}` ); } @@ -553,30 +542,28 @@ class ConfigurationManagement extends React.Component { goDetail(record) { this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 // 点击详情到另一个页面, 返回时候要保留原来的搜索条件 比如: record.dataId为详情的, this.dataId为搜索条件的. this.props.history.push( - `/configdetail?serverId=${this.serverId - || ''}&dataId=${record.dataId}&group=${ - record.group - }&namespace=${this.tenant}&edasAppName=${this.edasAppName}&searchDataId=${ - this.dataId - }&searchGroup=${this.group}&pageSize=${this.pageSize}&pageNo=${this.pageNo}` + `/configdetail?serverId=${this.serverId || ''}&dataId=${record.dataId}&group=${ + record.group + }&namespace=${this.tenant}&edasAppName=${this.edasAppName}&searchDataId=${ + this.dataId + }&searchGroup=${this.group}&pageSize=${this.pageSize}&pageNo=${this.pageNo}` ); } goEditor(record) { this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 this.props.history.push( - `/configeditor?serverId=${this.serverId - || ''}&dataId=${record.dataId}&group=${ - record.group - }&namespace=${this.tenant}&edasAppName=${this.edasAppName}&edasAppId=${ - this.edasAppId - }&searchDataId=${this.dataId}&searchGroup=${this.group}&pageSize=${this.pageSize}&pageNo=${ - this.pageNo - }` + `/configeditor?serverId=${this.serverId || ''}&dataId=${record.dataId}&group=${ + record.group + }&namespace=${this.tenant}&edasAppName=${this.edasAppName}&edasAppId=${ + this.edasAppId + }&searchDataId=${this.dataId}&searchGroup=${this.group}&pageSize=${this.pageSize}&pageNo=${ + this.pageNo + }` ); } @@ -615,23 +602,21 @@ class ConfigurationManagement extends React.Component { openUri(url, params) { window.open( - [ - url, - Object.keys(params) + [ + url, + Object.keys(params) .map(key => `${key}=${params[key]}`) .join('&'), - ].join('?') + ].join('?') ); } exportData() { - const {group, appName, dataId, openUri} = this; - const {accessToken = '', username = ''} = JSON.parse( - localStorage.token || '{}'); - openUri('v1/cs/configs', { - export: 'true', - tenant: getParams('namespace'), - group, + const { group, appName, dataId, openUri } = this; + const { accessToken = '', username = '' } = JSON.parse(localStorage.token || '{}'); + openUri('v3/console/cs/config/export', { + namespaceId: getParams('namespace'), + groupName: group, appName, dataId, ids: '', @@ -641,13 +626,11 @@ class ConfigurationManagement extends React.Component { } exportDataNew() { - const {group, appName, dataId, openUri} = this; - const {accessToken = '', username = ''} = JSON.parse( - localStorage.token || '{}'); - openUri('v1/cs/configs', { - exportV2: 'true', - tenant: getParams('namespace'), - group, + const { group, appName, dataId, openUri } = this; + const { accessToken = '', username = '' } = JSON.parse(localStorage.token || '{}'); + openUri('v3/console/cs/config/export2', { + namespaceId: getParams('namespace'), + groupName: group, appName, dataId, ids: '', @@ -658,9 +641,8 @@ class ConfigurationManagement extends React.Component { exportSelectedData(newVersion) { const ids = []; - const {locale = {}} = this.props; - const {accessToken = '', username = ''} = JSON.parse( - localStorage.token || '{}'); + const { locale = {} } = this.props; + const { accessToken = '', username = '' } = JSON.parse(localStorage.token || '{}'); if (!configsTableSelected.size) { Dialog.alert({ title: locale.exportSelectedAlertTitle, @@ -670,20 +652,18 @@ class ConfigurationManagement extends React.Component { } configsTableSelected.forEach((value, key, map) => ids.push(key)); if (newVersion) { - this.openUri('v1/cs/configs', { - exportV2: 'true', - tenant: getParams('namespace'), - group: '', + this.openUri('v3/console/cs/config/export2', { + namespaceId: getParams('namespace'), + groupName: '', appName: '', ids: ids.join(','), accessToken, username, }); } else { - this.openUri('v1/cs/configs', { - export: 'true', - tenant: getParams('namespace'), - group: '', + this.openUri('v3/console/cs/config/export', { + namespaceId: getParams('namespace'), + groupName: '', appName: '', ids: ids.join(','), accessToken, @@ -693,7 +673,7 @@ class ConfigurationManagement extends React.Component { } multipleSelectionDeletion() { - const {locale = {}} = this.props; + const { locale = {} } = this.props; const self = this; if (configsTableSelected.size === 0) { Dialog.alert({ @@ -711,20 +691,19 @@ class ConfigurationManagement extends React.Component { Dialog.confirm({ title: locale.removeConfiguration, content: ( -
-

{locale.sureDelete}

- - - -
-
+
+

{locale.sureDelete}

+ + + +
+
), onOk: () => { const url = - `v1/cs/configs?delType=ids&ids=${Array.from( - configsTableSelected.keys()).join( - ',' - )}&tenant=` + self.state.nownamespace_id; + `v3/console/cs/config/batchDelete?&ids=${Array.from(configsTableSelected.keys()).join( + ',' + )}&namespaceId=` + self.state.nownamespace_id; request({ url, type: 'delete', @@ -739,7 +718,7 @@ class ConfigurationManagement extends React.Component { } cloneSelectedDataConfirm() { - const {locale = {}} = this.props; + const { locale = {} } = this.props; const self = this; self.field.setValue('sameConfigPolicy', 'ABORT'); self.field.setValue('cloneTargetSpace', undefined); @@ -751,13 +730,13 @@ class ConfigurationManagement extends React.Component { return; } request({ - url: 'v1/console/namespaces?namespaceId=', + url: 'v3/console/core/namespace/list?namespaceId=', beforeSend() { self.openLoading(); }, success(data) { self.closeLoading(); - if (!data || data.code !== 200 || !data.data) { + if (!data || data.code !== 0 || !data.data) { Dialog.alert({ title: locale.getNamespaceFailed, content: locale.getNamespaceFailed, @@ -767,10 +746,16 @@ class ConfigurationManagement extends React.Component { let namespaceSelectData = []; let namespaceSelecItemRender = item => { if (item.isCurrent) { - return {item.label}; + return ( + + {item.label} + + ); } else { return {item.label}; } @@ -799,192 +784,196 @@ class ConfigurationManagement extends React.Component { dataItem.dataId = value.dataId; dataItem.group = value.group; editableTableData.push(dataItem); - configsTableSelectedDeepCopyed.set(key, - JSON.parse(JSON.stringify(value))); + configsTableSelectedDeepCopyed.set(key, JSON.parse(JSON.stringify(value))); }); let editableTableOnBlur = (record, type, e) => { if (type === 1) { - configsTableSelectedDeepCopyed.get( - record.id).dataId = e.target.value; + configsTableSelectedDeepCopyed.get(record.id).dataId = e.target.value; } else { - configsTableSelectedDeepCopyed.get( - record.id).group = e.target.value; + configsTableSelectedDeepCopyed.get(record.id).group = e.target.value; } }; let renderEditableTableCellDataId = (value, index, record) => ( - + ); let renderEditableTableCellGroup = (value, index, record) => ( - + ); const cloneConfirm = Dialog.confirm({ title: locale.cloningConfiguration, footer: false, content: ( - <> -
- +
+ {locale.source} - {self.state.nownamespace_name} |{' '} - {self.state.nownamespace_id} -
-
- + {locale.source} + + {self.state.nownamespace_name} |{' '} + {self.state.nownamespace_id} +
+
+ {locale.configurationNumber} - {configsTableSelected.size} - {locale.selectedEntry} -
-
- + {locale.configurationNumber} + + {configsTableSelected.size} + {locale.selectedEntry} +
+
+ {'*'} - + {'*'} + + {locale.target} - { + if (value) { + document.getElementById('cloneTargetSpaceSelectErr').style.display = 'none'; + self.field.setValue('cloneTargetSpace', value); + } + }} + /> +
+ {locale.selectNamespace} -
-
- +
+ {locale.samePreparation}: - { + if (value) { + self.field.setValue('sameConfigPolicy', value); + } + }} + /> +
+
+ -
-
- + }, + complete() { + self.closeLoading(); + }, + }); + }} + data-spm-click={'gostr=/aliyun;locaid=doClone'} + > + {locale.startCloning} + +
+
+ {locale.cloneEditableTitle} -
- - - -
- +
+ + + +
+ ), }); }, @@ -1004,83 +993,80 @@ class ConfigurationManagement extends React.Component { processImportAndCloneResult(ret, locale, confirm, isImport) { const resultCode = ret.code; - if (resultCode === 200) { + if (resultCode === 0) { confirm.hide(); let failCount = ret.data.failData ? ret.data.failData.length : 0; let skipCount = ret.data.skipData ? ret.data.skipData.length : 0; - let unrecognizedCount = ret.data.unrecognizedCount - ? ret.data.unrecognizedCount : 0; + let unrecognizedCount = ret.data.unrecognizedCount ? ret.data.unrecognizedCount : 0; if (failCount > 0) { Dialog.alert({ title: isImport ? locale.importAbort : locale.cloneAbort, content: ( -
-

- {locale.conflictConfig}:{ret.data.failData[0].group}/{ret.data.failData[0].dataId} -

-
-
- {locale.failureEntries}: {failCount} -
- - - -
-
-
-
- {locale.unprocessedEntries}: {skipCount} -
- - - -
-
-
-
- {locale.unrecognizedEntries}: {unrecognizedCount} -
- - -
-
+
+

+ {locale.conflictConfig}:{ret.data.failData[0].group}/{ret.data.failData[0].dataId} +

+
+
+ {locale.failureEntries}: {failCount} +
+ + + +
+
+
+ {locale.unprocessedEntries}: {skipCount} +
+ + + +
+
+
+
+ {locale.unrecognizedEntries}: {unrecognizedCount} +
+ + +
+
+
), }); } else if (skipCount > 0 || unrecognizedCount > 0) { - let message = `${isImport ? locale.importSuccEntries - : locale.cloneSuccEntries}${ - ret.data.succCount + let message = `${isImport ? locale.importSuccEntries : locale.cloneSuccEntries}${ + ret.data.succCount }`; Dialog.alert({ title: isImport ? locale.importSucc : locale.cloneSucc, content: ( -
-
{message}
-
-
- {locale.skippedEntries}: {skipCount} -
- - - -
-
-
-
- {locale.unrecognizedEntries}: {unrecognizedCount} -
- - -
-
+
+
{message}
+
+
+ {locale.skippedEntries}: {skipCount} +
+ + + +
+
+
+ {locale.unrecognizedEntries}: {unrecognizedCount} +
+ + +
+
+
), }); } else { - let message = `${isImport ? locale.importSuccBegin - : locale.cloneSuccBegin}${ - ret.data.succCount + let message = `${isImport ? locale.importSuccBegin : locale.cloneSuccBegin}${ + ret.data.succCount }${isImport ? locale.importSuccEnd : locale.cloneSuccEnd}`; Message.success(message); } @@ -1093,8 +1079,7 @@ class ConfigurationManagement extends React.Component { if (resultCode === 100002) { alertContent = locale.metadataIllegal; } - if (resultCode === 100003 || resultCode === 100004 || resultCode - === 100005) { + if (resultCode === 100003 || resultCode === 100004 || resultCode === 100005) { alertContent = locale.importDataValidationError; } Dialog.alert({ @@ -1105,7 +1090,7 @@ class ConfigurationManagement extends React.Component { } importData() { - const {locale = {}} = this.props; + const { locale = {} } = this.props; const self = this; self.field.setValue('sameConfigPolicy', 'ABORT'); @@ -1125,14 +1110,13 @@ class ConfigurationManagement extends React.Component { return; } } - const {accessToken = '', username = ''} = token; + const { accessToken = '', username = '' } = token; const uploadProps = { accept: 'application/zip', - action: `v1/cs/configs?import=true&namespace=${getParams( - 'namespace' - )}&accessToken=${accessToken}&username=${username}&tenant=${getParams( - 'namespace')}`, - headers: Object.assign({}, {}, {accessToken}), + action: `v3/console/cs/config/import?namespaceId=${getParams( + 'namespace' + )}&accessToken=${accessToken}&username=${username}`, + headers: Object.assign({}, {}, { accessToken }), data: { policy: self.field.getValue('sameConfigPolicy'), }, @@ -1143,11 +1127,10 @@ class ConfigurationManagement extends React.Component { return options; }, onSuccess(ret) { - self.processImportAndCloneResult(ret.response, locale, importConfirm, - true); + self.processImportAndCloneResult(ret.response, locale, importConfirm, true); }, onError(err) { - const {data = {}, status} = err.response; + const { data = {}, status } = err.response; if ([401, 403].includes(status)) { Dialog.alert({ title: locale.importFail, @@ -1165,72 +1148,78 @@ class ConfigurationManagement extends React.Component { title: locale.import, footer: false, content: ( -
-
- +
+ {locale.targetNamespace}: - {this.state.nownamespace_name} |{' '} - {this.state.nownamespace_id} -
-
- + {locale.targetNamespace}: + + {this.state.nownamespace_name} |{' '} + {this.state.nownamespace_id} +
+
+ {locale.samePreparation}: - +
+
+ + {locale.importRemind}
+
+ + + +
+
), }); } configDataTableOnChange(ids, records) { - const {rowSelection} = this.state; + const { rowSelection } = this.state; rowSelection.selectedRowKeys = ids; - this.setState({rowSelection}); + this.setState({ rowSelection }); configsTableSelected.clear(); records.forEach((record, i) => { configsTableSelected.set(record.id, record); @@ -1238,330 +1227,319 @@ class ConfigurationManagement extends React.Component { } render() { - const {locale = {}, configurations = {}} = this.props; + const { locale = {}, configurations = {} } = this.props; return ( - <> - (this.batchHandle = ref)}/> -
+ <> + (this.batchHandle = ref)} /> +
+
+
+ + +
+
-
- - -
+ + + + + + { + this.dataId = dataId; + this.setState({ dataId }); + setParams('dataId', this.dataId); + }} + onPressEnter={() => this.selectAll()} + /> + -
- - - - - - { - this.dataId = dataId; - this.setState({dataId}); - setParams('dataId', this.dataId); - }} - onPressEnter={() => this.selectAll()} - /> - + + this.selectAll()} + hasClear + /> + - - this.selectAll()} - hasClear - /> - + + + - - - + + + + + + - - - - + - + {locale.import} + + +
+ + this.getData()} + /> + + + + + + + + +
+ + + + + + + {!this.inApp && ( + + )} + +
+ {configurations.totalCount > 0 && ( + <> +
+ {[ + { + warning: true, + text: locale.deleteAction, + locaid: 'configsDelete', + onClick: () => this.multipleSelectionDeletion(), + }, - + { + text: locale.clone, + locaid: 'configsDelete', + onClick: () => this.cloneSelectedDataConfirm(), + }, + ].map((item, index) => ( - -
- - this.getData()} - /> - - - - - - - - -
- - - - - - - {!this.inApp && ( - - )} - -
- {configurations.totalCount > 0 && ( - <> -
- {[ - { - warning: true, - text: locale.deleteAction, - locaid: 'configsDelete', - onClick: () => this.multipleSelectionDeletion(), - }, - - { - text: locale.clone, - locaid: 'configsDelete', - onClick: () => this.cloneSelectedDataConfirm(), - }, - ].map((item, index) => ( - - ))} - - {[ - { - text: locale.export, - locaid: 'exportData', - onClick: () => this.exportData(this), - }, - { - text: locale.newExport, - locaid: 'exportDataNew', - onClick: () => this.exportDataNew(this), - }, - { - text: locale.exportSelected, - locaid: 'configsExport', - onClick: () => this.exportSelectedData(false), - }, - { - text: locale.newExportSelected, - locaid: 'configsExport', - onClick: () => this.exportSelectedData(true), - }, - ].map((item, index) => ( - - {item.text} - - ))} - -
- this.handlePageSizeChange(val)} - current={configurations.pageNumber} - total={configurations.totalCount} - totalRender={total => } - pageSize={this.state.pageSize} - onChange={this.changePage.bind(this)} - /> - - )} - - -
- {this.state.hasdash && ( -
- {this.state.contentList.map((v, i) => ( - ))} + + {[ + { + text: locale.export, + locaid: 'exportData', + onClick: () => this.exportData(this), + }, + { + text: locale.newExport, + locaid: 'exportDataNew', + onClick: () => this.exportDataNew(this), + }, + { + text: locale.exportSelected, + locaid: 'configsExport', + onClick: () => this.exportSelectedData(false), + }, + { + text: locale.newExportSelected, + locaid: 'configsExport', + onClick: () => this.exportSelectedData(true), + }, + ].map((item, index) => ( + + {item.text} + + ))} +
+ this.handlePageSizeChange(val)} + current={configurations.pageNumber} + total={configurations.totalCount} + totalRender={total => } + pageSize={this.state.pageSize} + onChange={this.changePage.bind(this)} + /> + )} + +
- + {this.state.hasdash && ( +
+ {this.state.contentList.map((v, i) => ( + + ))} +
+ )} +
+ ); } } diff --git a/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js b/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js index 19b0cecb763..6722c9fd4f1 100644 --- a/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js +++ b/console-ui/src/pages/ConfigurationManagement/HistoryDetail/HistoryDetail.js @@ -34,6 +34,8 @@ class HistoryDetail extends React.Component { super(props); this.state = { showmore: false, + currentPublishType: '', + grayRule: '', }; this.edasAppName = getParams('edasAppName'); this.edasAppId = getParams('edasAppId'); @@ -43,7 +45,7 @@ class HistoryDetail extends React.Component { this.group = getParams('group') || 'DEFAULT_GROUP'; this.serverId = getParams('serverId') || 'center'; this.nid = getParams('nid') || '123509854'; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 // this.params = window.location.hash.split('?')[1]||''; } @@ -61,10 +63,12 @@ class HistoryDetail extends React.Component { const { locale = {} } = this.props; const self = this; request({ - url: `v1/cs/history?dataId=${this.dataId}&group=${this.group}&nid=${this.nid}`, + url: `v3/console/cs/history?dataId=${this.dataId}&groupName=${this.group}&nid=${this.nid}`, success(result) { if (result != null) { - const data = result; + const data = result.data; + const extInfo = data.extInfo ? JSON.parse(data.extInfo) : {}; + const grayRule = extInfo.gray_rule ? JSON.parse(extInfo.gray_rule) : {}; self.field.setValue('dataId', data.dataId); self.field.setValue('content', data.content); self.field.setValue('appName', self.inApp ? self.edasAppName : data.appName); @@ -74,6 +78,12 @@ class HistoryDetail extends React.Component { self.field.setValue('opType', data.opType.trim()); self.field.setValue('group', data.group); self.field.setValue('md5', data.md5); + self.setState({ + currentPublishType: data.publishType, + ...(data.publishType === 'gray' && { + grayRule: grayRule.expr || '', + }), + }); } }, }); @@ -100,6 +110,7 @@ class HistoryDetail extends React.Component { render() { const { locale = {} } = this.props; const { init } = this.field; + const { currentPublishType, grayRule } = this.state; const formItemLayout = { labelCol: { fixedSpan: 6, @@ -132,6 +143,20 @@ class HistoryDetail extends React.Component {
+ + + + {currentPublishType === 'gray' && ( + <> + + + + + )} diff --git a/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js b/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js index 9d425d71365..28f3d919f6f 100644 --- a/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js +++ b/console-ui/src/pages/ConfigurationManagement/HistoryRollback/HistoryRollback.js @@ -120,9 +120,10 @@ class HistoryRollback extends React.Component { beforeSend() { self.openLoading(); }, - url: `v1/cs/history?search=accurate&dataId=${this.state.dataId}&group=${this.state.group}&&pageNo=${pageNo}&pageSize=${this.state.pageSize}`, - success(data) { - if (data != null) { + url: `v3/console/cs/history/list?dataId=${this.state.dataId}&groupName=${this.state.group}&pageNo=${pageNo}&pageSize=${this.state.pageSize}`, + success(res) { + if (res != null) { + const data = res.data; self.setState({ dataSource: data.pageItems || [], total: data.totalCount, @@ -138,13 +139,21 @@ class HistoryRollback extends React.Component { renderCol(value, index, record) { const { locale = {} } = this.props; + const isBeta = record.publishType === 'gray'; return (
{locale.details} | - + {locale.rollback} | @@ -208,7 +217,7 @@ class HistoryRollback extends React.Component { goDetail(record) { this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 this.props.history.push( `/historyDetail?serverId=${this.serverId || ''}&dataId=${record.dataId}&group=${ record.group @@ -217,7 +226,7 @@ class HistoryRollback extends React.Component { } goCompare(record) { - let tenant = getParams('namespace') || ''; + let tenant = getParams('namespace') || 'public'; let serverId = getParams('serverId') || 'center'; this.getConfig(-1, tenant, serverId, record.dataId, record.group).then(lasted => { this.getHistoryConfig(record.id, record.dataId, record.group).then(selected => { @@ -239,9 +248,9 @@ class HistoryRollback extends React.Component { return new Promise((resolve, reject) => { const { locale = {} } = this.props; const self = this; - this.tenant = tenant; + this.namespaceId = tenant; this.serverId = tenant; - const url = `v1/cs/configs?show=all&dataId=${dataId}&group=${group}`; + const url = `v3/console/cs/config?dataId=${dataId}&groupName=${group}`; request({ url, beforeSend() { @@ -249,7 +258,7 @@ class HistoryRollback extends React.Component { }, success(result) { if (result != null) { - resolve(result); + resolve(result.data); } }, complete() { @@ -271,10 +280,10 @@ class HistoryRollback extends React.Component { const { locale = {} } = this.props; const self = this; request({ - url: `v1/cs/history?dataId=${dataId}&group=${group}&nid=${nid}`, + url: `v3/console/cs/history?dataId=${dataId}&groupName=${group}&nid=${nid}`, success(result) { if (result != null) { - resolve(result); + resolve(result.data); } }, }); @@ -283,7 +292,7 @@ class HistoryRollback extends React.Component { goRollBack(record) { this.serverId = getParams('serverId') || 'center'; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 this.props.history.push( `/configRollback?serverId=${this.serverId || ''}&dataId=${record.dataId}&group=${ record.group @@ -293,12 +302,13 @@ class HistoryRollback extends React.Component { getConfigList() { const { locale = {} } = this.props; - this.tenant = getParams('namespace') || ''; // 为当前实例保存tenant参数 + this.tenant = getParams('namespace') || 'public'; // 为当前实例保存tenant参数 const self = this; request({ - url: `v1/cs/history/configs?tenant=${this.tenant}`, - success(result) { - if (result != null) { + url: `v3/console/cs/history/configs?namespaceId=${this.tenant}`, + success(res) { + if (res != null) { + const result = res.data; const dataIdList = []; const groupList = []; for (let i = 0; i < result.length; i++) { @@ -436,6 +446,23 @@ class HistoryRollback extends React.Component { + { + if (value === 'formal') { + return locale.formal; + } else if (value === 'gray') { + const extInfo = record.extInfo ? JSON.parse(record.extInfo) : {}; + if (extInfo.gray_name) { + return `${locale.gray}(${extInfo.gray_name})`; + } else { + return locale.gray; + } + } + return value; + }} + /> { @@ -217,7 +217,7 @@ class NewConfig extends React.Component { } goList() { - this.tenant = getParams('namespace') || ''; + this.tenant = getParams('namespace') || 'public'; this.serverId = getParams('serverId') || ''; this.props.history.push( generateUrl('/configurationManagement', { @@ -297,22 +297,28 @@ class NewConfig extends React.Component { const { locale = {} } = this.props; const { addonBefore } = this.state; request({ - url: 'v1/cs/configs', + url: 'v3/console/cs/config', data: { - show: 'all', dataId: addonBefore + this.field.getValue('dataId'), - group: this.field.getValue('group'), - tenant: getParams('namespace') || '', + groupName: this.field.getValue('group'), + namespaceId: getParams('namespace') || 'public', }, success: res => { - // 返回成功 说明存在就不提交配置 - Message.error({ - content: locale.dataIdExists, - align: 'cc cc', - }); + // 检查 res.data 是否为 null,如果不是 null,说明存在就不提交配置 + if (res.data !== null) { + console.log('Data exists, not submitting config'); // 输出提示信息 + Message.error({ + content: locale.dataIdExists, + align: 'cc cc', + }); + } else { + console.log('Data does not exist, proceeding to publish config'); // 输出提示信息 + // 如果 res.data 为 null,表示没有数据,可以继续处理 + this._publishConfig(content); + } }, error: err => { - // 后端接口很不规范 响应为空 说明没有数据 就可以新增 + // 后端接口很不规范,响应为空,说明没有数据,可以新增 const { status } = err || {}; if (status === 403) { Dialog.alert({ @@ -329,19 +335,19 @@ class NewConfig extends React.Component { const self = this; const { locale = {} } = this.props; let { addonBefore, config_tags, configType } = this.state; - this.tenant = getParams('namespace') || ''; + this.tenant = getParams('namespace') || 'public'; const payload = { dataId: addonBefore + this.field.getValue('dataId'), - group: this.field.getValue('group'), + groupName: this.field.getValue('group'), content, desc: this.field.getValue('desc'), - config_tags: config_tags.join(), + configTags: config_tags.join(), type: configType, appName: this.inApp ? this.edasAppId : this.field.getValue('appName'), - tenant: this.tenant, + namespaceId: this.tenant, }; this.serverId = getParams('serverId') || 'center'; - const url = 'v1/cs/configs'; + const url = 'v3/console/cs/config'; request({ type: 'post', contentType: 'application/x-www-form-urlencoded', diff --git a/console-ui/src/pages/NameSpace/NameSpace.js b/console-ui/src/pages/NameSpace/NameSpace.js index e5e119138e5..34e284aef12 100644 --- a/console-ui/src/pages/NameSpace/NameSpace.js +++ b/console-ui/src/pages/NameSpace/NameSpace.js @@ -57,9 +57,9 @@ class NameSpace extends React.Component { request({ type: 'get', beforeSend() {}, - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace/list', success: res => { - if (res.code === 200) { + if (res.code === 0) { const data = res.data || []; window.namespaceList = data; @@ -113,11 +113,12 @@ class NameSpace extends React.Component { const { namespaceDetails, namespaceName, namespaceID, configuration, description } = locale; const { namespace } = record; // 获取ak,sk request({ - url: `v1/console/namespaces?show=all&namespaceId=${namespace}`, + url: `v3/console/core/namespace?namespaceId=${namespace}`, beforeSend: () => { this.openLoading(); }, success: res => { + res = res.data; if (res !== null) { Dialog.alert({ style: { width: '500px' }, @@ -182,11 +183,12 @@ class NameSpace extends React.Component { ), onOk: () => { - const url = `v1/console/namespaces?namespaceId=${record.namespace}`; + const url = `v3/console/core/namespace?namespaceId=${record.namespace}`; request({ url, type: 'delete', success: res => { + res = res.data; const _payload = {}; _payload.title = configurationManagement; if (res === true) { @@ -210,9 +212,9 @@ class NameSpace extends React.Component { refreshNameSpace() { request({ type: 'get', - url: 'v1/console/namespaces', + url: 'v3/console/core/namespace', success: res => { - if (res.code === 200) { + if (res.code === 0) { window.namespaceList = res.data; } }, diff --git a/console-ui/src/pages/Register/Register.jsx b/console-ui/src/pages/Register/Register.jsx index 27ba151185f..3fb3766113a 100644 --- a/console-ui/src/pages/Register/Register.jsx +++ b/console-ui/src/pages/Register/Register.jsx @@ -67,6 +67,7 @@ class Register extends React.Component { admin(data) .then(res => { + res = res.data; if (res.username && res.password) { localStorage.setItem('token', JSON.stringify(res)); Dialog.alert({ @@ -79,7 +80,7 @@ class Register extends React.Component { } else { Dialog.alert({ title: locale.Login.initPassword + locale.ListeningToQuery.failure, - content: res.data, + content: res, onOk: () => { const _LOGINPAGE_ENABLED = localStorage.getItem(LOGINPAGE_ENABLED); diff --git a/console-ui/src/pages/ServiceManagement/ServiceDetail/EditClusterDialog.js b/console-ui/src/pages/ServiceManagement/ServiceDetail/EditClusterDialog.js index be8d0e15253..cec9368ef86 100644 --- a/console-ui/src/pages/ServiceManagement/ServiceDetail/EditClusterDialog.js +++ b/console-ui/src/pages/ServiceManagement/ServiceDetail/EditClusterDialog.js @@ -69,7 +69,7 @@ class EditClusterDialog extends React.Component { } = this.state.editCluster; request({ method: 'PUT', - url: 'v1/ns/cluster', + url: 'v3/console/ns/service/cluster', data: { serviceName, clusterName: name, @@ -78,10 +78,10 @@ class EditClusterDialog extends React.Component { useInstancePort4Check: useIPPort4Check, healthChecker: JSON.stringify(healthChecker), }, - dataType: 'text', + dataType: 'json', beforeSend: () => openLoading(), success: res => { - if (res !== 'ok') { + if (res.data !== 'ok') { Message.error(res); return; } diff --git a/console-ui/src/pages/ServiceManagement/ServiceDetail/EditInstanceDialog.js b/console-ui/src/pages/ServiceManagement/ServiceDetail/EditInstanceDialog.js index 0ae62f5170c..b3ab5051f0a 100644 --- a/console-ui/src/pages/ServiceManagement/ServiceDetail/EditInstanceDialog.js +++ b/console-ui/src/pages/ServiceManagement/ServiceDetail/EditInstanceDialog.js @@ -69,7 +69,7 @@ class EditInstanceDialog extends React.Component { const { ip, port, ephemeral, weight, enabled, metadataText } = this.state.editInstance; request({ method: 'PUT', - url: 'v1/ns/instance', + url: 'v3/console/ns/instance', data: { serviceName, clusterName, @@ -81,10 +81,10 @@ class EditInstanceDialog extends React.Component { enabled, metadata: metadataText, }, - dataType: 'text', + dataType: 'json', beforeSend: () => openLoading(), success: res => { - if (res !== 'ok') { + if (res.data !== 'ok') { Message.error(res); return; } diff --git a/console-ui/src/pages/ServiceManagement/ServiceDetail/EditServiceDialog.js b/console-ui/src/pages/ServiceManagement/ServiceDetail/EditServiceDialog.js index f7ec01bc80c..51e24ba08ed 100644 --- a/console-ui/src/pages/ServiceManagement/ServiceDetail/EditServiceDialog.js +++ b/console-ui/src/pages/ServiceManagement/ServiceDetail/EditServiceDialog.js @@ -86,7 +86,7 @@ class EditServiceDialog extends React.Component { if (!this.validator({ name, protectThreshold })) return; request({ method: isCreate ? 'POST' : 'PUT', - url: 'v1/ns/service', + url: 'v3/console/ns/service', data: { serviceName: name, groupName: groupName || 'DEFAULT_GROUP', @@ -94,11 +94,11 @@ class EditServiceDialog extends React.Component { metadata: metadataText, selector: JSON.stringify(selector), }, - dataType: 'text', + dataType: 'json', beforeSend: () => this.setState({ loading: true }), success: res => { - if (res !== 'ok') { - Message.error(res); + if (res.code !== 0 || res.data !== 'ok') { + Message.error(res.message); return; } if (isCreate) { @@ -107,7 +107,7 @@ class EditServiceDialog extends React.Component { this.props.getServiceDetail(); } }, - error: res => Message.error(res.responseText || res.statusText), + error: res => Message.error(res.data.responseText || res.data.statusText), complete: () => this.setState({ loading: false }), }); this.hide(); @@ -136,9 +136,9 @@ class EditServiceDialog extends React.Component { getSelectorTypes() { request({ method: 'GET', - url: 'v1/ns/service/selector/types', + url: 'v3/console/ns/service/selector/types', success: response => { - if (response.code !== 200) { + if (response.code !== 0) { Message.error(response.message); return; } diff --git a/console-ui/src/pages/ServiceManagement/ServiceDetail/InstanceTable.js b/console-ui/src/pages/ServiceManagement/ServiceDetail/InstanceTable.js index 3bcbea26d65..7de2a629b95 100644 --- a/console-ui/src/pages/ServiceManagement/ServiceDetail/InstanceTable.js +++ b/console-ui/src/pages/ServiceManagement/ServiceDetail/InstanceTable.js @@ -69,7 +69,7 @@ class InstanceTable extends React.Component { if (!clusterName) return; const { pageSize, pageNum } = this.state; request({ - url: 'v1/ns/catalog/instances', + url: 'v3/console/ns/instance/list', data: { serviceName, clusterName, @@ -78,7 +78,14 @@ class InstanceTable extends React.Component { pageNo: pageNum, }, beforeSend: () => this.openLoading(), - success: instance => this.setState({ instance }), + success: ({ data }) => { + const instance = { + list: data.instances || [], + count: data.count || 0, + }; + this.setState({ instance }); + }, + error: e => Message.error(e.responseText || 'error'), complete: () => this.closeLoading(), }); } @@ -93,7 +100,7 @@ class InstanceTable extends React.Component { const { clusterName, serviceName, groupName } = this.props; request({ method: 'PUT', - url: 'v1/ns/instance', + url: 'v3/console/ns/instance', data: { serviceName, clusterName, @@ -105,9 +112,9 @@ class InstanceTable extends React.Component { enabled: !enabled, metadata: JSON.stringify(metadata), }, - dataType: 'text', + dataType: 'json', beforeSend: () => this.openLoading(), - success: () => { + success: ({ data }) => { const newVal = Object.assign({}, instance); newVal.list[index].enabled = !enabled; this.setState({ instance: newVal }); diff --git a/console-ui/src/pages/ServiceManagement/ServiceDetail/ServiceDetail.js b/console-ui/src/pages/ServiceManagement/ServiceDetail/ServiceDetail.js index 651b6b116c2..a1309c4af27 100644 --- a/console-ui/src/pages/ServiceManagement/ServiceDetail/ServiceDetail.js +++ b/console-ui/src/pages/ServiceManagement/ServiceDetail/ServiceDetail.js @@ -72,9 +72,17 @@ class ServiceDetail extends React.Component { getServiceDetail() { const { serviceName, groupName } = this.state; request({ - url: `v1/ns/catalog/service?serviceName=${serviceName}&groupName=${groupName}`, + url: `v3/console/ns/service?serviceName=${serviceName}&groupName=${groupName}`, beforeSend: () => this.openLoading(), - success: ({ clusters = [], service = {} }) => this.setState({ service, clusters }), + success: res => { + if (res.code === 0) { + // 确保 res.data 存在并且 clusters 是数组 + const { clusters = [], service = {} } = res.data || { clusters: [], service: {} }; + this.setState({ service, clusters: Array.isArray(clusters) ? clusters : [] }); + } else { + Message.error(res.message || '请求失败'); + } + }, error: e => Message.error(e.responseText || 'error'), complete: () => this.closeLoading(), }); diff --git a/console-ui/src/pages/ServiceManagement/ServiceList/ServiceList.js b/console-ui/src/pages/ServiceManagement/ServiceList/ServiceList.js index f30ceac8d3e..b34dacf7818 100644 --- a/console-ui/src/pages/ServiceManagement/ServiceList/ServiceList.js +++ b/console-ui/src/pages/ServiceManagement/ServiceList/ServiceList.js @@ -103,8 +103,8 @@ class ServiceList extends React.Component { }); this.openLoading(); request({ - url: `v1/ns/catalog/services?${parameter.join('&')}`, - success: ({ count = 0, serviceList = [] } = {}) => { + url: `v3/console/ns/service/list?${parameter.join('&')}`, + success: ({ data: { count = 0, serviceList = [] } = {} }) => { this.setState({ dataSource: serviceList, total: count, @@ -158,17 +158,20 @@ class ServiceList extends React.Component { onOk: () => { request({ method: 'DELETE', - url: `v1/ns/service?serviceName=${service.name}&groupName=${service.groupName}`, - dataType: 'text', + url: `v3/console/ns/service?serviceName=${service.name}&groupName=${service.groupName}`, + dataType: 'json', beforeSend: () => this.openLoading(), success: res => { - if (res !== 'ok') { - Message.error(res); - return; + if (res.code !== 0) { + Message.error(res.message || '删除服务失败'); + } else { + Message.success('服务删除成功'); + this.queryServiceList(); } - this.queryServiceList(); }, - error: res => Message.error(res.responseText || res.statusText), + error: res => { + Message.error(res.data?.responseText || res.statusText || '请求失败'); + }, complete: () => this.closeLoading(), }); }, diff --git a/console-ui/src/pages/ServiceManagement/SubscriberList/SubscriberList.js b/console-ui/src/pages/ServiceManagement/SubscriberList/SubscriberList.js index 705ad0b1ca9..5400c6a4e2c 100644 --- a/console-ui/src/pages/ServiceManagement/SubscriberList/SubscriberList.js +++ b/console-ui/src/pages/ServiceManagement/SubscriberList/SubscriberList.js @@ -63,7 +63,7 @@ class SubscriberList extends React.Component { serviceName: getParams('name') || '', groupName: getParams('groupName') || '', }, - nowNamespaceId: getParams('namespace') || '', + nowNamespaceId: getParams('namespace') || 'public', }; this.field = new Field(this); } diff --git a/console-ui/src/reducers/authority.js b/console-ui/src/reducers/authority.js index e52d30ba916..195762f55b1 100644 --- a/console-ui/src/reducers/authority.js +++ b/console-ui/src/reducers/authority.js @@ -40,7 +40,7 @@ const initialState = { }; const successMsg = res => { - if (res.code === 200) { + if (res.code === 0) { Message.success(res.message); } return res; @@ -51,35 +51,37 @@ const successMsg = res => { * @param {*} params */ const getUsers = params => dispatch => - request.get('v1/auth/users', { params }).then(data => dispatch({ type: USER_LIST, data })); + request + .get('v3/auth/user/list', { params }) + .then(data => dispatch({ type: USER_LIST, data: data.data })); /** * 创建用户 * @param {*} param0 */ const createUser = ([username, password]) => - request.post('v1/auth/users', { username, password }).then(res => successMsg(res)); + request.post('v3/auth/user', { username, password }).then(res => successMsg(res)); /** * 通过username 模糊匹配 * @param {*} param0 */ const searchUsers = username => - request.get('v1/auth/users/search', { params: { username } }).then(res => successMsg(res)); + request.get('v3/auth/user/search', { params: { username } }).then(res => successMsg(res.data)); /** * 删除用户 * @param {*} username */ const deleteUser = username => - request.delete('v1/auth/users', { params: { username } }).then(res => successMsg(res)); + request.delete('v3/auth/user', { params: { username } }).then(res => successMsg(res)); /** * 重置密码 * @param {*} param0 */ const passwordReset = ([username, newPassword]) => - request.put('v1/auth/users', { username, newPassword }); + request.put('v3/auth/user', { username, newPassword }); /** * 角色列表 @@ -87,28 +89,30 @@ const passwordReset = ([username, newPassword]) => */ const getRoles = params => dispatch => - request.get('v1/auth/roles', { params }).then(data => dispatch({ type: ROLE_LIST, data })); + request + .get('v3/auth/role/list', { params }) + .then(data => dispatch({ type: ROLE_LIST, data: data.data })); /** * 通过username 模糊匹配 * @param {*} param0 */ const searchRoles = role => - request.get('v1/auth/roles/search', { params: { role } }).then(res => successMsg(res)); + request.get('v3/auth/role/search', { params: { role } }).then(res => successMsg(res.data)); /** * 创建角色 * @param {*} param0 */ const createRole = ([role, username]) => - request.post('v1/auth/roles', { role, username }).then(res => successMsg(res)); + request.post('v3/auth/role', { role, username }).then(res => successMsg(res)); /** * 删除角色 * @param {*} param0 */ const deleteRole = role => - request.delete('v1/auth/roles', { params: role }).then(res => successMsg(res)); + request.delete('v3/auth/role', { params: role }).then(res => successMsg(res)); /** * 权限列表 @@ -116,22 +120,31 @@ const deleteRole = role => */ const getPermissions = params => dispatch => request - .get('v1/auth/permissions', { params }) - .then(data => dispatch({ type: PERMISSIONS_LIST, data })); + .get('v3/auth/permission/list', { params }) + .then(data => dispatch({ type: PERMISSIONS_LIST, data: data.data })); + +/** + * 添加权限前置校验 + * @param {*} param0 + */ +const checkPermission = ([role, resource, action]) => { + const params = { role, resource, action }; + return request.get('v3/auth/permission', { params }).then(res => res.data); +}; /** * 给角色添加权限 * @param {*} param0 */ const createPermission = ([role, resource, action]) => - request.post('v1/auth/permissions', { role, resource, action }).then(res => successMsg(res)); + request.post('v3/auth/permission', { role, resource, action }).then(res => successMsg(res)); /** * 删除权限 * @param {*} param0 */ const deletePermission = permission => - request.delete('v1/auth/permissions', { params: permission }).then(res => successMsg(res)); + request.delete('v3/auth/permission', { params: permission }).then(res => successMsg(res)); export default (state = initialState, action) => { switch (action.type) { @@ -157,6 +170,7 @@ export { createRole, deleteRole, getPermissions, + checkPermission, createPermission, deletePermission, }; diff --git a/console-ui/src/reducers/base.js b/console-ui/src/reducers/base.js index 38638fb09a8..a04d91f0247 100644 --- a/console-ui/src/reducers/base.js +++ b/console-ui/src/reducers/base.js @@ -34,22 +34,22 @@ const initialState = { * 用户登录 * @param {*} param0 */ -const login = user => request.post('v1/auth/users/login', user); -const admin = user => request.post('v1/auth/users/admin', user); +const login = user => request.post('v3/auth/user/login', user); +const admin = user => request.post('v3/auth/user/admin', user); /** * 单独在login处调用 获取提示信息 */ -const guide = () => request.get('v1/console/server/guide'); +const guide = () => request.get('v3/console/server/guide'); /** * 单独在login调用 判断是否可以登陆 */ -const state = () => request.get('v1/console/server/state'); +const state = () => request.get('v3/console/server/state'); const getState = () => dispatch => request - .get('v1/console/server/state') + .get('v3/console/server/state') .then(res => { localStorage.setItem(LOGINPAGE_ENABLED, res.login_page_enabled); dispatch({ @@ -84,7 +84,7 @@ const getState = () => dispatch => const getNotice = () => dispatch => request - .get('v1/console/server/announcement?language=' + localStorage.getItem(LANGUAGE_KEY)) + .get('v3/console/server/announcement?language=' + localStorage.getItem(LANGUAGE_KEY)) .then(res => { dispatch({ type: GET_NOTICE, @@ -104,7 +104,7 @@ const getNotice = () => dispatch => const getGuide = () => dispatch => request - .get('v1/console/server/guide') + .get('v3/console/server/guide') .then(res => { dispatch({ type: SERVER_GUIDE, diff --git a/console-ui/src/reducers/configuration.js b/console-ui/src/reducers/configuration.js index 5e5c000bbc7..b7f720c6f3b 100644 --- a/console-ui/src/reducers/configuration.js +++ b/console-ui/src/reducers/configuration.js @@ -23,13 +23,13 @@ const initialState = { const getConfigs = params => dispatch => request - .get('v1/cs/configs', { params }) - .then(data => dispatch({ type: GET_CONFIGURATION, data })); + .get('v3/console/cs/config/list', { params }) + .then(data => dispatch({ type: GET_CONFIGURATION, data: data.data })); const getConfigsV2 = params => dispatch => request - .get('v2/cs/config/searchDetail', { params }) - .then(data => dispatch({ type: GET_CONFIGURATION, data })); + .get('v3/console/cs/config/searchDetail', { params }) + .then(data => dispatch({ type: GET_CONFIGURATION, data: data.data })); export default (state = initialState, action) => { switch (action.type) { diff --git a/console-ui/src/reducers/namespace.js b/console-ui/src/reducers/namespace.js index d5cecd8026a..d84cd9dca54 100644 --- a/console-ui/src/reducers/namespace.js +++ b/console-ui/src/reducers/namespace.js @@ -22,11 +22,11 @@ const initialState = { }; const getNamespaces = params => dispatch => - request.get('v1/console/namespaces', { params }).then(response => { + request.get('v3/console/core/namespace/list', { params }).then(response => { const { code, data } = response; dispatch({ type: GET_NAMESPACES, - data: code === 200 ? data : [], + data: code === 0 ? data : [], }); }); diff --git a/console-ui/src/reducers/subscribers.js b/console-ui/src/reducers/subscribers.js index da5667d2d1f..fb36b1d7f31 100644 --- a/console-ui/src/reducers/subscribers.js +++ b/console-ui/src/reducers/subscribers.js @@ -22,10 +22,10 @@ const initialState = { }; const getSubscribers = params => dispatch => - request.get('v1/ns/service/subscribers', { params }).then(data => { + request.get('v3/console/ns/service/subscribers', { params }).then(data => { dispatch({ type: GET_SUBSCRIBERS, - data, + data: data.data, }); }); const removeSubscribers = () => dispatch => dispatch({ type: REMOVE_SUBSCRIBERS }); diff --git a/console-ui/src/utils/request.js b/console-ui/src/utils/request.js index 4b9c6196a0b..ba31c409d46 100644 --- a/console-ui/src/utils/request.js +++ b/console-ui/src/utils/request.js @@ -40,7 +40,7 @@ const request = () => { if (!params) { config.params = {}; } - if (!url.includes('auth/users/login') && localStorage.token) { + if (!url.includes('auth/user/login') && localStorage.token) { let token = {}; try { token = JSON.parse(localStorage.token); @@ -74,7 +74,7 @@ const request = () => { // Message.error(resultMessage); // return Promise.reject(new Error(resultMessage)); // } - if (response.config && response.config.url === 'v1/console/server/state') { + if (response.config && response.config.url === 'v3/console/server/state') { const { auth_admin_request = '' } = response.data; if (auth_admin_request && auth_admin_request === 'true') { goRegister(); diff --git a/console/pom.xml b/console/pom.xml index 9c3bc49eb58..de96a485fa0 100644 --- a/console/pom.xml +++ b/console/pom.xml @@ -38,7 +38,11 @@ ${project.groupId} nacos-naming - + + ${project.groupId} + nacos-lock + + com.alibaba.nacos nacos-default-plugin-all @@ -52,6 +56,10 @@ ${project.groupId} nacos-prometheus + + ${project.groupId} + nacos-k8s-sync + @@ -476,39 +484,5 @@ - - release-nacos - - nacos-server - - - maven-jar-plugin - - - - true - true - - - - - - org.springframework.boot - spring-boot-maven-plugin - - com.alibaba.nacos.Nacos - ZIP - - - - - repackage - - - - - - - diff --git a/console/src/main/java/com/alibaba/nacos/Nacos.java b/console/src/main/java/com/alibaba/nacos/Nacos.java index 203f51c939d..600bb6abfa0 100644 --- a/console/src/main/java/com/alibaba/nacos/Nacos.java +++ b/console/src/main/java/com/alibaba/nacos/Nacos.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2018 Alibaba Group Holding Ltd. + * 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. @@ -22,14 +22,12 @@ import org.springframework.boot.autoconfigure.AutoConfigurationExcludeFilter; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.TypeExcludeFilter; -import org.springframework.boot.web.servlet.ServletComponentScan; import org.springframework.context.annotation.ComponentScan; -import org.springframework.context.annotation.ComponentScan.Filter; import org.springframework.context.annotation.FilterType; import org.springframework.context.annotation.ImportRuntimeHints; /** - * Nacos starter. + * Nacos Full merged starter. *

* Use @SpringBootApplication and @ComponentScan at the same time, using CUSTOM type filter to control module enabled. *

@@ -39,10 +37,9 @@ @SpringBootApplication @ImportRuntimeHints(NacosRuntimeHints.class) @ComponentScan(basePackages = "com.alibaba.nacos", excludeFilters = { - @Filter(type = FilterType.CUSTOM, classes = {NacosTypeExcludeFilter.class}), - @Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}), - @Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})}) -@ServletComponentScan + @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {NacosTypeExcludeFilter.class}), + @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {TypeExcludeFilter.class}), + @ComponentScan.Filter(type = FilterType.CUSTOM, classes = {AutoConfigurationExcludeFilter.class})}) public class Nacos { public static void main(String[] args) { diff --git a/console/src/main/java/com/alibaba/nacos/console/NacosConsole.java b/console/src/main/java/com/alibaba/nacos/console/NacosConsole.java new file mode 100644 index 00000000000..ccae48010ba --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/NacosConsole.java @@ -0,0 +1,36 @@ +/* + * 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.console; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; +import org.springframework.context.annotation.PropertySource; + +/** + * Nacos console starter. + * + * @author xiweng.yy + */ +@SpringBootApplication(exclude = LdapAutoConfiguration.class) +@PropertySource("classpath:nacos-console.properties") +public class NacosConsole { + + public static void main(String[] args) { + SpringApplication.run(NacosConsole.class, args); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/NacosConsoleStartUp.java b/console/src/main/java/com/alibaba/nacos/console/NacosConsoleStartUp.java new file mode 100644 index 00000000000..f6a906ba5bb --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/NacosConsoleStartUp.java @@ -0,0 +1,45 @@ +/* + * 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.console; + +import com.alibaba.nacos.core.listener.startup.AbstractNacosStartUp; +import com.alibaba.nacos.core.listener.startup.NacosStartUp; +import org.slf4j.Logger; + +/** + * Nacos Server Web API start up phase. + * + * @author xiweng.yy + */ +public class NacosConsoleStartUp extends AbstractNacosStartUp { + + public NacosConsoleStartUp() { + super(NacosStartUp.CONSOLE_START_UP_PHASE); + } + + @Override + protected String getPhaseNameInStartingInfo() { + return "Nacos Console"; + } + + @Override + public void logStarted(Logger logger) { + long endTimestamp = System.currentTimeMillis(); + long startupCost = endTimestamp - getStartTimestamp(); + logger.info("Nacos Console started successfully in {} ms", startupCost); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/config/ConsoleConfig.java b/console/src/main/java/com/alibaba/nacos/console/config/ConsoleConfig.java deleted file mode 100644 index 5aac2bf29f9..00000000000 --- a/console/src/main/java/com/alibaba/nacos/console/config/ConsoleConfig.java +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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.console.config; - -import com.alibaba.nacos.console.filter.XssFilter; -import com.alibaba.nacos.core.code.ControllerMethodsCache; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.PropertySource; -import org.springframework.scheduling.annotation.EnableScheduling; -import org.springframework.stereotype.Component; -import org.springframework.web.cors.CorsConfiguration; -import org.springframework.web.cors.UrlBasedCorsConfigurationSource; -import org.springframework.web.filter.CorsFilter; - -import javax.annotation.PostConstruct; -import java.time.ZoneId; -import java.util.Set; - -/** - * Console config. - * - * @author yshen - * @author nkorange - * @since 1.2.0 - */ -@Component -@EnableScheduling -@PropertySource("/application.properties") -public class ConsoleConfig { - - @Autowired - private ControllerMethodsCache methodsCache; - - @Value("${nacos.console.ui.enabled:true}") - private boolean consoleUiEnabled; - - /** - * Init. - */ - @PostConstruct - public void init() { - final String graalEnv = "org.graalvm.nativeimage.imagecode"; - final boolean isGraalEnv = System.getProperty(graalEnv) != null; - if (isGraalEnv) { - initAotPlatform(); - } else { - initJavaPlatform(); - } - } - - @Bean - public CorsFilter corsFilter() { - CorsConfiguration config = new CorsConfiguration(); - config.setAllowCredentials(true); - config.addAllowedHeader("*"); - config.setMaxAge(18000L); - config.addAllowedMethod("*"); - config.addAllowedOriginPattern("*"); - UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); - source.registerCorsConfiguration("/**", config); - return new CorsFilter(source); - } - - @Bean - public XssFilter xssFilter() { - return new XssFilter(); - } - - @Bean - public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { - return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(ZoneId.systemDefault().toString()); - } - - public boolean isConsoleUiEnabled() { - return consoleUiEnabled; - } - - private void initJavaPlatform() { - methodsCache.initClassMethod("com.alibaba.nacos.core.controller"); - methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers"); - methodsCache.initClassMethod("com.alibaba.nacos.config.server.controller"); - methodsCache.initClassMethod("com.alibaba.nacos.console.controller"); - } - - private void initAotPlatform() { - final Set> classList = Set.of( - com.alibaba.nacos.core.controller.v2.NacosClusterControllerV2.class, - com.alibaba.nacos.core.controller.ServerLoaderController.class, - com.alibaba.nacos.core.controller.CoreOpsController.class, - com.alibaba.nacos.core.controller.NacosClusterController.class, - com.alibaba.nacos.core.controller.v2.CoreOpsV2Controller.class, - com.alibaba.nacos.naming.controllers.CatalogController.class, - com.alibaba.nacos.naming.controllers.OperatorController.class, - com.alibaba.nacos.naming.controllers.v2.ServiceControllerV2.class, - com.alibaba.nacos.naming.controllers.v2.CatalogControllerV2.class, - com.alibaba.nacos.naming.controllers.ClusterController.class, - com.alibaba.nacos.naming.controllers.HealthController.class, - com.alibaba.nacos.naming.controllers.v2.HealthControllerV2.class, - com.alibaba.nacos.naming.controllers.InstanceController.class, - com.alibaba.nacos.naming.controllers.v2.InstanceControllerV2.class, - com.alibaba.nacos.naming.controllers.v2.OperatorControllerV2.class, - com.alibaba.nacos.naming.controllers.v2.ClientInfoControllerV2.class, - com.alibaba.nacos.naming.controllers.ServiceController.class, - com.alibaba.nacos.config.server.controller.HistoryController.class, - com.alibaba.nacos.config.server.controller.v2.HistoryControllerV2.class, - com.alibaba.nacos.config.server.controller.CommunicationController.class, - com.alibaba.nacos.config.server.controller.ListenerController.class, - com.alibaba.nacos.config.server.controller.HealthController.class, - com.alibaba.nacos.config.server.controller.ConfigController.class, - com.alibaba.nacos.config.server.controller.CapacityController.class, - com.alibaba.nacos.config.server.controller.ClientMetricsController.class, - com.alibaba.nacos.config.server.controller.ConfigOpsController.class, - com.alibaba.nacos.config.server.controller.v2.ConfigControllerV2.class, - com.alibaba.nacos.console.controller.HealthController.class, - com.alibaba.nacos.console.controller.ServerStateController.class, - com.alibaba.nacos.console.controller.v2.HealthControllerV2.class, - com.alibaba.nacos.console.controller.NamespaceController.class, - com.alibaba.nacos.console.controller.v2.NamespaceControllerV2.class, - com.alibaba.nacos.plugin.auth.impl.controller.UserController.class, - com.alibaba.nacos.plugin.auth.impl.controller.PermissionController.class, - com.alibaba.nacos.plugin.auth.impl.controller.RoleController.class - ); - methodsCache.initClassMethod(classList); - } -} diff --git a/console/src/main/java/com/alibaba/nacos/console/config/ConsoleModuleStateBuilder.java b/console/src/main/java/com/alibaba/nacos/console/config/ConsoleModuleStateBuilder.java index 3ab7a6ae4f6..db29cef319d 100644 --- a/console/src/main/java/com/alibaba/nacos/console/config/ConsoleModuleStateBuilder.java +++ b/console/src/main/java/com/alibaba/nacos/console/config/ConsoleModuleStateBuilder.java @@ -16,9 +16,9 @@ package com.alibaba.nacos.console.config; +import com.alibaba.nacos.sys.env.EnvUtil; import com.alibaba.nacos.sys.module.ModuleState; import com.alibaba.nacos.sys.module.ModuleStateBuilder; -import com.alibaba.nacos.sys.utils.ApplicationUtils; /** * Console module state builder. @@ -35,8 +35,8 @@ public class ConsoleModuleStateBuilder implements ModuleStateBuilder { public ModuleState build() { ModuleState result = new ModuleState(CONSOLE_MODULE); try { - ConsoleConfig consoleConfig = ApplicationUtils.getBean(ConsoleConfig.class); - result.newState(CONSOLE_UI_ENABLED, consoleConfig.isConsoleUiEnabled()); + boolean consoleUiEnabled = EnvUtil.getProperty("nacos.console.ui.enabled", Boolean.class, true); + result.newState(CONSOLE_UI_ENABLED, consoleUiEnabled); } catch (Exception ignored) { } return result; diff --git a/console/src/main/java/com/alibaba/nacos/console/config/ConsoleWebConfig.java b/console/src/main/java/com/alibaba/nacos/console/config/ConsoleWebConfig.java new file mode 100644 index 00000000000..9db043bec46 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/config/ConsoleWebConfig.java @@ -0,0 +1,150 @@ +/* + * 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.console.config; + +import com.alibaba.nacos.console.filter.NacosConsoleAuthFilter; +import com.alibaba.nacos.console.filter.XssFilter; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.controller.compatibility.ApiCompatibilityFilter; +import com.alibaba.nacos.core.paramcheck.ParamCheckerFilter; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.jackson.Jackson2ObjectMapperBuilderCustomizer; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.stereotype.Component; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; +import org.springframework.web.filter.CorsFilter; + +import javax.annotation.PostConstruct; +import java.time.ZoneId; + +/** + * Console config. + * + * @author yshen + * @author nkorange + * @since 1.2.0 + */ +@Component +public class ConsoleWebConfig { + + private final ControllerMethodsCache methodsCache; + + @Value("${nacos.console.ui.enabled:true}") + private boolean consoleUiEnabled; + + @Value("${nacos.deployment.type:merged}") + private String type; + + public ConsoleWebConfig(ControllerMethodsCache methodsCache) { + this.methodsCache = methodsCache; + } + + /** + * Init. + */ + @PostConstruct + public void init() { + methodsCache.initClassMethod("com.alibaba.nacos.console.controller"); + } + + @Bean + public CorsFilter corsFilter() { + CorsConfiguration config = new CorsConfiguration(); + config.setAllowCredentials(true); + config.addAllowedHeader("*"); + config.setMaxAge(18000L); + config.addAllowedMethod("*"); + config.addAllowedOriginPattern("*"); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", config); + return new CorsFilter(source); + } + + @Bean + public XssFilter xssFilter() { + return new XssFilter(); + } + + @Bean + public FilterRegistrationBean authFilterRegistration(NacosConsoleAuthFilter authFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(authFilter); + registration.addUrlPatterns("/*"); + registration.setName("consoleAuthFilter"); + registration.setOrder(6); + return registration; + } + + @Bean + public NacosConsoleAuthFilter consoleAuthFilter(ControllerMethodsCache methodsCache) { + return new NacosConsoleAuthFilter(NacosConsoleAuthConfig.getInstance(), methodsCache); + } + + @Bean + public FilterRegistrationBean consoleParamCheckerFilterRegistration( + ParamCheckerFilter consoleParamCheckerFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(consoleParamCheckerFilter); + registration.addUrlPatterns("/*"); + registration.setName("consoleParamCheckerFilter"); + registration.setOrder(8); + return registration; + } + + @Bean + public ParamCheckerFilter consoleParamCheckerFilter(ControllerMethodsCache methodsCache) { + return new ParamCheckerFilter(methodsCache); + } + + @Bean + public ApiCompatibilityFilter consoleApiCompatibilityFilter(ControllerMethodsCache methodsCache) { + return new ApiCompatibilityFilter(methodsCache); + } + + @Bean + public FilterRegistrationBean consoleApiCompatibilityFilterRegistration( + ApiCompatibilityFilter consoleApiCompatibilityFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(consoleApiCompatibilityFilter); + registration.addUrlPatterns("/v1/*", "/v2/*"); + registration.setName("consoleApiCompatibilityFilter"); + registration.setOrder(5); + return registration; + } + + @Bean + public Jackson2ObjectMapperBuilderCustomizer jacksonObjectMapperCustomization() { + return jacksonObjectMapperBuilder -> jacksonObjectMapperBuilder.timeZone(ZoneId.systemDefault().toString()); + } + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests().requestMatchers("/**").permitAll().and().csrf().disable().build(); + } + + public boolean isConsoleUiEnabled() { + return consoleUiEnabled; + } + + public String getType() { + return type; + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleAuthConfig.java b/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleAuthConfig.java new file mode 100644 index 00000000000..b7b816d799c --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleAuthConfig.java @@ -0,0 +1,93 @@ +/* + * 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.console.config; + +import com.alibaba.nacos.auth.config.NacosAuthConfig; +import com.alibaba.nacos.core.config.AbstractDynamicConfig; +import com.alibaba.nacos.plugin.auth.constant.Constants; +import com.alibaba.nacos.sys.env.EnvUtil; + +/** + * Nacos console auth configurations. + * + * @author xiweng.yy + */ +public class NacosConsoleAuthConfig extends AbstractDynamicConfig implements NacosAuthConfig { + + private static final NacosConsoleAuthConfig INSTANCE = new NacosConsoleAuthConfig(); + + /** + * Whether console auth enabled. + */ + private boolean authEnabled; + + /** + * Which auth system is in use. + */ + private String nacosAuthSystemType; + + private NacosConsoleAuthConfig() { + super("NacosConsoleAuth"); + resetConfig(); + } + + public static NacosConsoleAuthConfig getInstance() { + return INSTANCE; + } + + @Override + public boolean isAuthEnabled() { + return authEnabled; + } + + @Override + public String getNacosAuthSystemType() { + return nacosAuthSystemType; + } + + @Override + public boolean isSupportServerIdentity() { + return false; + } + + @Override + public String getServerIdentityKey() { + return ""; + } + + @Override + public String getServerIdentityValue() { + return ""; + } + + @Override + protected void getConfigFromEnv() { + authEnabled = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_CONSOLE_ENABLED, Boolean.class, true); + nacosAuthSystemType = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_SYSTEM_TYPE, ""); + } + + @Override + protected String printConfig() { + return toString(); + } + + @Override + public String toString() { + return "NacosConsoleAuthConfig{" + "authEnabled=" + authEnabled + ", nacosAuthSystemType='" + + nacosAuthSystemType + '\'' + '}'; + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleAuthControllerConfig.java b/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleAuthControllerConfig.java new file mode 100644 index 00000000000..d58e47f8e5c --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleAuthControllerConfig.java @@ -0,0 +1,56 @@ +/* + * 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.console.config; + +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; +import com.alibaba.nacos.plugin.auth.impl.controller.v3.PermissionControllerV3; +import com.alibaba.nacos.plugin.auth.impl.controller.v3.RoleControllerV3; +import com.alibaba.nacos.plugin.auth.impl.controller.v3.UserControllerV3; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration of console auth controller. + * + * @author xiweng.yy + */ +@Configuration +@ConditionalOnProperty(value = "nacos.deployment.type", havingValue = "merged") +public class NacosConsoleAuthControllerConfig { + + @Bean + public UserControllerV3 consoleUserControllerV3(NacosUserDetailsServiceImpl userDetailsService, + NacosRoleServiceImpl roleService, AuthConfigs authConfigs, IAuthenticationManager iAuthenticationManager, + TokenManagerDelegate jwtTokenManager) { + return new UserControllerV3(userDetailsService, roleService, authConfigs, iAuthenticationManager, jwtTokenManager); + } + + @Bean + public RoleControllerV3 consoleRoleControllerV3(NacosRoleServiceImpl roleService) { + return new RoleControllerV3(roleService); + } + + @Bean + public PermissionControllerV3 permissionControllerV3(NacosRoleServiceImpl roleService) { + return new PermissionControllerV3(roleService); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleBeanPostProcessorConfiguration.java b/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleBeanPostProcessorConfiguration.java new file mode 100644 index 00000000000..83929b0faf2 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/config/NacosConsoleBeanPostProcessorConfiguration.java @@ -0,0 +1,48 @@ +/* + * 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.console.config; + +import com.alibaba.nacos.sys.env.Constants; +import com.alibaba.nacos.sys.env.NacosDuplicateConfigurationBeanPostProcessor; +import com.alibaba.nacos.sys.env.NacosDuplicateSpringBeanPostProcessor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Bean Post Processor Configuration for nacos console. + * + * @author xiweng.yy + */ +@Configuration +@ConditionalOnProperty(value = Constants.NACOS_DUPLICATE_BEAN_ENHANCEMENT_ENABLED, havingValue = "true", matchIfMissing = true) +public class NacosConsoleBeanPostProcessorConfiguration { + + @Bean + public InstantiationAwareBeanPostProcessor nacosDuplicateSpringBeanPostProcessor( + ConfigurableApplicationContext context) { + return new NacosDuplicateSpringBeanPostProcessor(context); + } + + @Bean + public InstantiationAwareBeanPostProcessor nacosDuplicateConfigurationBeanPostProcessor( + ConfigurableApplicationContext context) { + return new NacosDuplicateConfigurationBeanPostProcessor(context); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/HealthController.java b/console/src/main/java/com/alibaba/nacos/console/controller/HealthController.java index aa56618fb99..a57c4142ac9 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/HealthController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/HealthController.java @@ -19,7 +19,9 @@ import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; import com.alibaba.nacos.core.cluster.health.ModuleHealthCheckerHolder; import com.alibaba.nacos.core.cluster.health.ReadinessResult; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.GetMapping; @@ -45,6 +47,7 @@ public class HealthController { * Nacos is in broken states. */ @GetMapping("/liveness") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/health/liveness") public ResponseEntity liveness() { return ResponseEntity.ok().body("OK"); } @@ -56,6 +59,7 @@ public ResponseEntity liveness() { * ready. */ @GetMapping("/readiness") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/health/readiness") public ResponseEntity readiness(HttpServletRequest request) { ReadinessResult result = ModuleHealthCheckerHolder.getInstance().checkReadiness(); if (result.isSuccess()) { diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/NamespaceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/NamespaceController.java index a02ca0704ed..6dd59d6aaa4 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/NamespaceController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/NamespaceController.java @@ -22,11 +22,13 @@ import com.alibaba.nacos.common.model.RestResultUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; import com.alibaba.nacos.core.namespace.model.Namespace; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.service.NamespaceOperationService; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.DeleteMapping; @@ -69,6 +71,7 @@ public class NamespaceController { * @return namespace list */ @GetMapping + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/core/namespace/list") public RestResult> getNamespaces() { return RestResultUtils.success(namespaceOperationService.getNamespaceList()); } @@ -80,6 +83,7 @@ public RestResult> getNamespaces() { * @return namespace all info */ @GetMapping(params = "show=all") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/core/namespace") public Namespace getNamespace(@RequestParam("namespaceId") String namespaceId) throws NacosException { return namespaceOperationService.getNamespace(namespaceId); } @@ -93,6 +97,7 @@ public Namespace getNamespace(@RequestParam("namespaceId") String namespaceId) t */ @PostMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "POST ${contextPath:nacos}/v3/console/core/namespace") public Boolean createNamespace(@RequestParam("customNamespaceId") String namespaceId, @RequestParam("namespaceName") String namespaceName, @RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) { @@ -129,6 +134,7 @@ public Boolean createNamespace(@RequestParam("customNamespaceId") String namespa * @return true if exist, otherwise false */ @GetMapping(params = "checkNamespaceIdExist=true") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/core/namespace/exist") public Boolean checkNamespaceIdExist(@RequestParam("customNamespaceId") String namespaceId) { if (StringUtils.isBlank(namespaceId)) { return false; @@ -146,6 +152,7 @@ public Boolean checkNamespaceIdExist(@RequestParam("customNamespaceId") String n */ @PutMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "PUT ${contextPath:nacos}/v3/console/core/namespace") public Boolean editNamespace(@RequestParam("namespace") String namespace, @RequestParam("namespaceShowName") String namespaceShowName, @RequestParam(value = "namespaceDesc", required = false) String namespaceDesc) { @@ -164,6 +171,7 @@ public Boolean editNamespace(@RequestParam("namespace") String namespace, */ @DeleteMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "DELETE ${contextPath:nacos}/v3/console/core/namespace") public Boolean deleteNamespace(@RequestParam("namespaceId") String namespaceId) { return namespaceOperationService.removeNamespace(namespaceId); } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/ServerStateController.java b/console/src/main/java/com/alibaba/nacos/console/controller/ServerStateController.java index 5d54fcbdf59..b32d7aceb27 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/ServerStateController.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/ServerStateController.java @@ -19,7 +19,9 @@ import com.alibaba.nacos.common.model.RestResult; import com.alibaba.nacos.common.model.RestResultUtils; import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.sys.env.EnvUtil; import com.alibaba.nacos.sys.module.ModuleState; import com.alibaba.nacos.sys.module.ModuleStateHolder; @@ -58,6 +60,7 @@ public class ServerStateController { * @return state json. */ @GetMapping("/state") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/server/state") public ResponseEntity> serverState() { Map serverState = new HashMap<>(4); for (ModuleState each : ModuleStateHolder.getInstance().getAllModuleStates()) { @@ -67,6 +70,7 @@ public ResponseEntity> serverState() { } @GetMapping("/announcement") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/server/announcement") public RestResult getAnnouncement( @RequestParam(required = false, name = "language", defaultValue = "zh-CN") String language) { String file = ANNOUNCEMENT_FILE.substring(0, ANNOUNCEMENT_FILE.length() - 5) + "_" + language + ".conf"; @@ -82,6 +86,7 @@ public RestResult getAnnouncement( } @GetMapping("/guide") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/server/guide") public RestResult getConsoleUiGuide() { File guideFile = new File(EnvUtil.getConfPath(), GUIDE_FILE); String guideInformation = null; diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v2/HealthControllerV2.java b/console/src/main/java/com/alibaba/nacos/console/controller/v2/HealthControllerV2.java index e755093166c..54e0f3f0957 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v2/HealthControllerV2.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v2/HealthControllerV2.java @@ -20,7 +20,9 @@ import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; import com.alibaba.nacos.core.cluster.health.ModuleHealthCheckerHolder; import com.alibaba.nacos.core.cluster.health.ReadinessResult; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -44,6 +46,7 @@ public class HealthControllerV2 { * Nacos is in broken states. */ @GetMapping("/liveness") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/health/liveness") public Result liveness() { return Result.success("ok"); } @@ -55,6 +58,7 @@ public Result liveness() { * ready. */ @GetMapping("/readiness") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/health/readiness") public Result readiness(HttpServletRequest request) { ReadinessResult result = ModuleHealthCheckerHolder.getInstance().checkReadiness(); if (result.isSuccess()) { diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v2/NamespaceControllerV2.java b/console/src/main/java/com/alibaba/nacos/console/controller/v2/NamespaceControllerV2.java index 73d4c21cfb3..314f183dcfc 100644 --- a/console/src/main/java/com/alibaba/nacos/console/controller/v2/NamespaceControllerV2.java +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v2/NamespaceControllerV2.java @@ -24,12 +24,14 @@ import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.namespace.model.Namespace; import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.service.NamespaceOperationService; 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 com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; import org.springframework.http.HttpStatus; @@ -78,6 +80,7 @@ public NamespaceControllerV2(NamespaceOperationService namespaceOperationService * @return namespace list */ @GetMapping("/list") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/core/namespace/list") public Result> getNamespaceList() { return Result.success(namespaceOperationService.getNamespaceList()); } @@ -91,6 +94,7 @@ public Result> getNamespaceList() { @GetMapping() @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.READ, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/core/namespace") public Result getNamespace(@RequestParam("namespaceId") String namespaceId) throws NacosException { return Result.success(namespaceOperationService.getNamespace(namespaceId)); } @@ -104,6 +108,7 @@ public Result getNamespace(@RequestParam("namespaceId") String namesp @PostMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "POST ${contextPath:nacos}/v3/console/core/namespace") public Result createNamespace(NamespaceForm namespaceForm) throws NacosException { namespaceForm.validate(); @@ -147,6 +152,7 @@ public Result createNamespace(NamespaceForm namespaceForm) throws Nacos @PutMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "PUT ${contextPath:nacos}/v3/console/core/namespace") public Result editNamespace(NamespaceForm namespaceForm) throws NacosException { namespaceForm.validate(); // contains illegal chars @@ -168,6 +174,7 @@ public Result editNamespace(NamespaceForm namespaceForm) throws NacosEx @DeleteMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "DELETE ${contextPath:nacos}/v3/console/core/namespace") public Result deleteNamespace(@RequestParam("namespaceId") String namespaceId) { return Result.success(namespaceOperationService.removeNamespace(namespaceId)); } diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthController.java new file mode 100644 index 00000000000..a1c580b8979 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleHealthController.java @@ -0,0 +1,69 @@ +/* + * Copyright 1999-2024 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.console.controller.v3; + +import com.alibaba.nacos.api.annotation.NacosApi; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.HealthProxy; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller class for handling health check operations. + * + * @author zhangyukun on:2024/8/27 + */ +@NacosApi +@RestController() +@RequestMapping("/v3/console/health") +@ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) +public class ConsoleHealthController { + + private final HealthProxy healthProxy; + + public ConsoleHealthController(HealthProxy healthProxy) { + this.healthProxy = healthProxy; + } + + /** + * Whether the Nacos is in broken states or not, and cannot recover except by being restarted. + * + * @return HTTP code equal to 200 indicates that Nacos is in right states. HTTP code equal to 500 indicates that + * Nacos is in broken states. + */ + @GetMapping("/liveness") + public Result liveness() { + return Result.success("ok"); + } + + /** + * Ready to receive the request or not. + * + * @return HTTP code equal to 200 indicates that Nacos is ready. HTTP code equal to 500 indicates that Nacos is not + * ready. + */ + @GetMapping("/readiness") + public Result readiness() throws NacosException { + return healthProxy.checkReadiness(); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java new file mode 100644 index 00000000000..9e918d70955 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateController.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2024 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.console.controller.v3; + +import com.alibaba.nacos.api.annotation.NacosApi; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.model.v2.SupportedLanguage; +import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.ServerStateProxy; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import org.springframework.http.ResponseEntity; +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 java.util.Map; + +/** + * Controller for managing server state-related operations. + * + * @author zhangyukun on:2024/8/27 + */ +@NacosApi +@RestController +@RequestMapping("/v3/console/server") +@ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) +public class ConsoleServerStateController { + + private final ServerStateProxy serverStateProxy; + + public ConsoleServerStateController(ServerStateProxy serverStateProxy) { + this.serverStateProxy = serverStateProxy; + } + + /** + * Get server state of current server. + * + * @return state json. + */ + @GetMapping("/state") + public ResponseEntity> serverState() { + Map serverState = serverStateProxy.getServerState(); + return ResponseEntity.ok().body(serverState); + } + + /** + * Get the announcement content based on the specified language. + * + * @param language Language for the announcement (default: "zh-CN") + * @return Announcement content as a string wrapped in a Result object + */ + @GetMapping("/announcement") + public Result getAnnouncement( + @RequestParam(required = false, name = "language", defaultValue = "zh-CN") String language) { + // Validate the language parameter + if (!SupportedLanguage.isSupported(language)) { + return Result.failure("Unsupported language: " + language); + } + String announcement = serverStateProxy.getAnnouncement(language); + return Result.success(announcement); + } + + /** + * Get the console UI guide information. + * + * @return Console UI guide information as a string wrapped in a Result object + */ + @GetMapping("/guide") + public Result getConsoleUiGuide() { + String guideInformation = serverStateProxy.getConsoleUiGuide(); + return Result.success(guideInformation); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java new file mode 100644 index 00000000000..3487fc57ccc --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigController.java @@ -0,0 +1,441 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.config; + +import com.alibaba.nacos.api.annotation.NacosApi; +import com.alibaba.nacos.api.config.ConfigType; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.common.utils.NamespaceUtil; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.constant.ParametersField; +import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigRequestInfo; +import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; +import com.alibaba.nacos.config.server.model.SameConfigPolicy; +import com.alibaba.nacos.config.server.model.form.ConfigFormV3; +import com.alibaba.nacos.config.server.paramcheck.ConfigBlurSearchHttpParamExtractor; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.config.server.utils.ParamUtils; +import com.alibaba.nacos.config.server.utils.RequestUtil; +import com.alibaba.nacos.console.proxy.config.ConfigProxy; +import com.alibaba.nacos.core.model.form.PageForm; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +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 org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static com.alibaba.nacos.config.server.utils.RequestUtil.getRemoteIp; + +/** + * Controller for handling HTTP requests related to configuration operations. + * + * @author zhangyukun + */ +@NacosApi +@RestController +@RequestMapping("/v3/console/cs/config") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsoleConfigController { + + private final ConfigProxy configProxy; + + public ConsoleConfigController(ConfigProxy configProxy) { + this.configProxy = configProxy; + } + + /** + * Get the specific configuration information. + * + * @param configForm config form + * @return Result containing detailed configuration information. + * @throws NacosException If a Nacos-specific error occurs. + */ + @GetMapping + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result getConfigDetail(ConfigFormV3 configForm) throws NacosException { + configForm.validate(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + return Result.success(configProxy.getConfigDetail(dataId, groupName, namespaceId)); + } + + /** + * Add or update configuration. + * + * @param request HTTP servlet request. + * @param configForm Configuration form. + * @return Result containing success status. + * @throws NacosException If a Nacos-specific error occurs. + */ + @PostMapping() + @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result publishConfig(HttpServletRequest request, ConfigFormV3 configForm) throws NacosException { + // check required field + configForm.validateWithContent(); + configForm.setNamespaceId(NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId())); + + // check param + ParamUtils.checkParam(configForm.getDataId(), configForm.getGroup(), "datumId", configForm.getContent()); + ParamUtils.checkParamV2(configForm.getTag()); + + if (StringUtils.isBlank(configForm.getSrcUser())) { + configForm.setSrcUser(RequestUtil.getSrcUserName(request)); + } + if (!ConfigType.isValidType(configForm.getType())) { + configForm.setType(ConfigType.getDefaultType().getType()); + } + + ConfigRequestInfo configRequestInfo = new ConfigRequestInfo(); + configRequestInfo.setSrcIp(RequestUtil.getRemoteIp(request)); + configRequestInfo.setRequestIpApp(RequestUtil.getAppName(request)); + configRequestInfo.setBetaIps(request.getHeader("betaIps")); + configRequestInfo.setCasMd5(request.getHeader("casMd5")); + + return Result.success(configProxy.publishConfig(configForm, configRequestInfo)); + } + + /** + * Delete configuration. + * + * @param request HTTP servlet request. + * @param configForm Config form. + * @return Result containing success status. + * @throws NacosException If a Nacos-specific error occurs. + */ + @DeleteMapping + @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result deleteConfig(HttpServletRequest request, ConfigFormV3 configForm) throws NacosException { + configForm.validate(); + //fix issue #9783 + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + ParamUtils.checkParamV2(configForm.getTag()); + + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String tag = configForm.getTag(); + String clientIp = RequestUtil.getRemoteIp(request); + String srcUser = RequestUtil.getSrcUserName(request); + + return Result.success(configProxy.deleteConfig(dataId, groupName, namespaceId, tag, clientIp, srcUser)); + } + + /** + * Batch delete configurations. + * + * @param request HTTP servlet request. + * @param ids List of config IDs. + * @return Result containing success status. + * @throws NacosException If a Nacos-specific error occurs. + */ + @DeleteMapping("/batchDelete") + @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result batchDeleteConfigs(HttpServletRequest request, @RequestParam(value = "ids") List ids) + throws NacosException { + String clientIp = RequestUtil.getRemoteIp(request); + String srcUser = RequestUtil.getSrcUserName(request); + + return Result.success(configProxy.batchDeleteConfigs(ids, clientIp, srcUser)); + } + + /** + * Get configure information list. + * + * @param configForm config form + * @param pageForm page form + * @return Result containing the configuration information. + * @throws ServletException If a servlet-specific error occurs. + * @throws IOException If an I/O error occurs. + * @throws NacosException If a Nacos-specific error occurs. + */ + @GetMapping("/list") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + @ExtractorManager.Extractor(httpExtractor = ConfigBlurSearchHttpParamExtractor.class) + public Result> getConfigList(ConfigFormV3 configForm, PageForm pageForm) + throws IOException, ServletException, NacosException { + configForm.blurSearchValidate(); + pageForm.validate(); + Map configAdvanceInfo = new HashMap<>(100); + if (StringUtils.isNotBlank(configForm.getAppName())) { + configAdvanceInfo.put("appName", configForm.getAppName()); + } + if (StringUtils.isNotBlank(configForm.getConfigTags())) { + configAdvanceInfo.put("config_tags", configForm.getConfigTags()); + } + if (StringUtils.isNotBlank(configForm.getType())) { + configAdvanceInfo.put(ParametersField.TYPES, configForm.getType()); + } + int pageNo = pageForm.getPageNo(); + int pageSize = pageForm.getPageSize(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + + return Result.success( + configProxy.getConfigList(pageNo, pageSize, dataId, groupName, namespaceId, configAdvanceInfo)); + } + + /** + * Search config list by config detail. + * + * @param configForm config form + * @param pageForm page form + * @param configDetail Configuration detail string value. + * @param search Search type. + * @return Result containing the configuration list by content. + * @throws NacosException If a Nacos-specific error occurs. + */ + @GetMapping("/searchDetail") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + @ExtractorManager.Extractor(httpExtractor = ConfigBlurSearchHttpParamExtractor.class) + public Result> getConfigListByContent(ConfigFormV3 configForm, PageForm pageForm, + String configDetail, @RequestParam(defaultValue = "blur") String search) throws NacosException { + configForm.blurSearchValidate(); + pageForm.validate(); + Map configAdvanceInfo = new HashMap<>(100); + if (StringUtils.isNotBlank(configForm.getAppName())) { + configAdvanceInfo.put("appName", configForm.getAppName()); + } + if (StringUtils.isNotBlank(configForm.getConfigTags())) { + configAdvanceInfo.put("config_tags", configForm.getConfigTags()); + } + if (StringUtils.isNotBlank(configForm.getType())) { + configAdvanceInfo.put(ParametersField.TYPES, configForm.getType()); + } + if (StringUtils.isNotBlank(configDetail)) { + configAdvanceInfo.put("content", configDetail); + } + int pageNo = pageForm.getPageNo(); + int pageSize = pageForm.getPageSize(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + + return Result.success( + configProxy.getConfigListByContent(search, pageNo, pageSize, dataId, groupName, namespaceId, + configAdvanceInfo)); + } + + /** + * Subscribe to configured client information. + * + * @param configForm config form + * @param sampleTime Sample time value. + * @return Result containing listener status. + * @throws Exception If an error occurs during the operation. + */ + @GetMapping("/listener") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result getListeners(ConfigFormV3 configForm, + @RequestParam(value = "sampleTime", required = false, defaultValue = "1") int sampleTime) throws Exception { + configForm.validate(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + + String groupName = configForm.getGroupName(); + String dataId = configForm.getDataId(); + return Result.success(configProxy.getListeners(dataId, groupName, namespaceId, sampleTime)); + } + + /** + * Get subscribe information from client side. + */ + @GetMapping("/listener/ip") + @Secured(resource = Constants.LISTENER_CONTROLLER_PATH, action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result getAllSubClientConfigByIp(@RequestParam("ip") String ip, + @RequestParam(value = "all", required = false) boolean all, + @RequestParam(value = "namespaceId", required = false) String namespaceId, + @RequestParam(value = "sampleTime", required = false, defaultValue = "1") int sampleTime, ModelMap modelMap) + throws NacosException { + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + GroupkeyListenserStatus result = configProxy.getAllSubClientConfigByIp(ip, all, namespaceId, sampleTime); + return Result.success(result); + } + + /** + * Export configuration. + * + * @param configForm config form + * @param ids List of config IDs. + * @return ResponseEntity containing the exported configuration. + * @throws Exception If an error occurs during the export. + */ + @GetMapping("/export") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public ResponseEntity exportConfig(ConfigFormV3 configForm, + @RequestParam(value = "ids", required = false) List ids) throws Exception { + configForm.blurSearchValidate(); + ids.removeAll(Collections.singleton(null)); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String appName = configForm.getAppName(); + return configProxy.exportConfig(dataId, groupName, namespaceId, appName, ids); + } + + /** + * New version export config adds metadata.yml file to record config metadata. + * + * @param configForm config form + * @param ids List of config IDs. + * @return ResponseEntity containing the exported configuration. + * @throws Exception If an error occurs during the export. + */ + @GetMapping("/export2") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public ResponseEntity exportConfigV2(ConfigFormV3 configForm, + @RequestParam(value = "ids", required = false) List ids) throws Exception { + configForm.blurSearchValidate(); + ids.removeAll(Collections.singleton(null)); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String appName = configForm.getAppName(); + + return configProxy.exportConfigV2(dataId, groupName, namespaceId, appName, ids); + } + + /** + * Import and publish configuration. + * + * @param request HTTP servlet request. + * @param srcUser Source user string value. + * @param namespaceId Namespace string value. + * @param policy Policy model. + * @param file Multipart file containing the configuration data. + * @return Result containing a map of the import status. + * @throws NacosException If a Nacos-specific error occurs. + */ + @PostMapping("/import") + @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result> importAndPublishConfig(HttpServletRequest request, + @RequestParam(required = false) String srcUser, + @RequestParam(value = "namespaceId", required = false) String namespaceId, + @RequestParam(value = "policy", defaultValue = "ABORT") SameConfigPolicy policy, MultipartFile file) + throws NacosException { + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + + if (StringUtils.isBlank(srcUser)) { + srcUser = RequestUtil.getSrcUserName(request); + } + final String srcIp = RequestUtil.getRemoteIp(request); + String requestIpApp = RequestUtil.getAppName(request); + + return configProxy.importAndPublishConfig(srcUser, namespaceId, policy, file, srcIp, requestIpApp); + } + + /** + * Clone configuration. + * + * @param request HTTP servlet request. + * @param srcUser Source user string value. + * @param namespaceId Namespace string value. + * @param configBeansList List of configuration beans. + * @param policy Policy model. + * @return Result containing a map of the clone status. + * @throws NacosException If a Nacos-specific error occurs. + */ + @PostMapping("/clone") + @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result> cloneConfig(HttpServletRequest request, + @RequestParam(required = false) String srcUser, + @RequestParam(value = "targetNamespaceId") String namespaceId, + @RequestBody List configBeansList, + @RequestParam(value = "policy", defaultValue = "ABORT") SameConfigPolicy policy) throws NacosException { + configBeansList.removeAll(Collections.singleton(null)); + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + if (StringUtils.isBlank(srcUser)) { + srcUser = RequestUtil.getSrcUserName(request); + } + final String srcIp = RequestUtil.getRemoteIp(request); + String requestIpApp = RequestUtil.getAppName(request); + + return configProxy.cloneConfig(srcUser, namespaceId, configBeansList, policy, srcIp, requestIpApp); + } + + /** + * Execute to remove beta operation. + * + * @param httpServletRequest HTTP request containing client details. + * @param configForm config form + * @return Result indicating the outcome of the operation. + * @throws NacosException If a Nacos-specific error occurs. + */ + @DeleteMapping("/beta") + @Secured(action = ActionTypes.WRITE, signType = SignType.CONFIG) + public Result stopBeta(HttpServletRequest httpServletRequest, ConfigFormV3 configForm) + throws NacosException { + configForm.validate(); + String remoteIp = getRemoteIp(httpServletRequest); + String requestIpApp = RequestUtil.getAppName(httpServletRequest); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + boolean success = configProxy.removeBetaConfig(dataId, groupName, namespaceId, remoteIp, requestIpApp); + if (!success) { + return Result.failure(HttpStatus.INTERNAL_SERVER_ERROR.value(), + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), false); + } + return Result.success(true); + } + + /** + * Execute to query beta operation. + * + * @param configForm config form + * @return Result containing the ConfigInfo4Beta details. + * @throws NacosException If a Nacos-specific error occurs. + */ + @GetMapping("/beta") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG) + public Result queryBeta(ConfigFormV3 configForm) throws NacosException { + configForm.validate(); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + return configProxy.queryBetaConfig(dataId, groupName, namespaceId); + } + +} + + diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java new file mode 100644 index 00000000000..f085191e130 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryController.java @@ -0,0 +1,136 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.config; + +import com.alibaba.nacos.api.annotation.NacosApi; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.common.utils.NamespaceUtil; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.config.server.model.form.ConfigFormV3; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.config.HistoryProxy; +import com.alibaba.nacos.core.model.form.PageForm; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import org.springframework.beans.factory.annotation.Autowired; +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 java.util.List; + +/** + * Controller for handling HTTP requests related to history operations. + * + * @author zhangyukun on:2024/8/16 + */ +@NacosApi +@RestController +@RequestMapping("/v3/console/cs/history") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class ConsoleHistoryController { + + private final HistoryProxy historyProxy; + + @Autowired + public ConsoleHistoryController(HistoryProxy historyProxy) { + this.historyProxy = historyProxy; + } + + /** + * Query the detailed configuration history information. notes: + * + * @param nid history_config_info nid + * @param configForm config form + * @return history config info + */ + @GetMapping + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result getConfigHistoryInfo(ConfigFormV3 configForm, @RequestParam("nid") Long nid) + throws NacosException { + configForm.validate(); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + + return Result.success(historyProxy.getConfigHistoryInfo(dataId, groupName, namespaceId, nid)); + } + + /** + * Query the list history config. notes: + * + * @param configForm config form + * @param pageForm page form + * @return the page of history config. + */ + @GetMapping("/list") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result> listConfigHistory(ConfigFormV3 configForm, PageForm pageForm) + throws NacosException { + configForm.validate(); + pageForm.validate(); + int pageSize = Math.min(500, pageForm.getPageSize()); + int pageNo = pageForm.getPageNo(); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + + return Result.success(historyProxy.listConfigHistory(dataId, groupName, namespaceId, pageNo, pageSize)); + } + + /** + * Query previous config history information. notes: + * + * @param id config_info id + * @param configForm config form + * @return history config info + */ + @GetMapping(value = "/previous") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result getPreviousConfigHistoryInfo(ConfigFormV3 configForm, @RequestParam("id") Long id) + throws NacosException { + configForm.validate(); + String dataId = configForm.getDataId(); + String groupName = configForm.getGroupName(); + String namespaceId = NamespaceUtil.processNamespaceParameter(configForm.getNamespaceId()); + return Result.success(historyProxy.getPreviousConfigHistoryInfo(dataId, groupName, namespaceId, id)); + } + + /** + * Query configs list by namespace. + * + * @param namespaceId config_info namespace + * @return list + */ + @GetMapping(value = "/configs") + @Secured(action = ActionTypes.READ, signType = SignType.CONFIG, apiType = ApiType.CONSOLE_API) + public Result> getConfigsByTenant(@RequestParam("namespaceId") String namespaceId) + throws NacosException { + namespaceId = NamespaceUtil.processNamespaceParameter(namespaceId); + + return Result.success(historyProxy.getConfigsByTenant(namespaceId)); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java new file mode 100644 index 00000000000..3a1b3a0b133 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterController.java @@ -0,0 +1,70 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.core; + +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.plugin.auth.constant.ApiType; +import com.alibaba.nacos.console.proxy.core.ClusterProxy; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.utils.Commons; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.SignType; +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 java.util.Collection; + +/** + * Controller for handling HTTP requests related to cluster operations. + * + * @author zhangyukun on:2024/8/16 + */ +@NacosApi +@RestController +@RequestMapping("/v3/console/core/cluster") +public class ConsoleClusterController { + + private final ClusterProxy clusterProxy; + + /** + * Constructs a new ConsoleClusterController with the provided ClusterProxy. + * + * @param clusterProxy the proxy used for handling cluster-related operations + */ + public ConsoleClusterController(ClusterProxy clusterProxy) { + this.clusterProxy = clusterProxy; + } + + /** + * The console displays the list of cluster members. + * + * @param ipKeyWord search keyWord + * @return all members + */ + @GetMapping(value = "/nodes") + @Secured(resource = Commons.NACOS_CORE_CONTEXT + + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) + public Result> getNodeList(@RequestParam(value = "keyword", required = false) String ipKeyWord) { + Collection result = clusterProxy.getNodeList(ipKeyWord); + return Result.success(result); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java new file mode 100644 index 00000000000..db99fff5a09 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceController.java @@ -0,0 +1,156 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.core; + +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.plugin.auth.constant.ApiType; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.console.paramcheck.ConsoleDefaultHttpParamExtractor; +import com.alibaba.nacos.console.proxy.core.NamespaceProxy; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.CreateNamespaceForm; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.SignType; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * Controller for handling HTTP requests related to namespace operations. + * + * @author zhangyukun on:2024/8/27 + */ +@NacosApi +@RestController +@RequestMapping("/v3/console/core/namespace") +@ExtractorManager.Extractor(httpExtractor = ConsoleDefaultHttpParamExtractor.class) +public class ConsoleNamespaceController { + + private final NamespaceProxy namespaceProxy; + + private final NamespacePersistService namespacePersistService; + + public ConsoleNamespaceController(NamespaceProxy namespaceProxy, NamespacePersistService namespacePersistService) { + this.namespaceProxy = namespaceProxy; + this.namespacePersistService = namespacePersistService; + } + + /** + * Get namespace list. + * + * @return namespace list + */ + @GetMapping("/list") + public Result> getNamespaceList() throws NacosException { + return Result.success(namespaceProxy.getNamespaceList()); + } + + /** + * get namespace all info by namespace id. + * + * @param namespaceId namespaceId + * @return namespace all info + */ + @GetMapping() + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.READ, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) + public Result getNamespaceDetail(@RequestParam("namespaceId") String namespaceId) throws NacosException { + return Result.success(namespaceProxy.getNamespaceDetail(namespaceId)); + } + + /** + * create namespace. + * + * @param namespaceForm create namespace form. + * @return whether create ok + */ + @PostMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) + public Result createNamespace(CreateNamespaceForm namespaceForm) throws NacosException { + namespaceForm.validate(); + String namespaceId = namespaceForm.getCustomNamespaceId(); + // TODO sink to proxy - handler + if (namespacePersistService.tenantInfoCountByTenantId(namespaceId) > 0) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.ILLEGAL_NAMESPACE, + "the namespaceId is existed, namespaceId: " + namespaceId); + } + String namespaceName = namespaceForm.getNamespaceName(); + String namespaceDesc = namespaceForm.getNamespaceDesc(); + return Result.success(namespaceProxy.createNamespace(namespaceId, namespaceName, namespaceDesc)); + } + + /** + * edit namespace. + * + * @param namespaceForm namespace form + * @return whether edit ok + */ + @PutMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) + public Result updateNamespace(NamespaceForm namespaceForm) throws NacosException { + namespaceForm.validate(); + return Result.success(namespaceProxy.updateNamespace(namespaceForm)); + } + + /** + * delete namespace by id. + * + * @param namespaceId namespace ID + * @return whether delete ok + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + + "namespaces", action = ActionTypes.WRITE, signType = SignType.CONSOLE, apiType = ApiType.CONSOLE_API) + public Result deleteNamespace(@RequestParam("namespaceId") String namespaceId) throws NacosException { + return Result.success(namespaceProxy.deleteNamespace(namespaceId)); + } + + /** + * check namespaceId exist. + * + * @param namespaceId namespace id + * @return true if exist, otherwise false + */ + @GetMapping("/exist") + public Result checkNamespaceIdExist(@RequestParam("customNamespaceId") String namespaceId) + throws NacosException { + // customNamespaceId if blank means create new namespace with uuid. + if (StringUtils.isBlank(namespaceId)) { + return Result.success(false); + } + return Result.success(namespaceProxy.checkNamespaceIdExist(namespaceId)); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java new file mode 100644 index 00000000000..694ae349874 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceController.java @@ -0,0 +1,140 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.naming; + +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.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.pojo.builder.InstanceBuilder; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.console.proxy.naming.InstanceProxy; +import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.model.form.PageForm; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.naming.misc.SwitchDomain; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; +import com.alibaba.nacos.naming.web.CanDistro; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for handling HTTP requests related to instance operations. + * + * @author zhangyukun on:2024/8/16 + */ +@NacosApi +@RestController +@RequestMapping("/v3/console/ns/instance") +@ExtractorManager.Extractor(httpExtractor = NamingDefaultHttpParamExtractor.class) +public class ConsoleInstanceController { + + private final SwitchDomain switchDomain; + + private final InstanceProxy instanceProxy; + + /** + * Constructs a new ConsoleInstanceController with the provided InstanceProxy. + * + * @param instanceProxy the proxy used for handling instance-related operations + */ + public ConsoleInstanceController(InstanceProxy instanceProxy, SwitchDomain switchDomain) { + this.instanceProxy = instanceProxy; + this.switchDomain = switchDomain; + } + + /** + * List instances of special service. + * + * @param serviceForm service form + * @param pageForm Page form + * @param healthyOnly whether only return health instance + * @param enabledOnly whether only return enabled instance + * @return instances information + */ + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) + @RequestMapping("/list") + public Result getInstanceList(ServiceForm serviceForm, PageForm pageForm, + @RequestParam(required = false) Boolean healthyOnly, @RequestParam(required = false) Boolean enabledOnly) + throws NacosApiException { + serviceForm.validate(); + String namespaceId = serviceForm.getNamespaceId(); + String groupName = serviceForm.getGroupName(); + String serviceName = serviceForm.getServiceName(); + ObjectNode result = instanceProxy.listInstances(namespaceId, serviceName, groupName, pageForm.getPageNo(), + pageForm.getPageSize(), healthyOnly, enabledOnly); + return Result.success(result); + } + + /** + * Update instance. + */ + @CanDistro + @PutMapping + @TpsControl(pointName = "NamingInstanceUpdate", name = "HttpNamingInstanceUpdate") + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) + public Result updateInstance(InstanceForm instanceForm) throws NacosException { + // check param + instanceForm.validate(); + checkWeight(instanceForm.getWeight()); + // build instance + Instance instance = buildInstance(instanceForm); + instanceProxy.updateInstance(instanceForm, instance); + return Result.success("ok"); + } + + private void checkWeight(Double weight) throws NacosException { + if (weight > com.alibaba.nacos.naming.constants.Constants.MAX_WEIGHT_VALUE + || weight < com.alibaba.nacos.naming.constants.Constants.MIN_WEIGHT_VALUE) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.WEIGHT_ERROR, + "instance format invalid: The weights range from " + + com.alibaba.nacos.naming.constants.Constants.MIN_WEIGHT_VALUE + " to " + + com.alibaba.nacos.naming.constants.Constants.MAX_WEIGHT_VALUE); + } + } + + private Instance buildInstance(InstanceForm instanceForm) throws NacosException { + Instance instance = InstanceBuilder.newBuilder().setServiceName(buildCompositeServiceName(instanceForm)) + .setIp(instanceForm.getIp()).setClusterName(instanceForm.getClusterName()) + .setPort(instanceForm.getPort()).setHealthy(instanceForm.getHealthy()) + .setWeight(instanceForm.getWeight()).setEnabled(instanceForm.getEnabled()) + .setMetadata(UtilsAndCommons.parseMetadata(instanceForm.getMetadata())) + .setEphemeral(instanceForm.getEphemeral()).build(); + if (instanceForm.getEphemeral() == null) { + instance.setEphemeral((switchDomain.isDefaultInstanceEphemeral())); + } + return instance; + } + + private String buildCompositeServiceName(InstanceForm instanceForm) { + return NamingUtils.getGroupedName(instanceForm.getServiceName(), instanceForm.getGroupName()); + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java new file mode 100644 index 00000000000..f78c5d7a159 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceController.java @@ -0,0 +1,251 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.naming; + +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.api.naming.pojo.healthcheck.AbstractHealthChecker; +import com.alibaba.nacos.api.naming.pojo.healthcheck.HealthCheckerFactory; +import com.alibaba.nacos.api.selector.Selector; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.console.proxy.naming.ServiceProxy; +import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.model.form.AggregationForm; +import com.alibaba.nacos.core.model.form.PageForm; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.misc.UtilsAndCommons; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.model.form.ServiceListForm; +import com.alibaba.nacos.naming.model.form.UpdateClusterForm; +import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; +import com.alibaba.nacos.naming.selector.NoneSelector; +import com.alibaba.nacos.naming.selector.SelectorManager; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.net.URLDecoder; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; + +/** + * Controller for handling HTTP requests related to service operations. + * + * @author zhangyukun on:2024/8/16 + */ +@NacosApi +@RestController +@RequestMapping("/v3/console/ns/service") +@ExtractorManager.Extractor(httpExtractor = NamingDefaultHttpParamExtractor.class) +public class ConsoleServiceController { + + private final ServiceProxy serviceProxy; + + private final SelectorManager selectorManager; + + public ConsoleServiceController(ServiceProxy serviceProxy, SelectorManager selectorManager) { + this.serviceProxy = serviceProxy; + this.selectorManager = selectorManager; + } + + /** + * Create a new service. This API will create a persistence service. + */ + @PostMapping() + @TpsControl(pointName = "NamingServiceRegister", name = "HttpNamingServiceRegister") + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) + public Result createService(ServiceForm serviceForm) throws Exception { + serviceForm.validate(); + ServiceMetadata serviceMetadata = new ServiceMetadata(); + serviceMetadata.setProtectThreshold(serviceForm.getProtectThreshold()); + serviceMetadata.setSelector(parseSelector(serviceForm.getSelector())); + serviceMetadata.setExtendData(UtilsAndCommons.parseMetadata(serviceForm.getMetadata())); + serviceMetadata.setEphemeral(serviceForm.getEphemeral()); + + serviceProxy.createService(serviceForm, serviceMetadata); + return Result.success("ok"); + } + + /** + * Remove service. + */ + @DeleteMapping() + @TpsControl(pointName = "NamingServiceDeregister", name = "HttpNamingServiceDeregister") + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) + public Result deleteService(ServiceForm serviceForm) throws Exception { + serviceForm.validate(); + serviceProxy.deleteService(serviceForm.getNamespaceId(), serviceForm.getServiceName(), + serviceForm.getGroupName()); + return Result.success("ok"); + } + + /** + * Update service. + */ + @PutMapping() + @TpsControl(pointName = "NamingServiceUpdate", name = "HttpNamingServiceUpdate") + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) + public Result updateService(ServiceForm serviceForm) throws Exception { + serviceForm.validate(); + + Map metadata = UtilsAndCommons.parseMetadata(serviceForm.getMetadata()); + ServiceMetadata serviceMetadata = new ServiceMetadata(); + serviceMetadata.setProtectThreshold(serviceForm.getProtectThreshold()); + serviceMetadata.setExtendData(metadata); + serviceMetadata.setSelector(parseSelector(serviceForm.getSelector())); + Service service = Service.newService(serviceForm.getNamespaceId(), serviceForm.getGroupName(), + serviceForm.getServiceName()); + + serviceProxy.updateService(serviceForm, service, serviceMetadata, metadata); + return Result.success("ok"); + } + + /** + * Get all {@link Selector} types. + * + * @return {@link Selector} types. + */ + @GetMapping("/selector/types") + public Result> getSelectorTypeList() { + return Result.success(serviceProxy.getSelectorTypeList()); + } + + /** + * get subscriber list. + * + * @param serviceForm service form data + * @param pageForm page form data + * @param aggregationForm whether aggregation form data + * @return subscribes result data. + * @throws Exception any exception during get subscriber list. + */ + @GetMapping("/subscribers") + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) + public Result subscribers(ServiceForm serviceForm, PageForm pageForm, AggregationForm aggregationForm) + throws Exception { + serviceForm.validate(); + pageForm.validate(); + int pageNo = pageForm.getPageNo(); + int pageSize = pageForm.getPageSize(); + String namespaceId = serviceForm.getNamespaceId(); + String serviceName = serviceForm.getServiceName(); + String groupName = serviceForm.getGroupName(); + boolean aggregation = aggregationForm.isAggregation(); + + return Result.success( + serviceProxy.getSubscribers(pageNo, pageSize, namespaceId, serviceName, groupName, aggregation)); + } + + /** + * List service detail information. + * + * @param serviceListForm service list form + * @param pageForm page form + * @return list service detail, depend on withInstances parameters, return ServiceDetailInfo or ServiceView. + */ + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) + @GetMapping("/list") + public Result getServiceList(ServiceListForm serviceListForm, PageForm pageForm) throws NacosException { + serviceListForm.validate(); + pageForm.validate(); + String namespaceId = serviceListForm.getNamespaceId(); + String serviceName = serviceListForm.getServiceNameParam(); + String groupName = serviceListForm.getGroupNameParam(); + boolean hasIpCount = serviceListForm.isHasIpCount(); + boolean withInstances = serviceListForm.isWithInstances(); + return Result.success( + serviceProxy.getServiceList(withInstances, namespaceId, pageForm.getPageNo(), pageForm.getPageSize(), + serviceName, groupName, hasIpCount)); + } + + /** + * Get service detail. + * + * @param serviceForm service form data + * @return service detail information + * @throws NacosException nacos exception + */ + @Secured(action = ActionTypes.READ, apiType = ApiType.CONSOLE_API) + @GetMapping() + public Object getServiceDetail(ServiceForm serviceForm) throws NacosException { + serviceForm.validate(); + return Result.success(serviceProxy.getServiceDetail(serviceForm.getNamespaceId(), serviceForm.getServiceName(), + serviceForm.getGroupName())); + } + + /** + * Update cluster. + * + * @param updateClusterForm update cluster form. + * @return 'ok' if success + * @throws Exception if failed + */ + @PutMapping("/cluster") + @Secured(action = ActionTypes.WRITE, apiType = ApiType.CONSOLE_API) + public Result updateCluster(UpdateClusterForm updateClusterForm) throws Exception { + updateClusterForm.validate(); + final String namespaceId = updateClusterForm.getNamespaceId(); + final String clusterName = updateClusterForm.getClusterName(); + final String serviceName = updateClusterForm.getServiceName(); + ClusterMetadata clusterMetadata = new ClusterMetadata(); + clusterMetadata.setHealthyCheckPort(updateClusterForm.getCheckPort()); + clusterMetadata.setUseInstancePortForCheck(updateClusterForm.isUseInstancePort4Check()); + AbstractHealthChecker healthChecker = HealthCheckerFactory.deserialize(updateClusterForm.getHealthChecker()); + clusterMetadata.setHealthChecker(healthChecker); + clusterMetadata.setHealthyCheckType(healthChecker.getType()); + clusterMetadata.setExtendData(UtilsAndCommons.parseMetadata(updateClusterForm.getMetadata())); + + serviceProxy.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + return Result.success("ok"); + } + + private Selector parseSelector(String selectorJsonString) throws Exception { + if (StringUtils.isBlank(selectorJsonString)) { + return new NoneSelector(); + } + + JsonNode selectorJson = JacksonUtils.toObj(URLDecoder.decode(selectorJsonString, "UTF-8")); + String type = Optional.ofNullable(selectorJson.get("type")).orElseThrow( + () -> new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.SELECTOR_ERROR, + "not match any type of selector!")).asText(); + String expression = Optional.ofNullable(selectorJson.get("expression")).map(JsonNode::asText).orElse(null); + Selector selector = selectorManager.parseSelector(type, expression); + if (Objects.isNull(selector)) { + throw new NacosApiException(NacosException.INVALID_PARAM, ErrorCode.SELECTOR_ERROR, + "not match any type of selector!"); + } + return selector; + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/filter/NacosConsoleAuthFilter.java b/console/src/main/java/com/alibaba/nacos/console/filter/NacosConsoleAuthFilter.java new file mode 100644 index 00000000000..153f3a29e1f --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/filter/NacosConsoleAuthFilter.java @@ -0,0 +1,50 @@ +/* + * 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.console.filter; + +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; +import com.alibaba.nacos.console.config.NacosConsoleAuthConfig; +import com.alibaba.nacos.core.auth.AbstractWebAuthFilter; +import com.alibaba.nacos.core.code.ControllerMethodsCache; + +import javax.servlet.http.HttpServletRequest; + +/** + * Nacos Console web auth filter. + * + * @author xiweng.yy + */ +public class NacosConsoleAuthFilter extends AbstractWebAuthFilter { + + private final NacosConsoleAuthConfig authConfig; + + public NacosConsoleAuthFilter(NacosConsoleAuthConfig authConfig, ControllerMethodsCache methodsCache) { + super(authConfig, methodsCache); + this.authConfig = authConfig; + } + + @Override + protected boolean isAuthEnabled() { + return authConfig.isAuthEnabled(); + } + + @Override + protected ServerIdentityResult checkServerIdentity(HttpServletRequest request, Secured secured) { + return ServerIdentityResult.noMatched(); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/HealthHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/HealthHandler.java new file mode 100644 index 00000000000..9866cf330ee --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/HealthHandler.java @@ -0,0 +1,36 @@ +/* + * Copyright 1999-2024 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.console.handler; + +import com.alibaba.nacos.api.model.v2.Result; + +/** + * Interface for handling health check operations. + * + * @author zhangyukun + */ +public interface HealthHandler { + + /** + * Perform readiness check to determine if Nacos is ready to handle requests. + * + * @return readiness result + */ + Result checkReadiness(); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/ServerStateHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/ServerStateHandler.java new file mode 100644 index 00000000000..4382db996e1 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/ServerStateHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright 1999-2024 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.console.handler; + +import java.util.Map; + +/** + * Interface for handling server state operations. + * + * @author zhangyukun + */ +public interface ServerStateHandler { + + /** + * Get the current state of the server. + * + * @return a map containing the server state + */ + Map getServerState(); + + /** + * Get the announcement content based on the language. + * + * @param language the language for the announcement + * @return the announcement content + */ + String getAnnouncement(String language); + + /** + * Get the console UI guide information. + * + * @return the console UI guide information + */ + String getConsoleUiGuide(); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java new file mode 100644 index 00000000000..0081ac4b151 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/config/ConfigHandler.java @@ -0,0 +1,230 @@ +/* + * Copyright 1999-2024 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.console.handler.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigRequestInfo; +import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; +import com.alibaba.nacos.config.server.model.SameConfigPolicy; +import com.alibaba.nacos.config.server.model.form.ConfigForm; +import com.alibaba.nacos.persistence.model.Page; +import org.springframework.http.ResponseEntity; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletException; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Interface for handling configuration operations. + * + * @author zhangyukun + */ +public interface ConfigHandler { + + /** + * Retrieves the configuration based on the specified parameters. + * + * @param pageNo The page number for pagination. + * @param pageSize The number of items per page. + * @param dataId The identifier of the configuration data. + * @param group The group to which the configuration belongs. + * @param namespaceId The namespace identifier. + * @param configAdvanceInfo Additional advanced search criteria. + * @return ConfigInfo containing all details of the specified configuration. + * @throws IOException If an input or output exception occurs. + * @throws ServletException If a servlet-specific exception occurs. + * @throws NacosException If an error related to Nacos configuration occurs. + */ + Page getConfigList(int pageNo, int pageSize, String dataId, String group, String namespaceId, + Map configAdvanceInfo) throws IOException, ServletException, NacosException; + + /** + * Retrieves detailed information about a specific configuration. + * + * @param dataId The identifier of the configuration data. + * @param group The group to which the configuration belongs. + * @param namespaceId The namespace identifier. + * @return A ConfigAllInfo object containing all details of the specified configuration. + * @throws NacosException If an error related to Nacos configuration occurs. + */ + ConfigAllInfo getConfigDetail(String dataId, String group, String namespaceId) throws NacosException; + + /** + * Publishes a new configuration or updates an existing configuration. + * + * @param configForm The form object containing configuration details. + * @param configRequestInfo Additional request information related to the configuration. + * @return A Boolean indicating whether the publish operation was successful. + * @throws NacosException If an error related to Nacos configuration occurs. + */ + Boolean publishConfig(ConfigForm configForm, ConfigRequestInfo configRequestInfo) throws NacosException; + + /** + * Deletes a specific configuration. + * + * @param dataId The identifier of the configuration data to delete. + * @param group The group to which the configuration belongs. + * @param namespaceId The namespace identifier. + * @param tag The tag associated with the configuration. + * @param clientIp The IP address of the client requesting the deletion. + * @param srcUser The source user requesting the deletion. + * @return A Boolean indicating whether the deletion was successful. + * @throws NacosException If an error related to Nacos configuration occurs. + */ + Boolean deleteConfig(String dataId, String group, String namespaceId, String tag, String clientIp, String srcUser) + throws NacosException; + + /** + * Deletes multiple configurations based on their IDs. + * + * @param ids A list of IDs of the configurations to delete. + * @param clientIp The IP address of the client requesting the deletion. + * @param srcUser The source user requesting the deletion. + * @return A Boolean indicating whether the deletion was successful. + */ + Boolean batchDeleteConfigs(List ids, String clientIp, String srcUser); + + /** + * Exports the configuration based on the specified parameters. + * + * @param dataId The identifier of the configuration data. + * @param group The group to which the configuration belongs. + * @param namespaceId The namespace identifier. + * @param appName The application name associated with the configuration. + * @param ids A list of IDs of the configurations to export. + * @return A ResponseEntity containing the exported configuration as a byte array. + * @throws Exception If an unexpected error occurs during the export process. + */ + ResponseEntity exportConfig(String dataId, String group, String namespaceId, String appName, List ids) + throws Exception; + + /** + * Exports the configuration with metadata based on the specified parameters. + * + * @param dataId The identifier of the configuration data. + * @param group The group to which the configuration belongs. + * @param namespaceId The namespace identifier. + * @param appName The application name associated with the configuration. + * @param ids A list of IDs of the configurations to export. + * @return A ResponseEntity containing the exported configuration as a byte array. + * @throws Exception If an unexpected error occurs during the export process. + */ + ResponseEntity exportConfigV2(String dataId, String group, String namespaceId, String appName, + List ids) throws Exception; + + /** + * Searches for configurations based on detailed criteria. + * + * @param search The search keyword. + * @param pageNo The page number for pagination. + * @param pageSize The number of items per page. + * @param dataId The identifier of the configuration data. + * @param group The group to which the configuration belongs. + * @param namespaceId The namespace identifier. + * @param configAdvanceInfo Additional advanced search criteria. + * @return A Page object containing a list of ConfigInfo that matches the search criteria. + * @throws NacosException If an error related to Nacos configuration occurs. + */ + Page getConfigListByContent(String search, int pageNo, int pageSize, String dataId, String group, + String namespaceId, Map configAdvanceInfo) throws NacosException; + + /** + * Retrieves the status of listeners for a specific configuration. + * + * @param dataId The identifier of the configuration data. + * @param group The group to which the configuration belongs. + * @param namespaceId The namespace identifier. + * @param sampleTime The sampling time for listener status. + * @return A GroupkeyListenserStatus object containing the status of the listeners. + * @throws Exception If an unexpected error occurs. + */ + GroupkeyListenserStatus getListeners(String dataId, String group, String namespaceId, int sampleTime) + throws Exception; + + /** + * Get subscription information based on IP, tenant, and other parameters. + * + * @param ip IP address of the client + * @param all Whether to retrieve all configurations + * @param namespaceId Tenant information + * @param sampleTime Sample time for the subscription + * @return GroupkeyListenserStatus object containing subscription information + */ + GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime); + + /** + * Imports and publishes a configuration from a file. + * + * @param srcUser The source user performing the import. + * @param namespaceId The namespace identifier. + * @param policy The policy for handling existing configurations. + * @param file The file containing the configuration to import. + * @param srcIp The IP address of the source. + * @param requestIpApp The IP address of the requester. + * @return A Result object containing the status and additional information about the operation. + * @throws NacosException If an error related to Nacos configuration occurs. + */ + Result> importAndPublishConfig(String srcUser, String namespaceId, SameConfigPolicy policy, + MultipartFile file, String srcIp, String requestIpApp) throws NacosException; + + + /** + * Clones an existing configuration to a different namespace. + * + * @param srcUser The source user performing the clone operation. + * @param namespaceId The namespace identifier where the configuration will be cloned to. + * @param configBeansList A list of configurations to be cloned. + * @param policy The policy for handling existing configurations in the target namespace. + * @param srcIp The IP address of the source. + * @param requestIpApp The IP address of the requester. + * @return A Result object containing the status and additional information about the operation. + * @throws NacosException If an error related to Nacos configuration occurs. + */ + Result> cloneConfig(String srcUser, String namespaceId, + List configBeansList, SameConfigPolicy policy, String srcIp, + String requestIpApp) throws NacosException; + + /** + * Remove beta configuration based on dataId, group, and namespaceId. + * + * @param dataId the dataId + * @param group the group + * @param namespaceId the namespaceId + * @param remoteIp the IP address of the client making the request + * @param requestIpApp the name of the application making the request + * @return true if the beta configuration is successfully removed + */ + boolean removeBetaConfig(String dataId, String group, String namespaceId, String remoteIp, String requestIpApp); + + /** + * Query beta configuration based on dataId, group, and namespaceId. + * + * @param dataId the dataId + * @param group the group + * @param namespaceId the namespaceId + * @return ConfigInfo4Beta containing the beta configuration details + */ + Result queryBetaConfig(String dataId, String group, String namespaceId); +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/config/HistoryHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/config/HistoryHandler.java new file mode 100644 index 00000000000..7e0db37aae9 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/config/HistoryHandler.java @@ -0,0 +1,83 @@ +/* + * Copyright 1999-2024 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.console.handler.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.persistence.model.Page; + +import java.util.List; + +/** + * Interface for handling configuration history related operations. + * + * @author zhangyukun + */ +public interface HistoryHandler { + + /** + * Query the detailed configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param nid the history record ID + * @return the detailed configuration history information + * @throws NacosException if any error occurs during the operation + */ + ConfigHistoryInfo getConfigHistoryInfo(String dataId, String group, String namespaceId, Long nid) + throws NacosException; + + /** + * Query the list of configuration history. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the number of items per page + * @return the paginated list of configuration history + * @throws NacosException if any error occurs during the operation + */ + Page listConfigHistory(String dataId, String group, String namespaceId, Integer pageNo, + Integer pageSize) throws NacosException; + + /** + * Query the previous configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param id the configuration ID + * @return the previous configuration history information + * @throws NacosException if any error occurs during the operation + */ + ConfigHistoryInfo getPreviousConfigHistoryInfo(String dataId, String group, String namespaceId, Long id) + throws NacosException; + + /** + * Query the list of configurations by namespace. + * + * @param namespaceId the namespace ID + * @return the list of configurations + * @throws NacosApiException if any error occurs during the operation + */ + List getConfigsByTenant(String namespaceId) throws NacosApiException; +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java new file mode 100644 index 00000000000..f8b795de338 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/core/ClusterHandler.java @@ -0,0 +1,39 @@ +/* + * Copyright 1999-2024 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.console.handler.core; + +import com.alibaba.nacos.core.cluster.Member; + +import java.util.Collection; + +/** + * Interface for handling cluster-related operations. + * + * @author zhangyukun + */ +public interface ClusterHandler { + + /** + * Retrieve a list of cluster members with an optional search keyword. + * + * @param ipKeyWord the search keyword for filtering members + * @return a collection of matching members + */ + Collection getNodeList(String ipKeyWord); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/core/NamespaceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/core/NamespaceHandler.java new file mode 100644 index 00000000000..63ac5f75663 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/core/NamespaceHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright 1999-2024 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.console.handler.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; + +import java.util.List; + +/** + * Interface for handling namespace-related operations. + * + * @author zhangyukun + */ +public interface NamespaceHandler { + + /** + * Get a list of namespaces. + * + * @return list of namespaces + */ + List getNamespaceList(); + + /** + * Get details of a specific namespace. + * + * @param namespaceId the ID of the namespace + * @return namespace details + * @throws NacosException if there is an issue fetching the namespace + */ + Namespace getNamespaceDetail(String namespaceId) throws NacosException; + + /** + * Create a new namespace. + * + * @param namespaceId the ID of the namespace + * @param namespaceName the name of the namespace + * @param namespaceDesc the description of the namespace + * @return true if the namespace was successfully created, otherwise false + * @throws NacosException if there is an issue creating the namespace + */ + Boolean createNamespace(String namespaceId, String namespaceName, String namespaceDesc) throws NacosException; + + /** + * Update an existing namespace. + * + * @param namespaceForm the form containing the updated namespace details + * @return true if the namespace was successfully updated, otherwise false + * @throws NacosException if there is an issue updating the namespace + */ + Boolean updateNamespace(NamespaceForm namespaceForm) throws NacosException; + + /** + * Delete a namespace by its ID. + * + * @param namespaceId the ID of the namespace + * @return true if the namespace was successfully deleted, otherwise false + */ + Boolean deleteNamespace(String namespaceId); + + /** + * Check if a namespace ID exists. + * + * @param namespaceId the ID of the namespace to check + * @return true if the namespace exists, otherwise false + */ + Boolean checkNamespaceIdExist(String namespaceId); +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/EnabledInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/EnabledInnerHandler.java new file mode 100644 index 00000000000..012d47ce002 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/EnabledInnerHandler.java @@ -0,0 +1,39 @@ +/* + * 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.console.handler.impl.inner; + +import com.alibaba.nacos.sys.env.Constants; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Using Inner handler to handle console API request. + * + * @author xiweng.yy + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ConditionalOnProperty(value = Constants.NACOS_DEPLOYMENT_TYPE, havingValue = Constants.NACOS_DEPLOYMENT_TYPE_MERGED) +public @interface EnabledInnerHandler { + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/HealthInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/HealthInnerHandler.java new file mode 100644 index 00000000000..33dcf827777 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/HealthInnerHandler.java @@ -0,0 +1,43 @@ +/* + * 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.console.handler.impl.inner; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.handler.HealthHandler; +import com.alibaba.nacos.core.cluster.health.ModuleHealthCheckerHolder; +import com.alibaba.nacos.core.cluster.health.ReadinessResult; +import org.springframework.stereotype.Service; + +/** + * Implementation of HealthHandler that performs health check operations. + * + * @author zhangyukun + */ +@Service +@EnabledInnerHandler +public class HealthInnerHandler implements HealthHandler { + + @Override + public Result checkReadiness() { + ReadinessResult result = ModuleHealthCheckerHolder.getInstance().checkReadiness(); + if (result.isSuccess()) { + return Result.success("ok"); + } + return Result.failure(result.getResultMessage()); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/ServerStateInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/ServerStateInnerHandler.java new file mode 100644 index 00000000000..f5d250ae29b --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/ServerStateInnerHandler.java @@ -0,0 +1,79 @@ +/* + * 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.console.handler.impl.inner; + +import com.alibaba.nacos.console.handler.ServerStateHandler; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.module.ModuleState; +import com.alibaba.nacos.sys.module.ModuleStateHolder; +import com.alibaba.nacos.sys.utils.DiskUtils; +import org.springframework.stereotype.Service; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +import static com.alibaba.nacos.common.utils.StringUtils.FOLDER_SEPARATOR; +import static com.alibaba.nacos.common.utils.StringUtils.TOP_PATH; +import static com.alibaba.nacos.common.utils.StringUtils.WINDOWS_FOLDER_SEPARATOR; + +/** + * Implementation of ServerStateHandler that performs server state operations. + * + * @author zhangyukun + */ +@Service +@EnabledInnerHandler +public class ServerStateInnerHandler implements ServerStateHandler { + + private static final String ANNOUNCEMENT_FILE = "announcement.conf"; + + private static final String GUIDE_FILE = "console-guide.conf"; + + public Map getServerState() { + Map serverState = new HashMap<>(4); + for (ModuleState each : ModuleStateHolder.getInstance().getAllModuleStates()) { + each.getStates().forEach((s, o) -> serverState.put(s, null == o ? null : o.toString())); + } + return serverState; + } + + @Override + public String getAnnouncement(String language) { + String file = ANNOUNCEMENT_FILE.substring(0, ANNOUNCEMENT_FILE.length() - 5) + "_" + language + ".conf"; + if (file.contains(TOP_PATH) || file.contains(FOLDER_SEPARATOR) || file.contains(WINDOWS_FOLDER_SEPARATOR)) { + throw new IllegalArgumentException("Invalid filename"); + } + File announcementFile = new File(EnvUtil.getConfPath(), file); + String announcement = null; + if (announcementFile.exists() && announcementFile.isFile()) { + announcement = DiskUtils.readFile(announcementFile); + } + return announcement; + } + + @Override + public String getConsoleUiGuide() { + File guideFile = new File(EnvUtil.getConfPath(), GUIDE_FILE); + String guideInformation = null; + if (guideFile.exists() && guideFile.isFile()) { + guideInformation = DiskUtils.readFile(guideFile); + } + return guideInformation; + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/config/ConfigInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/config/ConfigInnerHandler.java new file mode 100644 index 00000000000..8dfdb79f61f --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/config/ConfigInnerHandler.java @@ -0,0 +1,628 @@ +/* + * 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.console.handler.impl.inner.config; + +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.common.utils.DateFormatUtils; +import com.alibaba.nacos.common.utils.Pair; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.constant.Constants; +import com.alibaba.nacos.config.server.controller.ConfigServletInner; +import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigMetadata; +import com.alibaba.nacos.config.server.model.ConfigRequestInfo; +import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; +import com.alibaba.nacos.config.server.model.SameConfigPolicy; +import com.alibaba.nacos.config.server.model.SampleResult; +import com.alibaba.nacos.config.server.model.event.ConfigDataChangeEvent; +import com.alibaba.nacos.config.server.model.form.ConfigForm; +import com.alibaba.nacos.config.server.model.gray.BetaGrayRule; +import com.alibaba.nacos.config.server.service.ConfigChangePublisher; +import com.alibaba.nacos.config.server.service.ConfigDetailService; +import com.alibaba.nacos.config.server.service.ConfigOperationService; +import com.alibaba.nacos.config.server.service.ConfigSubService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoBetaPersistService; +import com.alibaba.nacos.config.server.service.repository.ConfigInfoPersistService; +import com.alibaba.nacos.config.server.service.trace.ConfigTraceService; +import com.alibaba.nacos.config.server.utils.GroupKey; +import com.alibaba.nacos.config.server.utils.GroupKey2; +import com.alibaba.nacos.config.server.utils.TimeUtils; +import com.alibaba.nacos.config.server.utils.YamlParserUtil; +import com.alibaba.nacos.config.server.utils.ZipUtils; +import com.alibaba.nacos.console.handler.config.ConfigHandler; +import com.alibaba.nacos.console.handler.impl.inner.EnabledInnerHandler; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.encryption.handler.EncryptionHandler; +import com.alibaba.nacos.sys.utils.InetUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.util.CollectionUtils; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletException; +import java.io.IOException; +import java.sql.Timestamp; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +/** + * Implementation of ConfigHandler for handling internal configuration operations. + * + * @author zhangyukun + */ +@Service +@EnabledInnerHandler +public class ConfigInnerHandler implements ConfigHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigInnerHandler.class); + + private static final String EXPORT_CONFIG_FILE_NAME = "nacos_config_export_"; + + private static final String EXPORT_CONFIG_FILE_NAME_EXT = ".zip"; + + private static final String EXPORT_CONFIG_FILE_NAME_DATE_FORMAT = "yyyyMMddHHmmss"; + + private final ConfigInfoPersistService configInfoPersistService; + + private final ConfigServletInner inner; + + private final ConfigOperationService configOperationService; + + private final ConfigDetailService configDetailService; + + private final ConfigSubService configSubService; + + private NamespacePersistService namespacePersistService; + + private ConfigInfoBetaPersistService configInfoBetaPersistService; + + public ConfigInnerHandler(ConfigServletInner inner, ConfigOperationService configOperationService, + ConfigInfoPersistService configInfoPersistService, ConfigDetailService configDetailService, + ConfigSubService configSubService, NamespacePersistService namespacePersistService, + ConfigInfoBetaPersistService configInfoBetaPersistService) { + this.inner = inner; + this.configOperationService = configOperationService; + this.configInfoPersistService = configInfoPersistService; + this.configDetailService = configDetailService; + this.configSubService = configSubService; + this.namespacePersistService = namespacePersistService; + this.configInfoBetaPersistService = configInfoBetaPersistService; + } + + @Override + public Page getConfigList(int pageNo, int pageSize, String dataId, String group, String namespaceId, + Map configAdvanceInfo) throws IOException, ServletException, NacosException { + return configInfoPersistService.findConfigInfoLike4Page(pageNo, pageSize, dataId, group, namespaceId, + configAdvanceInfo); + } + + @Override + public ConfigAllInfo getConfigDetail(String dataId, String group, String namespaceId) throws NacosException { + ConfigAllInfo configAllInfo = configInfoPersistService.findConfigAllInfo(dataId, group, namespaceId); + // decrypted + if (Objects.nonNull(configAllInfo)) { + String encryptedDataKey = configAllInfo.getEncryptedDataKey(); + Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, + configAllInfo.getContent()); + configAllInfo.setContent(pair.getSecond()); + } + return configAllInfo; + } + + @Override + public Boolean publishConfig(ConfigForm configForm, ConfigRequestInfo configRequestInfo) throws NacosException { + String encryptedDataKeyFinal = configForm.getEncryptedDataKey(); + if (StringUtils.isBlank(encryptedDataKeyFinal)) { + // encrypted + Pair pair = EncryptionHandler.encryptHandler(configForm.getDataId(), + configForm.getContent()); + configForm.setContent(pair.getSecond()); + encryptedDataKeyFinal = pair.getFirst(); + } + return configOperationService.publishConfig(configForm, configRequestInfo, encryptedDataKeyFinal); + } + + @Override + public Boolean deleteConfig(String dataId, String group, String namespaceId, String tag, String clientIp, + String srcUser) throws NacosException { + return configOperationService.deleteConfig(dataId, group, namespaceId, tag, clientIp, srcUser); + } + + @Override + public Boolean batchDeleteConfigs(List ids, String clientIp, String srcUser) { + final Timestamp time = TimeUtils.getCurrentTime(); + List configInfoList = configInfoPersistService.removeConfigInfoByIds(ids, clientIp, srcUser); + if (CollectionUtils.isEmpty(configInfoList)) { + return true; + } + for (ConfigInfo configInfo : configInfoList) { + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), + time.getTime())); + + ConfigTraceService.logPersistenceEvent(configInfo.getDataId(), configInfo.getGroup(), + configInfo.getTenant(), null, time.getTime(), clientIp, ConfigTraceService.PERSISTENCE_EVENT, + ConfigTraceService.PERSISTENCE_TYPE_REMOVE, null); + } + return true; + } + + @Override + public Page getConfigListByContent(String search, int pageNo, int pageSize, String dataId, String group, + String namespaceId, Map configAdvanceInfo) throws NacosException { + try { + return configDetailService.findConfigInfoPage(search, pageNo, pageSize, dataId, group, namespaceId, + configAdvanceInfo); + } catch (Exception e) { + String errorMsg = "serialize page error, dataId=" + dataId + ", group=" + group; + LOGGER.error(errorMsg, e); + throw e; + } + } + + @Override + public GroupkeyListenserStatus getListeners(String dataId, String group, String namespaceId, int sampleTime) + throws Exception { + SampleResult collectSampleResult = configSubService.getCollectSampleResult(dataId, group, namespaceId, + sampleTime); + GroupkeyListenserStatus gls = new GroupkeyListenserStatus(); + gls.setCollectStatus(200); + if (collectSampleResult.getLisentersGroupkeyStatus() != null) { + gls.setLisentersGroupkeyStatus(collectSampleResult.getLisentersGroupkeyStatus()); + } + return gls; + } + + @Override + public GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, + int sampleTime) { + SampleResult collectSampleResult = configSubService.getCollectSampleResultByIp(ip, sampleTime); + GroupkeyListenserStatus gls = new GroupkeyListenserStatus(); + gls.setCollectStatus(200); + Map configMd5Status = new HashMap<>(100); + + if (collectSampleResult.getLisentersGroupkeyStatus() == null) { + return gls; + } + + Map status = collectSampleResult.getLisentersGroupkeyStatus(); + for (Map.Entry config : status.entrySet()) { + if (!StringUtils.isBlank(namespaceId) && config.getKey().contains(namespaceId)) { + configMd5Status.put(config.getKey(), config.getValue()); + continue; + } + if (all) { + configMd5Status.put(config.getKey(), config.getValue()); + } else { + String[] configKeys = GroupKey2.parseKey(config.getKey()); + if (StringUtils.isBlank(configKeys[2])) { + configMd5Status.put(config.getKey(), config.getValue()); + } + } + } + gls.setLisentersGroupkeyStatus(configMd5Status); + return gls; + } + + @Override + public ResponseEntity exportConfig(String dataId, String group, String namespaceId, String appName, + List ids) throws Exception { + List dataList = configInfoPersistService.findAllConfigInfo4Export(dataId, group, namespaceId, + appName, ids); + List zipItemList = new ArrayList<>(); + StringBuilder metaData = null; + for (ConfigInfo ci : dataList) { + if (StringUtils.isNotBlank(ci.getAppName())) { + // Handle appName + if (metaData == null) { + metaData = new StringBuilder(); + } + String metaDataId = ci.getDataId(); + if (metaDataId.contains(".")) { + metaDataId = metaDataId.substring(0, metaDataId.lastIndexOf(".")) + "~" + metaDataId.substring( + metaDataId.lastIndexOf(".") + 1); + } + metaData.append(ci.getGroup()).append('.').append(metaDataId).append(".app=") + // Fixed use of "\r\n" here + .append(ci.getAppName()).append("\r\n"); + } + Pair pair = EncryptionHandler.decryptHandler(ci.getDataId(), ci.getEncryptedDataKey(), + ci.getContent()); + String itemName = ci.getGroup() + Constants.CONFIG_EXPORT_ITEM_FILE_SEPARATOR + ci.getDataId(); + zipItemList.add(new ZipUtils.ZipItem(itemName, pair.getSecond())); + } + if (metaData != null) { + zipItemList.add(new ZipUtils.ZipItem(Constants.CONFIG_EXPORT_METADATA, metaData.toString())); + } + + HttpHeaders headers = new HttpHeaders(); + String fileName = + EXPORT_CONFIG_FILE_NAME + DateFormatUtils.format(new Date(), EXPORT_CONFIG_FILE_NAME_DATE_FORMAT) + + EXPORT_CONFIG_FILE_NAME_EXT; + headers.add("Content-Disposition", "attachment;filename=" + fileName); + return new ResponseEntity<>(ZipUtils.zip(zipItemList), headers, HttpStatus.OK); + } + + @Override + public ResponseEntity exportConfigV2(String dataId, String group, String namespaceId, String appName, + List ids) throws Exception { + List dataList = configInfoPersistService.findAllConfigInfo4Export(dataId, group, namespaceId, + appName, ids); + List zipItemList = new ArrayList<>(); + List configMetadataItems = new ArrayList<>(); + for (ConfigAllInfo ci : dataList) { + ConfigMetadata.ConfigExportItem configMetadataItem = new ConfigMetadata.ConfigExportItem(); + configMetadataItem.setAppName(ci.getAppName()); + configMetadataItem.setDataId(ci.getDataId()); + configMetadataItem.setDesc(ci.getDesc()); + configMetadataItem.setGroup(ci.getGroup()); + configMetadataItem.setType(ci.getType()); + configMetadataItems.add(configMetadataItem); + Pair pair = EncryptionHandler.decryptHandler(ci.getDataId(), ci.getEncryptedDataKey(), + ci.getContent()); + String itemName = ci.getGroup() + Constants.CONFIG_EXPORT_ITEM_FILE_SEPARATOR + ci.getDataId(); + zipItemList.add(new ZipUtils.ZipItem(itemName, pair.getSecond())); + } + ConfigMetadata configMetadata = new ConfigMetadata(); + configMetadata.setMetadata(configMetadataItems); + zipItemList.add( + new ZipUtils.ZipItem(Constants.CONFIG_EXPORT_METADATA_NEW, YamlParserUtil.dumpObject(configMetadata))); + HttpHeaders headers = new HttpHeaders(); + String fileName = + EXPORT_CONFIG_FILE_NAME + DateFormatUtils.format(new Date(), EXPORT_CONFIG_FILE_NAME_DATE_FORMAT) + + EXPORT_CONFIG_FILE_NAME_EXT; + headers.add("Content-Disposition", "attachment;filename=" + fileName); + return new ResponseEntity<>(ZipUtils.zip(zipItemList), headers, HttpStatus.OK); + } + + @Override + public Result> importAndPublishConfig(String srcUser, String namespaceId, + SameConfigPolicy policy, MultipartFile file, String srcIp, String requestIpApp) throws NacosException { + Map failedData = new HashMap<>(4); + if (Objects.isNull(file)) { + return Result.failure(ErrorCode.DATA_EMPTY, failedData); + } + if (StringUtils.isNotBlank(namespaceId) + && namespacePersistService.tenantInfoCountByTenantId(namespaceId) <= 0) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.NAMESPACE_NOT_EXIST, failedData); + } + + List configInfoList = new ArrayList<>(); + List> unrecognizedList = new ArrayList<>(); + try { + ZipUtils.UnZipResult unziped = ZipUtils.unzip(file.getBytes()); + ZipUtils.ZipItem metaDataZipItem = unziped.getMetaDataItem(); + Result> errorResult; + if (metaDataZipItem != null && Constants.CONFIG_EXPORT_METADATA_NEW.equals(metaDataZipItem.getItemName())) { + // new export + errorResult = parseImportDataV2(srcUser, unziped, configInfoList, unrecognizedList, namespaceId); + } else { + errorResult = parseImportData(srcUser, unziped, configInfoList, unrecognizedList, namespaceId); + } + if (errorResult != null) { + return errorResult; + } + } catch (IOException e) { + failedData.put("succCount", 0); + LOGGER.error("parsing data failed", e); + return Result.failure(ErrorCode.PARSING_DATA_FAILED, failedData); + } + + if (CollectionUtils.isEmpty(configInfoList)) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.DATA_EMPTY, failedData); + } + final Timestamp time = TimeUtils.getCurrentTime(); + Map saveResult = configInfoPersistService.batchInsertOrUpdate(configInfoList, srcUser, srcIp, + null, policy); + for (ConfigInfo configInfo : configInfoList) { + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent(configInfo.getDataId(), configInfo.getGroup(), configInfo.getTenant(), + time.getTime())); + ConfigTraceService.logPersistenceEvent(configInfo.getDataId(), configInfo.getGroup(), + configInfo.getTenant(), requestIpApp, time.getTime(), InetUtils.getSelfIP(), + ConfigTraceService.PERSISTENCE_EVENT, ConfigTraceService.PERSISTENCE_TYPE_PUB, + configInfo.getContent()); + } + // unrecognizedCount + if (!unrecognizedList.isEmpty()) { + saveResult.put("unrecognizedCount", unrecognizedList.size()); + saveResult.put("unrecognizedData", unrecognizedList); + } + return Result.success(saveResult); + } + + /** + * old import config. + * + * @param unziped export file. + * @param configInfoList parse file result. + * @param unrecognizedList unrecognized file. + * @param namespace import namespace. + * @return error result. + */ + private Result> parseImportData(String srcUser, ZipUtils.UnZipResult unziped, + List configInfoList, List> unrecognizedList, String namespace) { + ZipUtils.ZipItem metaDataZipItem = unziped.getMetaDataItem(); + + Map metaDataMap = new HashMap<>(16); + if (metaDataZipItem != null) { + // compatible all file separator + String metaDataStr = metaDataZipItem.getItemData().replaceAll("[\r\n]+", "|"); + String[] metaDataArr = metaDataStr.split("\\|"); + Map failedData = new HashMap<>(4); + for (String metaDataItem : metaDataArr) { + String[] metaDataItemArr = metaDataItem.split("="); + if (metaDataItemArr.length != 2) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.METADATA_ILLEGAL, failedData); + } + metaDataMap.put(metaDataItemArr[0], metaDataItemArr[1]); + } + } + + List itemList = unziped.getZipItemList(); + if (itemList != null && !itemList.isEmpty()) { + for (ZipUtils.ZipItem item : itemList) { + String[] groupAdnDataId = item.getItemName().split(Constants.CONFIG_EXPORT_ITEM_FILE_SEPARATOR); + if (groupAdnDataId.length != 2) { + Map unrecognizedItem = new HashMap<>(2); + unrecognizedItem.put("itemName", item.getItemName()); + unrecognizedList.add(unrecognizedItem); + continue; + } + String group = groupAdnDataId[0]; + String dataId = groupAdnDataId[1]; + String tempDataId = dataId; + if (tempDataId.contains(".")) { + tempDataId = tempDataId.substring(0, tempDataId.lastIndexOf(".")) + "~" + tempDataId.substring( + tempDataId.lastIndexOf(".") + 1); + } + final String metaDataId = group + "." + tempDataId + ".app"; + + //encrypted + String content = item.getItemData(); + Pair pair = EncryptionHandler.encryptHandler(dataId, content); + content = pair.getSecond(); + + ConfigAllInfo ci = new ConfigAllInfo(); + ci.setGroup(group); + ci.setDataId(dataId); + ci.setContent(content); + if (metaDataMap.get(metaDataId) != null) { + ci.setAppName(metaDataMap.get(metaDataId)); + } + ci.setTenant(namespace); + ci.setEncryptedDataKey(pair.getFirst()); + ci.setCreateUser(srcUser); + configInfoList.add(ci); + } + } + return null; + } + + /** + * new version import config add .metadata.yml file. + * + * @param unziped export file. + * @param configInfoList parse file result. + * @param unrecognizedList unrecognized file. + * @param namespace import namespace. + * @return error result. + */ + private Result> parseImportDataV2(String srcUser, ZipUtils.UnZipResult unziped, + List configInfoList, List> unrecognizedList, String namespace) { + ZipUtils.ZipItem metaDataItem = unziped.getMetaDataItem(); + String metaData = metaDataItem.getItemData(); + Map failedData = new HashMap<>(4); + + ConfigMetadata configMetadata = YamlParserUtil.loadObject(metaData, ConfigMetadata.class); + if (configMetadata == null || CollectionUtils.isEmpty(configMetadata.getMetadata())) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.METADATA_ILLEGAL, failedData); + } + List configExportItems = configMetadata.getMetadata(); + // check config metadata + for (ConfigMetadata.ConfigExportItem configExportItem : configExportItems) { + if (StringUtils.isBlank(configExportItem.getDataId()) || StringUtils.isBlank(configExportItem.getGroup()) + || StringUtils.isBlank(configExportItem.getType())) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.METADATA_ILLEGAL, failedData); + } + } + + List zipItemList = unziped.getZipItemList(); + Set metaDataKeys = configExportItems.stream() + .map(metaItem -> GroupKey.getKey(metaItem.getDataId(), metaItem.getGroup())) + .collect(Collectors.toSet()); + + Map configContentMap = new HashMap<>(zipItemList.size()); + int itemNameLength = 2; + zipItemList.forEach(item -> { + String itemName = item.getItemName(); + String[] groupAdnDataId = itemName.split(Constants.CONFIG_EXPORT_ITEM_FILE_SEPARATOR); + if (groupAdnDataId.length != itemNameLength) { + Map unrecognizedItem = new HashMap<>(2); + unrecognizedItem.put("itemName", item.getItemName()); + unrecognizedList.add(unrecognizedItem); + return; + } + + String group = groupAdnDataId[0]; + String dataId = groupAdnDataId[1]; + String key = GroupKey.getKey(dataId, group); + // metadata does not contain config file + if (!metaDataKeys.contains(key)) { + Map unrecognizedItem = new HashMap<>(2); + unrecognizedItem.put("itemName", "未在元数据中找到: " + item.getItemName()); + unrecognizedList.add(unrecognizedItem); + return; + } + String itemData = item.getItemData(); + configContentMap.put(key, itemData); + }); + + for (ConfigMetadata.ConfigExportItem configExportItem : configExportItems) { + String dataId = configExportItem.getDataId(); + String group = configExportItem.getGroup(); + String content = configContentMap.get(GroupKey.getKey(dataId, group)); + // config file not in metadata + if (content == null) { + Map unrecognizedItem = new HashMap<>(2); + unrecognizedItem.put("itemName", "未在文件中找到: " + group + "/" + dataId); + unrecognizedList.add(unrecognizedItem); + continue; + } + // encrypted + Pair pair = EncryptionHandler.encryptHandler(dataId, content); + content = pair.getSecond(); + + ConfigAllInfo ci = new ConfigAllInfo(); + ci.setGroup(group); + ci.setDataId(dataId); + ci.setContent(content); + ci.setType(configExportItem.getType()); + ci.setDesc(configExportItem.getDesc()); + ci.setAppName(configExportItem.getAppName()); + ci.setTenant(namespace); + ci.setEncryptedDataKey(pair.getFirst()); + ci.setCreateUser(srcUser); + configInfoList.add(ci); + } + return null; + } + + @Override + public Result> cloneConfig(String srcUser, String namespaceId, + List configBeansList, SameConfigPolicy policy, String srcIp, + String requestIpApp) throws NacosException { + Map failedData = new HashMap<>(4); + if (CollectionUtils.isEmpty(configBeansList)) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.NO_SELECTED_CONFIG, failedData); + } + if (StringUtils.isNotBlank(namespaceId) + && namespacePersistService.tenantInfoCountByTenantId(namespaceId) <= 0) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.NAMESPACE_NOT_EXIST, failedData); + } + + List idList = new ArrayList<>(configBeansList.size()); + Map configBeansMap = configBeansList.stream() + .collect(Collectors.toMap(SameNamespaceCloneConfigBean::getCfgId, cfg -> { + idList.add(cfg.getCfgId()); + return cfg; + }, (k1, k2) -> k1)); + + List queryedDataList = configInfoPersistService.findAllConfigInfo4Export(null, null, null, null, + idList); + + if (queryedDataList == null || queryedDataList.isEmpty()) { + failedData.put("succCount", 0); + return Result.failure(ErrorCode.DATA_EMPTY, failedData); + } + + List configInfoList4Clone = new ArrayList<>(queryedDataList.size()); + + for (ConfigAllInfo ci : queryedDataList) { + SameNamespaceCloneConfigBean paramBean = configBeansMap.get(ci.getId()); + ConfigAllInfo ci4save = new ConfigAllInfo(); + ci4save.setTenant(namespaceId); + ci4save.setType(ci.getType()); + ci4save.setGroup((paramBean != null && StringUtils.isNotBlank(paramBean.getGroup())) ? paramBean.getGroup() + : ci.getGroup()); + ci4save.setDataId( + (paramBean != null && StringUtils.isNotBlank(paramBean.getDataId())) ? paramBean.getDataId() + : ci.getDataId()); + ci4save.setContent(ci.getContent()); + if (StringUtils.isNotBlank(ci.getAppName())) { + ci4save.setAppName(ci.getAppName()); + } + ci4save.setDesc(ci.getDesc()); + ci4save.setEncryptedDataKey( + ci.getEncryptedDataKey() == null ? StringUtils.EMPTY : ci.getEncryptedDataKey()); + configInfoList4Clone.add(ci4save); + } + + final Timestamp time = TimeUtils.getCurrentTime(); + Map saveResult = configInfoPersistService.batchInsertOrUpdate(configInfoList4Clone, srcUser, + srcIp, null, policy); + for (ConfigInfo configInfo : configInfoList4Clone) { + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent(configInfo.getDataId(), configInfo.getGroup(), + configInfo.getTenant(), time.getTime())); + ConfigTraceService.logPersistenceEvent(configInfo.getDataId(), configInfo.getGroup(), + configInfo.getTenant(), requestIpApp, time.getTime(), InetUtils.getSelfIP(), + ConfigTraceService.PERSISTENCE_EVENT, ConfigTraceService.PERSISTENCE_TYPE_PUB, + configInfo.getContent()); + } + return Result.success(saveResult); + } + + @Override + public boolean removeBetaConfig(String dataId, String group, String namespaceId, String remoteIp, + String requestIpApp) { + try { + configInfoBetaPersistService.removeConfigInfo4Beta(dataId, group, namespaceId); + } catch (Throwable e) { + LOGGER.error("remove beta data error", e); + return false; + } + ConfigTraceService.logPersistenceEvent(dataId, group, namespaceId, requestIpApp, System.currentTimeMillis(), + remoteIp, ConfigTraceService.PERSISTENCE_EVENT_BETA, ConfigTraceService.PERSISTENCE_TYPE_REMOVE, null); + ConfigChangePublisher.notifyConfigChange( + new ConfigDataChangeEvent(dataId, group, namespaceId, BetaGrayRule.TYPE_BETA, System.currentTimeMillis())); + return true; + + } + + @Override + public Result queryBetaConfig(String dataId, String group, String namespaceId) { + try { + ConfigInfo4Beta ci = configInfoBetaPersistService.findConfigInfo4Beta(dataId, group, namespaceId); + + if (Objects.nonNull(ci)) { + String encryptedDataKey = ci.getEncryptedDataKey(); + Pair pair = EncryptionHandler.decryptHandler(dataId, encryptedDataKey, ci.getContent()); + ci.setContent(pair.getSecond()); + } + return Result.success(ci); + } catch (Throwable e) { + LOGGER.error("query beta data error", e); + return Result.failure(HttpStatus.INTERNAL_SERVER_ERROR.value(), + HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), null); + } + } + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/config/HistoryInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/config/HistoryInnerHandler.java new file mode 100644 index 00000000000..472795204e6 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/config/HistoryInnerHandler.java @@ -0,0 +1,87 @@ +/* + * 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.console.handler.impl.inner.config; + +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.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.config.server.service.HistoryService; +import com.alibaba.nacos.console.handler.config.HistoryHandler; +import com.alibaba.nacos.console.handler.impl.inner.EnabledInnerHandler; +import com.alibaba.nacos.persistence.model.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.dao.DataAccessException; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * . + * + * @author zhangyukun on:2024/8/16 + */ +@Service +@EnabledInnerHandler +public class HistoryInnerHandler implements HistoryHandler { + + private final HistoryService historyService; + + @Autowired + public HistoryInnerHandler(HistoryService historyService) { + this.historyService = historyService; + } + + @Override + public ConfigHistoryInfo getConfigHistoryInfo(String dataId, String group, String namespaceId, Long nid) + throws NacosException { + ConfigHistoryInfo configHistoryInfo; + try { + configHistoryInfo = historyService.getConfigHistoryInfo(dataId, group, namespaceId, nid); + } catch (DataAccessException e) { + throw new NacosApiException(HttpStatus.NOT_FOUND.value(), ErrorCode.RESOURCE_NOT_FOUND, + "certain config history for nid = " + nid + " not exist"); + } + return configHistoryInfo; + } + + @Override + public Page listConfigHistory(String dataId, String group, String namespaceId, Integer pageNo, + Integer pageSize) throws NacosException { + return historyService.listConfigHistory(dataId, group, namespaceId, pageNo, pageSize); + } + + @Override + public ConfigHistoryInfo getPreviousConfigHistoryInfo(String dataId, String group, String namespaceId, Long id) + throws NacosException { + ConfigHistoryInfo configHistoryInfo; + try { + configHistoryInfo = historyService.getPreviousConfigHistoryInfo(dataId, group, namespaceId, id); + } catch (DataAccessException e) { + throw new NacosApiException(HttpStatus.NOT_FOUND.value(), ErrorCode.RESOURCE_NOT_FOUND, + "previous config history for id = " + id + " not exist"); + } + return configHistoryInfo; + } + + @Override + public List getConfigsByTenant(String namespaceId) throws NacosApiException { + return historyService.getConfigListByNamespace(namespaceId); + } +} \ No newline at end of file diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/core/ClusterInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/core/ClusterInnerHandler.java new file mode 100644 index 00000000000..f8b7675b2f3 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/core/ClusterInnerHandler.java @@ -0,0 +1,75 @@ +/* + * 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.console.handler.impl.inner.core; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.console.handler.core.ClusterHandler; +import com.alibaba.nacos.console.handler.impl.inner.EnabledInnerHandler; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Implementation of ClusterHandler that handles cluster-related operations. + * + * @author zhangyukun + */ +@Service +@EnabledInnerHandler +public class ClusterInnerHandler implements ClusterHandler { + + private final ServerMemberManager memberManager; + + /** + * Constructs a new ClusterInnerHandler with the provided dependencies. + * + * @param memberManager the manager for server members + */ + @Autowired + public ClusterInnerHandler(ServerMemberManager memberManager) { + this.memberManager = memberManager; + } + + /** + * Retrieves a list of cluster members with an optional search keyword. + * + * @param ipKeyWord the search keyword for filtering members + * @return a collection of matching members + */ + @Override + public Collection getNodeList(String ipKeyWord) { + Collection members = memberManager.allMembers(); + Collection result = new ArrayList<>(); + + members.stream().sorted().forEach(member -> { + if (StringUtils.isBlank(ipKeyWord)) { + result.add(member); + return; + } + final String address = member.getAddress(); + if (StringUtils.equals(address, ipKeyWord) || StringUtils.startsWith(address, ipKeyWord)) { + result.add(member); + } + }); + + return result; + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/core/NamespaceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/core/NamespaceInnerHandler.java new file mode 100644 index 00000000000..7c20f665cad --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/core/NamespaceInnerHandler.java @@ -0,0 +1,81 @@ +/* + * 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.console.handler.impl.inner.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.console.handler.core.NamespaceHandler; +import com.alibaba.nacos.console.handler.impl.inner.EnabledInnerHandler; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import com.alibaba.nacos.core.namespace.repository.NamespacePersistService; +import com.alibaba.nacos.core.service.NamespaceOperationService; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Implementation of NamespaceHandler that handles namespace-related operations. + * + * @author zhangyukun + */ +@Service +@EnabledInnerHandler +public class NamespaceInnerHandler implements NamespaceHandler { + + private final NamespaceOperationService namespaceOperationService; + + private final NamespacePersistService namespacePersistService; + + public NamespaceInnerHandler(NamespaceOperationService namespaceOperationService, + NamespacePersistService namespacePersistService) { + this.namespaceOperationService = namespaceOperationService; + this.namespacePersistService = namespacePersistService; + } + + @Override + public List getNamespaceList() { + return namespaceOperationService.getNamespaceList(); + } + + @Override + public Namespace getNamespaceDetail(String namespaceId) throws NacosException { + return namespaceOperationService.getNamespace(namespaceId); + } + + @Override + public Boolean createNamespace(String namespaceId, String namespaceName, String namespaceDesc) + throws NacosException { + return namespaceOperationService.createNamespace(namespaceId, namespaceName, namespaceDesc); + } + + @Override + public Boolean updateNamespace(NamespaceForm namespaceForm) throws NacosException { + return namespaceOperationService.editNamespace(namespaceForm.getNamespaceId(), namespaceForm.getNamespaceName(), + namespaceForm.getNamespaceDesc()); + } + + @Override + public Boolean deleteNamespace(String namespaceId) { + return namespaceOperationService.removeNamespace(namespaceId); + } + + @Override + public Boolean checkNamespaceIdExist(String namespaceId) { + return (namespacePersistService.tenantInfoCountByTenantId(namespaceId) > 0); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/naming/InstanceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/naming/InstanceInnerHandler.java new file mode 100644 index 00000000000..56c8e3c65ec --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/naming/InstanceInnerHandler.java @@ -0,0 +1,138 @@ +/* + * 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.console.handler.impl.inner.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.trace.event.naming.UpdateInstanceTraceEvent; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.console.handler.impl.inner.EnabledInnerHandler; +import com.alibaba.nacos.console.handler.naming.InstanceHandler; +import com.alibaba.nacos.naming.core.CatalogServiceV2Impl; +import com.alibaba.nacos.naming.core.InstanceOperatorClientImpl; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.alibaba.nacos.naming.model.form.InstanceMetadataBatchOperationForm; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Implementation of InstanceHandler that handles instance-related operations. + * + * @author zhangyukun + */ +@Service +@EnabledInnerHandler +public class InstanceInnerHandler implements InstanceHandler { + + private final CatalogServiceV2Impl catalogServiceV2; + + private final InstanceOperatorClientImpl instanceServiceV2; + + /** + * Constructs a new InstanceInnerHandler with the provided dependencies. + * + * @param catalogServiceV2 the service for catalog-related operations + */ + @Autowired + public InstanceInnerHandler(CatalogServiceV2Impl catalogServiceV2, InstanceOperatorClientImpl instanceServiceV2) { + this.catalogServiceV2 = catalogServiceV2; + this.instanceServiceV2 = instanceServiceV2; + } + + /** + * Retrieves a list of instances for a specific service and returns as an ObjectNode. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @param page the page number + * @param pageSize the size of the page + * @param healthyOnly filter by healthy instances only + * @param enabledOnly filter by enabled instances only + * @return a JSON node containing the instances information + */ + @Override + public ObjectNode listInstances(String namespaceId, String serviceNameWithoutGroup, String groupName, int page, + int pageSize, Boolean healthyOnly, Boolean enabledOnly) { + List instances = catalogServiceV2.listAllInstances(namespaceId, groupName, + serviceNameWithoutGroup); + int start = (page - 1) * pageSize; + + if (start < 0) { + start = 0; + } + int end = start + pageSize; + + if (start > instances.size()) { + start = instances.size(); + } + + if (end > instances.size()) { + end = instances.size(); + } + + Stream stream = instances.stream(); + if (healthyOnly != null) { + stream = stream.filter(instance -> instance.isHealthy() == healthyOnly); + } + if (enabledOnly != null) { + stream = stream.filter(i -> i.isEnabled() == enabledOnly); + } + List ins = stream.collect(Collectors.toList()); + + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + if (ins.size() > start) { + result.replace("instances", JacksonUtils.transferToJsonNode(ins.subList(start, end))); + } + result.put("count", ins.size()); + + return result; + } + + /** + * Updates an instance. + * + * @param instanceForm the instanceForm + * @param instance the instance to update + * @throws NacosException if the update operation fails + */ + @Override + public void updateInstance(InstanceForm instanceForm, Instance instance) throws NacosException { + instanceServiceV2.updateInstance(instanceForm.getNamespaceId(), buildCompositeServiceName(instanceForm), + instance); + NotifyCenter.publishEvent( + new UpdateInstanceTraceEvent(System.currentTimeMillis(), "", instanceForm.getNamespaceId(), + instanceForm.getGroupName(), instanceForm.getServiceName(), instance.getIp(), + instance.getPort(), instance.getMetadata())); + } + + private String buildCompositeServiceName(InstanceForm instanceForm) { + return NamingUtils.getGroupedName(instanceForm.getServiceName(), instanceForm.getGroupName()); + } + + private String buildCompositeServiceName(InstanceMetadataBatchOperationForm form) { + return NamingUtils.getGroupedName(form.getServiceName(), form.getGroupName()); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/naming/ServiceInnerHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/naming/ServiceInnerHandler.java new file mode 100644 index 00000000000..e8b6cf8630d --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/inner/naming/ServiceInnerHandler.java @@ -0,0 +1,163 @@ +/* + * 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.console.handler.impl.inner.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.trace.event.naming.DeregisterServiceTraceEvent; +import com.alibaba.nacos.common.trace.event.naming.RegisterServiceTraceEvent; +import com.alibaba.nacos.common.trace.event.naming.UpdateServiceTraceEvent; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.console.handler.impl.inner.EnabledInnerHandler; +import com.alibaba.nacos.console.handler.naming.ServiceHandler; +import com.alibaba.nacos.naming.core.CatalogServiceV2Impl; +import com.alibaba.nacos.naming.core.ClusterOperatorV2Impl; +import com.alibaba.nacos.naming.core.ServiceOperatorV2Impl; +import com.alibaba.nacos.naming.core.SubscribeManager; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.pojo.Subscriber; +import com.alibaba.nacos.naming.selector.SelectorManager; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.Map; + +/** + * Implementation of ServiceHandler that handles service-related operations. + * + * @author zhangyukun + */ +@org.springframework.stereotype.Service +@EnabledInnerHandler +public class ServiceInnerHandler implements ServiceHandler { + + private final ServiceOperatorV2Impl serviceOperatorV2; + + private final SelectorManager selectorManager; + + private final CatalogServiceV2Impl catalogServiceV2; + + private final SubscribeManager subscribeManager; + + private final ClusterOperatorV2Impl clusterOperatorV2; + + @Autowired + public ServiceInnerHandler(ServiceOperatorV2Impl serviceOperatorV2, SelectorManager selectorManager, + CatalogServiceV2Impl catalogServiceV2, SubscribeManager subscribeManager, + ClusterOperatorV2Impl clusterOperatorV2) { + this.serviceOperatorV2 = serviceOperatorV2; + this.selectorManager = selectorManager; + this.catalogServiceV2 = catalogServiceV2; + this.subscribeManager = subscribeManager; + this.clusterOperatorV2 = clusterOperatorV2; + } + + @Override + public void createService(ServiceForm serviceForm, ServiceMetadata serviceMetadata) throws Exception { + serviceOperatorV2.create(com.alibaba.nacos.naming.core.v2.pojo.Service.newService(serviceForm.getNamespaceId(), + serviceForm.getGroupName(), serviceForm.getServiceName(), serviceForm.getEphemeral()), serviceMetadata); + NotifyCenter.publishEvent( + new RegisterServiceTraceEvent(System.currentTimeMillis(), serviceForm.getNamespaceId(), + serviceForm.getGroupName(), serviceForm.getServiceName())); + } + + @Override + public void deleteService(String namespaceId, String serviceName, String groupName) throws Exception { + serviceOperatorV2.delete( + com.alibaba.nacos.naming.core.v2.pojo.Service.newService(namespaceId, groupName, serviceName)); + NotifyCenter.publishEvent( + new DeregisterServiceTraceEvent(System.currentTimeMillis(), namespaceId, groupName, serviceName)); + } + + @Override + public void updateService(ServiceForm serviceForm, com.alibaba.nacos.naming.core.v2.pojo.Service service, + ServiceMetadata serviceMetadata, Map metadata) throws Exception { + serviceOperatorV2.update(service, serviceMetadata); + NotifyCenter.publishEvent(new UpdateServiceTraceEvent(System.currentTimeMillis(), serviceForm.getNamespaceId(), + serviceForm.getGroupName(), serviceForm.getServiceName(), metadata)); + } + + @Override + public List getSelectorTypeList() { + return selectorManager.getAllSelectorTypes(); + } + + @Override + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, + boolean aggregation) throws Exception { + + Service service = Service.newService(namespaceId, groupName, serviceName); + + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + + int count = 0; + + try { + List subscribers = subscribeManager.getSubscribers(service, aggregation); + + int start = (pageNo - 1) * pageSize; + if (start < 0) { + start = 0; + } + + int end = start + pageSize; + count = subscribers.size(); + if (end > count) { + end = count; + } + + result.replace("subscribers", JacksonUtils.transferToJsonNode(subscribers.subList(start, end))); + result.put("count", count); + + return result; + } catch (Exception e) { + Loggers.SRV_LOG.warn("query subscribers failed!", e); + result.replace("subscribers", JacksonUtils.createEmptyArrayNode()); + result.put("count", count); + return result; + } + } + + @Override + public Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int pageSize, + String serviceName, String groupName, boolean hasIpCount) throws NacosException { + if (withInstances) { + return catalogServiceV2.pageListServiceDetail(namespaceId, groupName, serviceName, pageNo, pageSize); + } + return catalogServiceV2.pageListService(namespaceId, groupName, serviceName, pageNo, pageSize, + StringUtils.EMPTY, hasIpCount); + } + + @Override + public Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException { + return catalogServiceV2.getServiceDetail(namespaceId, groupName, serviceName); + } + + @Override + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception { + clusterOperatorV2.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + } + +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/impl/remote/EnabledRemoteHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/impl/remote/EnabledRemoteHandler.java new file mode 100644 index 00000000000..296677baafd --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/impl/remote/EnabledRemoteHandler.java @@ -0,0 +1,39 @@ +/* + * 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.console.handler.impl.remote; + +import com.alibaba.nacos.sys.env.Constants; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Using Remote handler to call Nacos Admin API to handle console API request. + * + * @author xiweng.yy + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@ConditionalOnProperty(value = Constants.NACOS_DEPLOYMENT_TYPE, havingValue = Constants.NACOS_DEPLOYMENT_TYPE_CONSOLE) +public @interface EnabledRemoteHandler { + +} diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/naming/InstanceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/naming/InstanceHandler.java new file mode 100644 index 00000000000..91b27803ba3 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/naming/InstanceHandler.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2024 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.console.handler.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; + +/** + * Interface for handling instance-related operations. + * + * @author zhangyukun + */ +public interface InstanceHandler { + + /** + * Retrieve a list of instances for a specific service and returns as an ObjectNode. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @param page the page number + * @param pageSize the size of the page + * @param healthyOnly filter by healthy instances only + * @param enabledOnly filter by enabled instances only + * @return a JSON node containing the instances information + */ + ObjectNode listInstances(String namespaceId, String serviceNameWithoutGroup, String groupName, + int page, int pageSize, Boolean healthyOnly, Boolean enabledOnly); + + /** + * Update an instance. + * + * @param instanceForm the form containing instance data + * @param instance the instance to update + * @throws NacosException if the update operation fails + */ + void updateInstance(InstanceForm instanceForm, Instance instance) throws NacosException; +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java new file mode 100644 index 00000000000..3ff7c91c1ac --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/handler/naming/ServiceHandler.java @@ -0,0 +1,129 @@ +/* + * Copyright 1999-2024 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.console.handler.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; + +import java.util.List; +import java.util.Map; + +/** + * Interface for handling service-related operations. + * + * @author zhangyukun + */ +public interface ServiceHandler { + + /** + * Create a new service. + * + * @param serviceForm the service form containing the service details + * @param serviceMetadata the service metadata created from serviceForm + * @throws Exception if an error occurs during service creation + */ + void createService(ServiceForm serviceForm, ServiceMetadata serviceMetadata) throws Exception; + + /** + * Delete an existing service. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param groupName the group name + * @throws Exception if an error occurs during service deletion + */ + void deleteService(String namespaceId, String serviceName, String groupName) throws Exception; + + /** + * Update an existing service. + * + * @param serviceForm the service form containing the service details + * @param service the service object created from serviceForm + * @param serviceMetadata the service metadata created from serviceForm + * @param metadata the service metadata + * @throws Exception if an error occurs during service update + */ + void updateService(ServiceForm serviceForm, Service service, ServiceMetadata serviceMetadata, + Map metadata) throws Exception; + + /** + * Get all selector types. + * + * @return a list of selector types + */ + List getSelectorTypeList(); + + /** + * Get the list of subscribers for a service. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param groupName the group name + * @param aggregation whether to aggregate the results + * @return a JSON node containing the list of subscribers + * @throws Exception if an error occurs during fetching subscribers + */ + ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, + boolean aggregation) throws Exception; + + /** + * List service detail information. + * + * @param withInstances whether to include instances + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the size of the page + * @param serviceName the service name + * @param groupName the group name + * @param hasIpCount whether to filter services with empty instances + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int pageSize, String serviceName, + String groupName, boolean hasIpCount) throws NacosException; + + /** + * Get the detail of a specific service. + * + * @param namespaceId the namespace ID + * @param serviceName the service name without group + * @param groupName the group name + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException; + + /** + * Update the metadata of a cluster. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param clusterName the cluster name + * @param clusterMetadata the metadata for the cluster + * @throws Exception if the update operation fails + */ + void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception; +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/HealthProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/HealthProxy.java new file mode 100644 index 00000000000..eb7e364f3ad --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/HealthProxy.java @@ -0,0 +1,47 @@ +/* + * Copyright 1999-2024 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.console.proxy; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.handler.HealthHandler; +import org.springframework.stereotype.Service; + +/** + * Proxy class for handling health check operations. + * + * @author zhangyukun + */ +@Service +public class HealthProxy { + + private final HealthHandler healthHandler; + + public HealthProxy(HealthHandler healthHandler) { + this.healthHandler = healthHandler; + } + + /** + * Perform readiness check to determine if Nacos is ready to handle requests. + * + * @return readiness result + */ + public Result checkReadiness() { + return healthHandler.checkReadiness(); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/ServerStateProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/ServerStateProxy.java new file mode 100644 index 00000000000..a38ba929485 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/ServerStateProxy.java @@ -0,0 +1,67 @@ +/* + * Copyright 1999-2024 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.console.proxy; + +import com.alibaba.nacos.console.handler.ServerStateHandler; +import org.springframework.stereotype.Service; + +import java.util.Map; + +/** + * Proxy class for handling server state operations. + * + * @author zhangyukun + */ +@Service +public class ServerStateProxy { + + private final ServerStateHandler serverStateHandler; + + public ServerStateProxy(ServerStateHandler serverStateHandler) { + this.serverStateHandler = serverStateHandler; + } + + /** + * Get the current state of the server. + * + * @return the server state as a Map + */ + public Map getServerState() { + return serverStateHandler.getServerState(); + } + + /** + * Get the announcement content based on the language. + * + * @param language the language for the announcement + * @return the announcement content as a String + */ + public String getAnnouncement(String language) { + return serverStateHandler.getAnnouncement(language); + } + + /** + * Get the console UI guide information. + * + * @return the console UI guide information as a String + */ + public String getConsoleUiGuide() { + return serverStateHandler.getConsoleUiGuide(); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java new file mode 100644 index 00000000000..7d92f389f3f --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/config/ConfigProxy.java @@ -0,0 +1,167 @@ +/* + * Copyright 1999-2024 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.console.proxy.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigRequestInfo; +import com.alibaba.nacos.config.server.model.GroupkeyListenserStatus; +import com.alibaba.nacos.config.server.model.SameConfigPolicy; +import com.alibaba.nacos.config.server.model.form.ConfigForm; +import com.alibaba.nacos.console.handler.config.ConfigHandler; +import com.alibaba.nacos.persistence.model.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import javax.servlet.ServletException; +import java.io.IOException; +import java.util.List; +import java.util.Map; + +/** + * Proxy class for handling configuration operations. + * + * @author zhangyukun + */ +@Service +public class ConfigProxy { + + private final ConfigHandler configHandler; + + @Autowired + public ConfigProxy(ConfigHandler configHandler) { + this.configHandler = configHandler; + } + + /** + * Get configure information list. + */ + public Page getConfigList(int pageNo, int pageSize, String dataId, String group, String namespaceId, + Map configAdvanceInfo) throws IOException, ServletException, NacosException { + return configHandler.getConfigList(pageNo, pageSize, dataId, group, namespaceId, configAdvanceInfo); + } + + /** + * Get the specific configuration information. + */ + public ConfigAllInfo getConfigDetail(String dataId, String group, String namespaceId) throws NacosException { + return configHandler.getConfigDetail(dataId, group, namespaceId); + } + + /** + * Add or update configuration. + */ + public Boolean publishConfig(ConfigForm configForm, ConfigRequestInfo configRequestInfo) throws NacosException { + return configHandler.publishConfig(configForm, configRequestInfo); + } + + /** + * Delete configuration. + */ + public Boolean deleteConfig(String dataId, String group, String namespaceId, String tag, String clientIp, + String srcUser) throws NacosException { + return configHandler.deleteConfig(dataId, group, namespaceId, tag, clientIp, srcUser); + } + + /** + * Batch delete configurations. + */ + public Boolean batchDeleteConfigs(List ids, String clientIp, String srcUser) throws NacosException { + return configHandler.batchDeleteConfigs(ids, clientIp, srcUser); + } + + /** + * Search config list by config detail. + */ + public Page getConfigListByContent(String search, int pageNo, int pageSize, String dataId, String group, + String namespaceId, Map configAdvanceInfo) throws NacosException { + return configHandler.getConfigListByContent(search, pageNo, pageSize, dataId, group, namespaceId, + configAdvanceInfo); + } + + /** + * Subscribe to configured client information. + */ + public GroupkeyListenserStatus getListeners(String dataId, String group, String namespaceId, int sampleTime) + throws Exception { + return configHandler.getListeners(dataId, group, namespaceId, sampleTime); + } + + /** + * Get subscription information based on IP, tenant, and other parameters. + */ + public GroupkeyListenserStatus getAllSubClientConfigByIp(String ip, boolean all, String namespaceId, int sampleTime) + throws NacosException { + return configHandler.getAllSubClientConfigByIp(ip, all, namespaceId, sampleTime); + } + + /** + * Export configuration. + */ + public ResponseEntity exportConfig(String dataId, String group, String namespaceId, String appName, + List ids) throws Exception { + return configHandler.exportConfig(dataId, group, namespaceId, appName, ids); + } + + /** + * New version export config adds metadata.yml file to record config metadata. + */ + public ResponseEntity exportConfigV2(String dataId, String group, String namespaceId, String appName, + List ids) throws Exception { + return configHandler.exportConfigV2(dataId, group, namespaceId, appName, ids); + } + + /** + * Imports and publishes a configuration from a file. + */ + public Result> importAndPublishConfig(String srcUser, String namespaceId, + SameConfigPolicy policy, MultipartFile file, String srcIp, String requestIpApp) throws NacosException { + return configHandler.importAndPublishConfig(srcUser, namespaceId, policy, file, srcIp, requestIpApp); + } + + /** + * Clone configuration. + */ + public Result> cloneConfig(String srcUser, String namespaceId, + List configBeansList, SameConfigPolicy policy, String srcIp, + String requestIpApp) throws NacosException { + return configHandler.cloneConfig(srcUser, namespaceId, configBeansList, policy, srcIp, requestIpApp); + } + + /** + * Remove beta configuration based on dataId, group, and namespaceId. + */ + public boolean removeBetaConfig(String dataId, String group, String namespaceId, String remoteIp, + String requestIpApp) throws NacosException { + return configHandler.removeBetaConfig(dataId, group, namespaceId, remoteIp, requestIpApp); + } + + /** + * Query beta configuration based on dataId, group, and namespaceId. + */ + public Result queryBetaConfig(String dataId, String group, String namespaceId) + throws NacosException { + return configHandler.queryBetaConfig(dataId, group, namespaceId); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/config/HistoryProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/config/HistoryProxy.java new file mode 100644 index 00000000000..9d5c53915dc --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/config/HistoryProxy.java @@ -0,0 +1,107 @@ +/* + * Copyright 1999-2024 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.console.proxy.config; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.console.handler.config.HistoryHandler; +import com.alibaba.nacos.persistence.model.Page; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * . + * + * @author zhangyukun on:2024/8/16 + */ +@Service +public class HistoryProxy { + + private final HistoryHandler historyHandler; + + /** + * Constructs a new HistoryProxy with the given HistoryInnerHandler and ConsoleConfig. + * + * @param historyHandler the default implementation of HistoryHandler + */ + @Autowired + public HistoryProxy(HistoryHandler historyHandler) { + this.historyHandler = historyHandler; + } + + /** + * Query the detailed configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param nid the history record ID + * @return the detailed configuration history information + * @throws NacosException if any error occurs during the operation + */ + public ConfigHistoryInfo getConfigHistoryInfo(String dataId, String group, String namespaceId, Long nid) + throws NacosException { + return historyHandler.getConfigHistoryInfo(dataId, group, namespaceId, nid); + } + + /** + * Query the list of configuration history. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the number of items per page + * @return the paginated list of configuration history + * @throws NacosException if any error occurs during the operation + */ + public Page listConfigHistory(String dataId, String group, String namespaceId, Integer pageNo, + Integer pageSize) throws NacosException { + return historyHandler.listConfigHistory(dataId, group, namespaceId, pageNo, pageSize); + } + + /** + * Query the previous configuration history information. + * + * @param dataId the ID of the data + * @param group the group ID + * @param namespaceId the namespace ID + * @param id the configuration ID + * @return the previous configuration history information + * @throws NacosException if any error occurs during the operation + */ + public ConfigHistoryInfo getPreviousConfigHistoryInfo(String dataId, String group, String namespaceId, Long id) + throws NacosException { + return historyHandler.getPreviousConfigHistoryInfo(dataId, group, namespaceId, id); + } + + /** + * Query the list of configurations by namespace. + * + * @param namespaceId the namespace ID + * @return the list of configurations + * @throws NacosApiException if any error occurs during the operation + */ + public List getConfigsByTenant(String namespaceId) throws NacosException { + return historyHandler.getConfigsByTenant(namespaceId); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java new file mode 100644 index 00000000000..2dc6f6a78c7 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/core/ClusterProxy.java @@ -0,0 +1,56 @@ +/* + * Copyright 1999-2024 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.console.proxy.core; + +import com.alibaba.nacos.console.handler.core.ClusterHandler; +import com.alibaba.nacos.core.cluster.Member; +import org.springframework.stereotype.Service; + +import java.util.Collection; + +/** + * Proxy class for handling cluster-related operations. + * + * @author zhangyukun + */ +@Service +public class ClusterProxy { + + private final ClusterHandler clusterHandler; + + /** + * Constructs a new ClusterProxy with the given ClusterInnerHandler and ConsoleConfig. + * + * @param clusterHandler the default implementation of ClusterHandler + */ + public ClusterProxy(ClusterHandler clusterHandler) { + this.clusterHandler = clusterHandler; + } + + /** + * Retrieve a list of cluster members with an optional search keyword. + * + * @param ipKeyWord the search keyword for filtering members + * @return a collection of matching members + * @throws IllegalArgumentException if the deployment type is invalid + */ + public Collection getNodeList(String ipKeyWord) { + return clusterHandler.getNodeList(ipKeyWord); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/core/NamespaceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/core/NamespaceProxy.java new file mode 100644 index 00000000000..6ee1043a108 --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/core/NamespaceProxy.java @@ -0,0 +1,84 @@ +/* + * Copyright 1999-2024 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.console.proxy.core; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.console.handler.core.NamespaceHandler; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import org.springframework.stereotype.Service; + +import java.util.List; + +/** + * Proxy class for handling namespace operations. + * + * @author zhangyukun + */ +@Service +public class NamespaceProxy { + + private final NamespaceHandler namespaceHandler; + + public NamespaceProxy(NamespaceHandler namespaceHandler) { + this.namespaceHandler = namespaceHandler; + } + + /** + * Get namespace list. + */ + public List getNamespaceList() { + return namespaceHandler.getNamespaceList(); + } + + /** + * Get the specific namespace information. + */ + public Namespace getNamespaceDetail(String namespaceId) throws NacosException { + return namespaceHandler.getNamespaceDetail(namespaceId); + } + + /** + * Create or update namespace. + */ + public Boolean createNamespace(String namespaceId, String namespaceName, String namespaceDesc) + throws NacosException { + return namespaceHandler.createNamespace(namespaceId, namespaceName, namespaceDesc); + } + + /** + * Edit namespace. + */ + public Boolean updateNamespace(NamespaceForm namespaceForm) throws NacosException { + return namespaceHandler.updateNamespace(namespaceForm); + } + + /** + * Delete namespace. + */ + public Boolean deleteNamespace(String namespaceId) { + return namespaceHandler.deleteNamespace(namespaceId); + } + + /** + * Check if namespace exists. + */ + public Boolean checkNamespaceIdExist(String namespaceId) { + return namespaceHandler.checkNamespaceIdExist(namespaceId); + } +} diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/InstanceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/InstanceProxy.java new file mode 100644 index 00000000000..4d2f095787e --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/InstanceProxy.java @@ -0,0 +1,77 @@ +/* + * Copyright 1999-2024 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.console.proxy.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.console.handler.naming.InstanceHandler; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Service; + +/** + * Proxy class for handling instance-related operations. + * + * @author zhangyukun + */ +@Service +public class InstanceProxy { + + private final InstanceHandler instanceHandler; + + /** + * Constructs a new InstanceProxy with the given InstanceInnerHandler and ConsoleConfig. + * + * @param instanceHandler the default implementation of InstanceHandler + */ + public InstanceProxy(InstanceHandler instanceHandler) { + this.instanceHandler = instanceHandler; + } + + /** + * Retrieve a list of instances for a specific service and returns as an ObjectNode. + * + * @param namespaceId the namespace ID + * @param serviceNameWithoutGroup the service name without group + * @param groupName the group name + * @param page the page number + * @param pageSize the size of the page + * @param healthyOnly filter by healthy instances only + * @param enabledOnly filter by enabled instances only + * @return a JSON node containing the instances information + * @throws IllegalArgumentException if the deployment type is invalid + */ + public ObjectNode listInstances(String namespaceId, String serviceNameWithoutGroup, String groupName, int page, + int pageSize, Boolean healthyOnly, Boolean enabledOnly) { + return instanceHandler.listInstances(namespaceId, serviceNameWithoutGroup, groupName, page, pageSize, + healthyOnly, enabledOnly); + } + + /** + * Updates an instance. + * + * @param instanceForm the form containing instance data + * @param instance the instance to update + * @throws NacosException if the update operation fails + * @throws IllegalArgumentException if the deployment type is invalid + */ + public void updateInstance(InstanceForm instanceForm, Instance instance) throws NacosException { + instanceHandler.updateInstance(instanceForm, instance); + } +} + diff --git a/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java new file mode 100644 index 00000000000..249571b1f3a --- /dev/null +++ b/console/src/main/java/com/alibaba/nacos/console/proxy/naming/ServiceProxy.java @@ -0,0 +1,159 @@ +/* + * Copyright 1999-2024 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.console.proxy.naming; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.console.handler.naming.ServiceHandler; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Map; + +/** + * Proxy class for handling service-related operations. + * + * @author zhangyukun + */ +@Service +public class ServiceProxy { + + private final ServiceHandler serviceHandler; + + /** + * Constructs a new ServiceProxy with the given ServiceInnerHandler and ConsoleConfig. The handler is mapped to a + * deployment type key. + * + * @param serviceHandler the default implementation of ServiceHandler + */ + public ServiceProxy(ServiceHandler serviceHandler) { + this.serviceHandler = serviceHandler; + } + + /** + * Creates a new service by delegating the operation to the appropriate handler. + * + * @param serviceForm the service form containing the service details + * @throws Exception if an error occurs during service creation + */ + public void createService(ServiceForm serviceForm, ServiceMetadata serviceMetadata) throws Exception { + serviceHandler.createService(serviceForm, serviceMetadata); + } + + /** + * Deletes an existing service by delegating the operation to the appropriate handler. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param groupName the group name + * @throws Exception if an error occurs during service deletion + */ + public void deleteService(String namespaceId, String serviceName, String groupName) throws Exception { + serviceHandler.deleteService(namespaceId, serviceName, groupName); + } + + /** + * Updates an existing service by delegating the operation to the appropriate handler. + * + * @param serviceForm the service form containing the service details + * @param service the service object created from serviceForm + * @param serviceMetadata the service metadata created from serviceForm + * @throws Exception if an error occurs during service update + */ + public void updateService(ServiceForm serviceForm, com.alibaba.nacos.naming.core.v2.pojo.Service service, + ServiceMetadata serviceMetadata, Map metadata) throws Exception { + serviceHandler.updateService(serviceForm, service, serviceMetadata, metadata); + } + + /** + * Retrieves all selector types by delegating the operation to the appropriate handler. + * + * @return a list of selector types + */ + public List getSelectorTypeList() { + return serviceHandler.getSelectorTypeList(); + } + + /** + * Retrieves the list of subscribers for a service by delegating the operation to the appropriate handler. + * + * @param pageNo the page number + * @param pageSize the size of the page + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param groupName the group name + * @param aggregation whether to aggregate the results + * @return a JSON node containing the list of subscribers + * @throws Exception if an error occurs during fetching subscribers + */ + public ObjectNode getSubscribers(int pageNo, int pageSize, String namespaceId, String serviceName, String groupName, + boolean aggregation) throws Exception { + return serviceHandler.getSubscribers(pageNo, pageSize, namespaceId, serviceName, groupName, aggregation); + } + + /** + * Retrieves the list of services and their details by delegating the operation to the appropriate handler. + * + * @param withInstances whether to include instances + * @param namespaceId the namespace ID + * @param pageNo the page number + * @param pageSize the size of the page + * @param serviceName the service name + * @param groupName the group name + * @param hasIpCount whether to filter services with empty instances + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + public Object getServiceList(boolean withInstances, String namespaceId, int pageNo, int pageSize, + String serviceName, String groupName, boolean hasIpCount) throws NacosException { + return serviceHandler.getServiceList(withInstances, namespaceId, pageNo, pageSize, serviceName, groupName, + hasIpCount); + } + + /** + * Retrieves the details of a specific service by delegating the operation to the appropriate handler. + * + * @param namespaceId the namespace ID + * @param serviceName the service name without group + * @param groupName the group name + * @return service detail information + * @throws NacosException if an error occurs during fetching service details + */ + public Object getServiceDetail(String namespaceId, String serviceName, String groupName) throws NacosException { + return serviceHandler.getServiceDetail(namespaceId, serviceName, groupName); + } + + /** + * Updates the metadata of a cluster. + * + * @param namespaceId the namespace ID + * @param serviceName the service name + * @param clusterName the cluster name + * @param clusterMetadata the metadata for the cluster + * @throws Exception if the update operation fails + * @throws IllegalArgumentException if the deployment type is invalid + */ + public void updateClusterMetadata(String namespaceId, String serviceName, String clusterName, + ClusterMetadata clusterMetadata) throws Exception { + serviceHandler.updateClusterMetadata(namespaceId, serviceName, clusterName, clusterMetadata); + } +} + diff --git a/console/src/main/resources/META-INF/derby-schema.sql b/console/src/main/resources/META-INF/derby-schema.sql index be595491b89..452f0bf0ac8 100644 --- a/console/src/main/resources/META-INF/derby-schema.sql +++ b/console/src/main/resources/META-INF/derby-schema.sql @@ -54,7 +54,10 @@ CREATE TABLE his_config_info ( gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', src_user varchar(128), src_ip varchar(50) DEFAULT NULL, + publish_type varchar(50) DEFAULT 'formal', + ext_info CLOB, op_type char(10) DEFAULT NULL, + gray_name varchar(128) DEFAULT NULL, encrypted_data_key LONG VARCHAR DEFAULT NULL, constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); @@ -96,17 +99,25 @@ CREATE TABLE config_info_tag ( constraint configinfotag_id_key PRIMARY KEY (id), constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); -CREATE TABLE config_info_aggr ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - datum_id varchar(255) NOT NULL, - app_name varchar(128), - content CLOB, - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - constraint configinfoaggr_id_key PRIMARY KEY (id), - constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); +CREATE TABLE config_info_gray ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + gray_name varchar(128) NOT NULL, + gray_rule CLOB, + app_name varchar(128), + src_ip varchar(128), + src_user varchar(128) default '', + content CLOB, + md5 varchar(32) DEFAULT NULL, + encrypted_data_key varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfogray_id_key PRIMARY KEY (id), + constraint uk_configinfogray_datagrouptenantgrayname UNIQUE (data_id,group_id,tenant_id,gray_name)); +CREATE INDEX config_info_gray_dataid_gmt_modified ON config_info_gray(data_id,gmt_modified); +CREATE INDEX config_info_gray_gmt_modified ON config_info_gray(gmt_modified); CREATE TABLE app_list ( id bigint NOT NULL generated by default as identity, @@ -215,14 +226,16 @@ CREATE TABLE permissions ( /******************************************/ /* ipv6 support */ /******************************************/ -ALTER TABLE `config_info_tag` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `his_config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `src_user`; +ALTER TABLE config_info_tag ADD src_ip varchar(50) DEFAULT NULL; + +ALTER TABLE his_config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info_beta ADD src_ip varchar(50) DEFAULT NULL ; -ALTER TABLE `config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `config_info_beta` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; \ No newline at end of file +ALTER TABLE his_config_info ADD publish_type varchar(50) DEFAULT 'formal'; +ALTER TABLE his_config_info ADD ext_info CLOB DEFAULT NULL ; +ALTER TABLE his_config_info ADD gray_name varchar(128) DEFAULT NULL; diff --git a/console/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.startup.NacosStartUp b/console/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.startup.NacosStartUp new file mode 100644 index 00000000000..1ae3a713ff2 --- /dev/null +++ b/console/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.startup.NacosStartUp @@ -0,0 +1,17 @@ +# +# 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. +# + +com.alibaba.nacos.console.NacosConsoleStartUp \ No newline at end of file diff --git a/console/src/main/resources/application.properties b/console/src/main/resources/application.properties index 8cec2ee7078..262a0f7c477 100644 --- a/console/src/main/resources/application.properties +++ b/console/src/main/resources/application.properties @@ -112,8 +112,9 @@ nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/ ### The auth system to use, currently only 'nacos' and 'ldap' is supported: nacos.core.auth.system.type=nacos -### If turn on auth system: +### If turn on auth system v3: nacos.core.auth.enabled=false +nacos.core.auth.console.enabled=true ### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. nacos.core.auth.caching.enabled=true @@ -173,11 +174,8 @@ nacos.core.auth.plugin.nacos.token.secret.key= ### If turn on the MCP server: nacos.istio.mcp.server.enabled=false - - ###*************** Add from 1.3.0 ***************### - #*************** Core Related Configurations ***************# ### set the WorkerID manually @@ -219,3 +217,37 @@ nacos.istio.mcp.server.enabled=false # nacos.core.protocol.raft.data.rpc_request_timeout_ms=5000 ### enable to support prometheus service discovery #nacos.prometheus.metrics.enabled=true + +#************** Console UI Configuration ***************# + +### Turn on/off the nacos console ui. +#nacos.console.ui.enabled=true + +###*************** Add from 3.0.0 ***************### + +#*************** Deployment Type Configuration ***************# + +### Sets the deployment type: 'merged' for joint deployment, 'server' for separate deployment server only, 'console' for separate deployment console only. +nacos.deployment.type=merged + +#************** Nacos Admin Compatibility Related Configurations ***************# + +### Enabled for open API compatibility +# nacos.core.api.compatibility.client.enabled=true +### Enabled for admin API compatibility +# nacos.core.api.compatibility.admin.enabled=true +### Enabled for console API compatibility +# nacos.core.api.compatibility.console.enabled=false + +#*************** K8s Related Configurations ***************# +### If turn on the K8s sync: +nacos.k8s.sync.enabled=false + +### If use the Java API from an application outside a kubernetes cluster +#nacos.k8s.sync.outsideCluster=false +#nacos.k8s.sync.kubeConfig=/.kube/config + +#*************** DistributedLock Configurations ***************# + +# nacos.lock.default_expire_time = 30000000 +# nacos.lock.max_expire_time = 1800000000 \ No newline at end of file diff --git a/core/src/main/resources/banner.txt b/console/src/main/resources/nacos-console-banner.txt similarity index 78% rename from core/src/main/resources/banner.txt rename to console/src/main/resources/nacos-console-banner.txt index e197a61db79..ec0ba7c94e7 100644 --- a/core/src/main/resources/banner.txt +++ b/console/src/main/resources/nacos-console-banner.txt @@ -1,11 +1,11 @@ ,--. ,--.'| - ,--,: : | Nacos ${application.version} + ,--,: : | Nacos Console ${application.version} ,`--.'`| ' : ,---. Running in ${nacos.mode} mode, ${nacos.function.mode} function modules -| : : | | ' ,'\ .--.--. Port: ${server.port} +| : : | | ' ,'\ .--.--. Port: ${nacos.console.port} : | \ | : ,--.--. ,---. / / | / / ' Pid: ${pid} -| : ' '; | / \ / \. ; ,. :| : /`./ Console: http://${nacos.local.ip}:${server.port}${server.servlet.contextPath}/index.html +| : ' '; | / \ / \. ; ,. :| : /`./ Console: http://${nacos.local.ip}:${nacos.console.port}${nacos.console.contextPath}/index.html ' ' ;. ;.--. .-. | / / '' | |: :| : ;_ | | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io ' : | ; .' ," .--.; |' ; :__| : | `----. \ diff --git a/console/src/main/resources/nacos-console.properties b/console/src/main/resources/nacos-console.properties new file mode 100644 index 00000000000..78584d08a26 --- /dev/null +++ b/console/src/main/resources/nacos-console.properties @@ -0,0 +1,19 @@ +# +# 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. +# + +### nacos console port: +server.port=${nacos.console.port:8080} +server.servlet.contextPath=${nacos.console.contextPath:} \ No newline at end of file diff --git a/console/src/main/resources/static/index.html b/console/src/main/resources/static/index.html index 3584f5e66bf..121dab1872b 100644 --- a/console/src/main/resources/static/index.html +++ b/console/src/main/resources/static/index.html @@ -35,7 +35,7 @@ - +
@@ -56,6 +56,6 @@ - + diff --git a/console/src/main/resources/static/js/main.js b/console/src/main/resources/static/js/main.js index 21667eec5c4..212c87988b0 100644 --- a/console/src/main/resources/static/js/main.js +++ b/console/src/main/resources/static/js/main.js @@ -1,19 +1,19 @@ -!function(n){var a={};function r(e){var t;return(a[e]||(t=a[e]={i:e,l:!1,exports:{}},n[e].call(t.exports,t,t.exports,r),t.l=!0,t)).exports}r.m=n,r.c=a,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(t,e){if(1&e&&(t=r(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var a in t)r.d(n,a,function(e){return t[e]}.bind(null,a));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=462)}([function(e,t,n){"use strict";e.exports=n(469)},function(e,t,n){"use strict";n.d(t,"b",function(){return T}),n.d(t,"f",function(){return L}),n.d(t,"c",function(){return m}),n.d(t,"d",function(){return a}),n.d(t,"a",function(){return o}),n.d(t,"e",function(){return O});n(47);var t=n(25),c=n.n(t),l=n(76),r=n(94),d=n(62),f=n(32),t=n(111),u=n.n(t),t=n(68),p=n.n(t),h=n(22);function m(){var e=window.location.href,e=(localStorage.removeItem("token"),e.split("#")[0]);console.log("base_url",e),window.location="".concat(e,"#/login")}function a(){var e=window.location.href,e=(localStorage.removeItem("token"),e.split("#")[0]);window.location="".concat(e,"#/register")}function o(e){for(var t="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789",n="",a=0;a>>0,a;for(a=0;a0)for(n=0;n=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,r)).toString().substr(1)+a}var ie=/(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|N{1,5}|YYYYYY|YYYYY|YYYY|YY|y{2,4}|yo?|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g,se=/(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g,le={},ue={};function a(e,t,n,a){var r=a;if(typeof a==="string")r=function(){return this[a]()};if(e)ue[e]=r;if(t)ue[t[0]]=function(){return o(r.apply(this,arguments),t[1],t[2])};if(n)ue[n]=function(){return this.localeData().ordinal(r.apply(this,arguments),e)}}function ce(e){if(e.match(/\[[\s\S]/))return e.replace(/^\[|\]$/g,"");return e.replace(/\\/g,"")}function de(a){var r=a.match(ie),e,o;for(e=0,o=r.length;e=0&&se.test(e)){e=e.replace(se,a);se.lastIndex=0;n-=1}return e}var he={LTS:"h:mm:ss A",LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D, YYYY",LLL:"MMMM D, YYYY h:mm A",LLLL:"dddd, MMMM D, YYYY h:mm A"};function me(e){var t=this._longDateFormat[e],n=this._longDateFormat[e.toUpperCase()];if(t||!n)return t;this._longDateFormat[e]=n.match(ie).map(function(e){if(e==="MMMM"||e==="MM"||e==="DD"||e==="dddd")return e.slice(1);return e}).join("");return this._longDateFormat[e]}var ge="Invalid date";function ye(){return this._invalidDate}var ve="%d",_e=/\d{1,2}/;function be(e){return this._ordinal.replace("%d",e)}var we={future:"in %s",past:"%s ago",s:"a few seconds",ss:"%d seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",w:"a week",ww:"%d weeks",M:"a month",MM:"%d months",y:"a year",yy:"%d years"};function Me(e,t,n,a){var r=this._relativeTime[n];return h(r)?r(e,t,n,a):r.replace(/%d/i,e)}function ke(e,t){var n=this._relativeTime[e>0?"future":"past"];return h(n)?n(t):n.replace(/%s/i,t)}var Se={};function t(e,t){var n=e.toLowerCase();Se[n]=Se[n+"s"]=Se[t]=e}function m(e){return typeof e==="string"?Se[e]||Se[e.toLowerCase()]:undefined}function Ee(e){var t={},n,a;for(a in e)if(l(e,a)){n=m(a);if(n)t[n]=e[a]}return t}var xe={};function n(e,t){xe[e]=t}function Ce(e){var t=[],n;for(n in e)if(l(e,n))t.push({unit:n,priority:xe[n]});t.sort(function(e,t){return e.priority-t.priority});return t}function Te(e){return e%4===0&&e%100!==0||e%400===0}function g(e){if(e<0)return Math.ceil(e)||0;else return Math.floor(e)}function y(e){var t=+e,n=0;if(t!==0&&isFinite(t))n=g(t);return n}function Le(t,n){return function(e){if(e!=null){De(this,t,e);d.updateOffset(this,n);return this}else return Oe(this,t)}}function Oe(e,t){return e.isValid()?e._d["get"+(e._isUTC?"UTC":"")+t]():NaN}function De(e,t,n){if(e.isValid()&&!isNaN(n))if(t==="FullYear"&&Te(e.year())&&e.month()===1&&e.date()===29){n=y(n);e._d["set"+(e._isUTC?"UTC":"")+t](n,e.month(),ot(n,e.month()))}else e._d["set"+(e._isUTC?"UTC":"")+t](n)}function Ne(e){e=m(e);if(h(this[e]))return this[e]();return this}function Pe(e,t){if(typeof e==="object"){e=Ee(e);var n=Ce(e),a,r=n.length;for(a=0;a68?1900:2e3)};var Mt=Le("FullYear",true);function kt(){return Te(this.year())}function St(e,t,n,a,r,o,i){var s;if(e<100&&e>=0){s=new Date(e+400,t,n,a,r,o,i);if(isFinite(s.getFullYear()))s.setFullYear(e)}else s=new Date(e,t,n,a,r,o,i);return s}function Et(e){var t,n;if(e<100&&e>=0){n=Array.prototype.slice.call(arguments);n[0]=e+400;t=new Date(Date.UTC.apply(null,n));if(isFinite(t.getUTCFullYear()))t.setUTCFullYear(e)}else t=new Date(Date.UTC.apply(null,arguments));return t}function xt(e,t,n){var a=7+t-n,r=(7+Et(e,0,a).getUTCDay()-t)%7;return-r+a-1}function Ct(e,t,n,a,r){var o=(7+n-a)%7,i=xt(e,a,r),s=1+7*(t-1)+o+i,l,u;if(s<=0){l=e-1;u=wt(l)+s}else if(s>wt(e)){l=e+1;u=s-wt(e)}else{l=e;u=s}return{year:l,dayOfYear:u}}function Tt(e,t,n){var a=xt(e.year(),t,n),r=Math.floor((e.dayOfYear()-a-1)/7)+1,o,i;if(r<1){i=e.year()-1;o=r+L(i,t,n)}else if(r>L(e.year(),t,n)){o=r-L(e.year(),t,n);i=e.year()+1}else{i=e.year();o=r}return{week:o,year:i}}function L(e,t,n){var a=xt(e,t,n),r=xt(e+1,t,n);return(wt(e)-a+r)/7}function Lt(e){return Tt(e,this._week.dow,this._week.doy).week}a("w",["ww",2],"wo","week"),a("W",["WW",2],"Wo","isoWeek"),t("week","w"),t("isoWeek","W"),n("week",5),n("isoWeek",5),_("w",v),_("ww",v,r),_("W",v),_("WW",v,r),Ze(["w","ww","W","WW"],function(e,t,n,a){t[a.substr(0,1)]=y(e)});var Ot={dow:0,doy:6};function Dt(){return this._week.dow}function Nt(){return this._week.doy}function Pt(e){var t=this.localeData().week(this);return e==null?t:this.add((e-t)*7,"d")}function jt(e){var t=Tt(this,1,4).week;return e==null?t:this.add((e-t)*7,"d")}function Yt(e,t){if(typeof e!=="string")return e;if(!isNaN(e))return parseInt(e,10);e=t.weekdaysParse(e);if(typeof e==="number")return e;return null}function It(e,t){if(typeof e==="string")return t.weekdaysParse(e)%7||7;return isNaN(e)?null:e}function At(e,t){return e.slice(t,7).concat(e.slice(0,t))}a("d",0,"do","day"),a("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),a("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),a("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),a("e",0,0,"weekday"),a("E",0,0,"isoWeekday"),t("day","d"),t("weekday","e"),t("isoWeekday","E"),n("day",11),n("weekday",11),n("isoWeekday",11),_("d",v),_("e",v),_("E",v),_("dd",function(e,t){return t.weekdaysMinRegex(e)}),_("ddd",function(e,t){return t.weekdaysShortRegex(e)}),_("dddd",function(e,t){return t.weekdaysRegex(e)}),Ze(["dd","ddd","dddd"],function(e,t,n,a){var r=n._locale.weekdaysParse(e,a,n._strict);if(r!=null)t.d=r;else f(n).invalidWeekday=e}),Ze(["d","e","E"],function(e,t,n,a){t[a]=y(e)});var Rt="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),Ht="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Ft="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),zt=Ge,Wt=Ge,Bt=Ge;function Ut(e,t){var n=i(this._weekdays)?this._weekdays:this._weekdays[e&&e!==true&&this._weekdays.isFormat.test(t)?"format":"standalone"];return e===true?At(n,this._week.dow):e?n[e.day()]:n}function Vt(e){return e===true?At(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort}function Kt(e){return e===true?At(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin}function qt(e,t,n){var a,r,o,i=e.toLocaleLowerCase();if(!this._weekdaysParse){this._weekdaysParse=[];this._shortWeekdaysParse=[];this._minWeekdaysParse=[];for(a=0;a<7;++a){o=c([2e3,1]).day(a);this._minWeekdaysParse[a]=this.weekdaysMin(o,"").toLocaleLowerCase();this._shortWeekdaysParse[a]=this.weekdaysShort(o,"").toLocaleLowerCase();this._weekdaysParse[a]=this.weekdays(o,"").toLocaleLowerCase()}}if(n)if(t==="dddd"){r=T.call(this._weekdaysParse,i);return r!==-1?r:null}else if(t==="ddd"){r=T.call(this._shortWeekdaysParse,i);return r!==-1?r:null}else{r=T.call(this._minWeekdaysParse,i);return r!==-1?r:null}else if(t==="dddd"){r=T.call(this._weekdaysParse,i);if(r!==-1)return r;r=T.call(this._shortWeekdaysParse,i);if(r!==-1)return r;r=T.call(this._minWeekdaysParse,i);return r!==-1?r:null}else if(t==="ddd"){r=T.call(this._shortWeekdaysParse,i);if(r!==-1)return r;r=T.call(this._weekdaysParse,i);if(r!==-1)return r;r=T.call(this._minWeekdaysParse,i);return r!==-1?r:null}else{r=T.call(this._minWeekdaysParse,i);if(r!==-1)return r;r=T.call(this._weekdaysParse,i);if(r!==-1)return r;r=T.call(this._shortWeekdaysParse,i);return r!==-1?r:null}}function Gt(e,t,n){var a,r,o;if(this._weekdaysParseExact)return qt.call(this,e,t,n);if(!this._weekdaysParse){this._weekdaysParse=[];this._minWeekdaysParse=[];this._shortWeekdaysParse=[];this._fullWeekdaysParse=[]}for(a=0;a<7;a++){r=c([2e3,1]).day(a);if(n&&!this._fullWeekdaysParse[a]){this._fullWeekdaysParse[a]=new RegExp("^"+this.weekdays(r,"").replace(".","\\.?")+"$","i");this._shortWeekdaysParse[a]=new RegExp("^"+this.weekdaysShort(r,"").replace(".","\\.?")+"$","i");this._minWeekdaysParse[a]=new RegExp("^"+this.weekdaysMin(r,"").replace(".","\\.?")+"$","i")}if(!this._weekdaysParse[a]){o="^"+this.weekdays(r,"")+"|^"+this.weekdaysShort(r,"")+"|^"+this.weekdaysMin(r,"");this._weekdaysParse[a]=new RegExp(o.replace(".",""),"i")}if(n&&t==="dddd"&&this._fullWeekdaysParse[a].test(e))return a;else if(n&&t==="ddd"&&this._shortWeekdaysParse[a].test(e))return a;else if(n&&t==="dd"&&this._minWeekdaysParse[a].test(e))return a;else if(!n&&this._weekdaysParse[a].test(e))return a}}function $t(e){if(!this.isValid())return e!=null?this:NaN;var t=this._isUTC?this._d.getUTCDay():this._d.getDay();if(e!=null){e=Yt(e,this.localeData());return this.add(e-t,"d")}else return t}function Jt(e){if(!this.isValid())return e!=null?this:NaN;var t=(this.day()+7-this.localeData()._week.dow)%7;return e==null?t:this.add(e-t,"d")}function Qt(e){if(!this.isValid())return e!=null?this:NaN;if(e!=null){var t=It(e,this.localeData());return this.day(this.day()%7?t:t-7)}else return this.day()||7}function Xt(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))tn.call(this);if(e)return this._weekdaysStrictRegex;else return this._weekdaysRegex}else{if(!l(this,"_weekdaysRegex"))this._weekdaysRegex=zt;return this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex}}function Zt(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))tn.call(this);if(e)return this._weekdaysShortStrictRegex;else return this._weekdaysShortRegex}else{if(!l(this,"_weekdaysShortRegex"))this._weekdaysShortRegex=Wt;return this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}}function en(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))tn.call(this);if(e)return this._weekdaysMinStrictRegex;else return this._weekdaysMinRegex}else{if(!l(this,"_weekdaysMinRegex"))this._weekdaysMinRegex=Bt;return this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}}function tn(){function e(e,t){return t.length-e.length}var t=[],n=[],a=[],r=[],o,i,s,l,u;for(o=0;o<7;o++){i=c([2e3,1]).day(o);s=b(this.weekdaysMin(i,""));l=b(this.weekdaysShort(i,""));u=b(this.weekdays(i,""));t.push(s);n.push(l);a.push(u);r.push(s);r.push(l);r.push(u)}t.sort(e);n.sort(e);a.sort(e);r.sort(e);this._weekdaysRegex=new RegExp("^("+r.join("|")+")","i");this._weekdaysShortRegex=this._weekdaysRegex;this._weekdaysMinRegex=this._weekdaysRegex;this._weekdaysStrictRegex=new RegExp("^("+a.join("|")+")","i");this._weekdaysShortStrictRegex=new RegExp("^("+n.join("|")+")","i");this._weekdaysMinStrictRegex=new RegExp("^("+t.join("|")+")","i")}function nn(){return this.hours()%12||12}function an(){return this.hours()||24}function rn(e,t){a(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function on(e,t){return t._meridiemParse}function sn(e){return(e+"").toLowerCase().charAt(0)==="p"}a("H",["HH",2],0,"hour"),a("h",["hh",2],0,nn),a("k",["kk",2],0,an),a("hmm",0,0,function(){return""+nn.apply(this)+o(this.minutes(),2)}),a("hmmss",0,0,function(){return""+nn.apply(this)+o(this.minutes(),2)+o(this.seconds(),2)}),a("Hmm",0,0,function(){return""+this.hours()+o(this.minutes(),2)}),a("Hmmss",0,0,function(){return""+this.hours()+o(this.minutes(),2)+o(this.seconds(),2)}),rn("a",true),rn("A",false),t("hour","h"),n("hour",13),_("a",on),_("A",on),_("H",v),_("h",v),_("k",v),_("HH",v,r),_("hh",v,r),_("kk",v,r),_("hmm",Re),_("hmmss",He),_("Hmm",Re),_("Hmmss",He),w(["H","HH"],E),w(["k","kk"],function(e,t,n){var a=y(e);t[E]=a===24?0:a}),w(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e);n._meridiem=e}),w(["h","hh"],function(e,t,n){t[E]=y(e);f(n).bigHour=true}),w("hmm",function(e,t,n){var a=e.length-2;t[E]=y(e.substr(0,a));t[x]=y(e.substr(a));f(n).bigHour=true}),w("hmmss",function(e,t,n){var a=e.length-4,r=e.length-2;t[E]=y(e.substr(0,a));t[x]=y(e.substr(a,2));t[C]=y(e.substr(r));f(n).bigHour=true}),w("Hmm",function(e,t,n){var a=e.length-2;t[E]=y(e.substr(0,a));t[x]=y(e.substr(a))}),w("Hmmss",function(e,t,n){var a=e.length-4,r=e.length-2;t[E]=y(e.substr(0,a));t[x]=y(e.substr(a,2));t[C]=y(e.substr(r))});var ln,un=Le("Hours",true);function cn(e,t,n){if(e>11)return n?"pm":"PM";else return n?"am":"AM"}var dn={calendar:re,longDateFormat:he,invalidDate:ge,ordinal:ve,dayOfMonthOrdinalParse:_e,relativeTime:we,months:it,monthsShort:st,week:Ot,weekdays:Rt,weekdaysMin:Ft,weekdaysShort:Ht,meridiemParse:/[ap]\.?m?\.?/i},O={},fn={},pn;function hn(e,t){var n,a=Math.min(e.length,t.length);for(n=0;n0){r=vn(o.slice(0,n).join("-"));if(r)return r;if(a&&a.length>=n&&hn(o,a)>=n-1)break;n--}t++}return pn}function yn(e){return e.match("^[^/\\\\]*$")!=null}function vn(t){var e=null,n;if(O[t]===undefined&&typeof ci!=="undefined"&&ci&&ci.exports&&yn(t))try{e=pn._abbr;n=di;fi(536)("./"+t);_n(e)}catch(e){O[t]=null}return O[t]}function _n(e,t){var n;if(e){if(s(t))n=Mn(e);else n=bn(e,t);if(n)pn=n;else if(typeof console!=="undefined"&&console.warn)console.warn("Locale "+e+" not found. Did you forget to load it?")}return pn._abbr}function bn(e,t){if(t!==null){var n,a=dn;t.abbr=e;if(O[e]!=null){ee("defineLocaleOverride","use moment.updateLocale(localeName, config) to change "+"an existing locale. moment.defineLocale(localeName, "+"config) should only be used for creating a new locale "+"See http://momentjs.com/guides/#/warnings/define-locale/ for more info.");a=O[e]._config}else if(t.parentLocale!=null)if(O[t.parentLocale]!=null)a=O[t.parentLocale]._config;else{n=vn(t.parentLocale);if(n!=null)a=n._config;else{if(!fn[t.parentLocale])fn[t.parentLocale]=[];fn[t.parentLocale].push({name:e,config:t});return null}}O[e]=new ae(ne(a,t));if(fn[e])fn[e].forEach(function(e){bn(e.name,e.config)});_n(e);return O[e]}else{delete O[e];return null}}function wn(e,t){if(t!=null){var n,a,r=dn;if(O[e]!=null&&O[e].parentLocale!=null)O[e].set(ne(O[e]._config,t));else{a=vn(e);if(a!=null)r=a._config;t=ne(r,t);if(a==null)t.abbr=e;n=new ae(t);n.parentLocale=O[e];O[e]=n}_n(e)}else if(O[e]!=null)if(O[e].parentLocale!=null){O[e]=O[e].parentLocale;if(e===_n())_n(e)}else if(O[e]!=null)delete O[e];return O[e]}function Mn(e){var t;if(e&&e._locale&&e._locale._abbr)e=e._locale._abbr;if(!e)return pn;if(!i(e)){t=vn(e);if(t)return t;e=[e]}return gn(e)}function kn(){return Z(O)}function Sn(e){var t,n=e._a;if(n&&f(e).overflow===-2){t=n[k]<0||n[k]>11?k:n[S]<1||n[S]>ot(n[M],n[k])?S:n[E]<0||n[E]>24||n[E]===24&&(n[x]!==0||n[C]!==0||n[tt]!==0)?E:n[x]<0||n[x]>59?x:n[C]<0||n[C]>59?C:n[tt]<0||n[tt]>999?tt:-1;if(f(e)._overflowDayOfYear&&(tS))t=S;if(f(e)._overflowWeeks&&t===-1)t=nt;if(f(e)._overflowWeekday&&t===-1)t=at;f(e).overflow=t}return e}var En=/^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,xn=/^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d|))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([+-]\d\d(?::?\d\d)?|\s*Z)?)?$/,Cn=/Z|[+-]\d\d(?::?\d\d)?/,Tn=[["YYYYYY-MM-DD",/[+-]\d{6}-\d\d-\d\d/],["YYYY-MM-DD",/\d{4}-\d\d-\d\d/],["GGGG-[W]WW-E",/\d{4}-W\d\d-\d/],["GGGG-[W]WW",/\d{4}-W\d\d/,false],["YYYY-DDD",/\d{4}-\d{3}/],["YYYY-MM",/\d{4}-\d\d/,false],["YYYYYYMMDD",/[+-]\d{10}/],["YYYYMMDD",/\d{8}/],["GGGG[W]WWE",/\d{4}W\d{3}/],["GGGG[W]WW",/\d{4}W\d{2}/,false],["YYYYDDD",/\d{7}/],["YYYYMM",/\d{6}/,false],["YYYY",/\d{4}/,false]],Ln=[["HH:mm:ss.SSSS",/\d\d:\d\d:\d\d\.\d+/],["HH:mm:ss,SSSS",/\d\d:\d\d:\d\d,\d+/],["HH:mm:ss",/\d\d:\d\d:\d\d/],["HH:mm",/\d\d:\d\d/],["HHmmss.SSSS",/\d\d\d\d\d\d\.\d+/],["HHmmss,SSSS",/\d\d\d\d\d\d,\d+/],["HHmmss",/\d\d\d\d\d\d/],["HHmm",/\d\d\d\d/],["HH",/\d\d/]],On=/^\/?Date\((-?\d+)/i,Dn=/^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/,Nn={UT:0,GMT:0,EDT:-4*60,EST:-5*60,CDT:-5*60,CST:-6*60,MDT:-6*60,MST:-7*60,PDT:-7*60,PST:-8*60};function Pn(e){var t,n,a=e._i,r=En.exec(a)||xn.exec(a),o,i,s,l,u=Tn.length,c=Ln.length;if(r){f(e).iso=true;for(t=0,n=u;twt(i)||e._dayOfYear===0)f(e)._overflowDayOfYear=true;n=Et(i,0,e._dayOfYear);e._a[k]=n.getUTCMonth();e._a[S]=n.getUTCDate()}for(t=0;t<3&&e._a[t]==null;++t)e._a[t]=a[t]=r[t];for(;t<7;t++)e._a[t]=a[t]=e._a[t]==null?t===2?1:0:e._a[t];if(e._a[E]===24&&e._a[x]===0&&e._a[C]===0&&e._a[tt]===0){e._nextDay=true;e._a[E]=0}e._d=(e._useUTC?Et:St).apply(null,a);o=e._useUTC?e._d.getUTCDay():e._d.getDay();if(e._tzm!=null)e._d.setUTCMinutes(e._d.getUTCMinutes()-e._tzm);if(e._nextDay)e._a[E]=24;if(e._w&&typeof e._w.d!=="undefined"&&e._w.d!==o)f(e).weekdayMismatch=true}function Un(e){var t,n,a,r,o,i,s,l,u;t=e._w;if(t.GG!=null||t.W!=null||t.E!=null){o=1;i=4;n=zn(t.GG,e._a[M],Tt(D(),1,4).year);a=zn(t.W,1);r=zn(t.E,1);if(r<1||r>7)l=true}else{o=e._locale._week.dow;i=e._locale._week.doy;u=Tt(D(),o,i);n=zn(t.gg,e._a[M],u.year);a=zn(t.w,u.week);if(t.d!=null){r=t.d;if(r<0||r>6)l=true}else if(t.e!=null){r=t.e+o;if(t.e<0||t.e>6)l=true}else r=o}if(a<1||a>L(n,o,i))f(e)._overflowWeeks=true;else if(l!=null)f(e)._overflowWeekday=true;else{s=Ct(n,a,r,o,i);e._a[M]=s.year;e._dayOfYear=s.dayOfYear}}function Vn(e){if(e._f===d.ISO_8601){Pn(e);return}if(e._f===d.RFC_2822){Hn(e);return}e._a=[];f(e).empty=true;var t=""+e._i,n,a,r,o,i,s=t.length,l=0,u,c;r=pe(e._f,e._locale).match(ie)||[];c=r.length;for(n=0;n0)f(e).unusedInput.push(i);t=t.slice(t.indexOf(a)+a.length);l+=a.length}if(ue[o]){if(a)f(e).empty=false;else f(e).unusedTokens.push(o);et(o,a,e)}else if(e._strict&&!a)f(e).unusedTokens.push(o)}f(e).charsLeftOver=s-l;if(t.length>0)f(e).unusedInput.push(t);if(e._a[E]<=12&&f(e).bigHour===true&&e._a[E]>0)f(e).bigHour=undefined;f(e).parsedDateParts=e._a.slice(0);f(e).meridiem=e._meridiem;e._a[E]=Kn(e._locale,e._a[E],e._meridiem);u=f(e).era;if(u!==null)e._a[M]=e._locale.erasConvertYear(u,e._a[M]);Bn(e);Sn(e)}function Kn(e,t,n){var a;if(n==null)return t;if(e.meridiemHour!=null)return e.meridiemHour(t,n);else if(e.isPM!=null){a=e.isPM(n);if(a&&t<12)t+=12;if(!a&&t===12)t=0;return t}else return t}function qn(e){var t,n,a,r,o,i,s=false,l=e._f.length;if(l===0){f(e).invalidFormat=true;e._d=new Date(NaN);return}for(r=0;rthis?this:e;else return K()});function ta(e,t){var n,a;if(t.length===1&&i(t[0]))t=t[0];if(!t.length)return D();n=t[0];for(a=1;athis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Ea(){if(!s(this._isDSTShifted))return this._isDSTShifted;var e={},t;$(e,this);e=Jn(e);if(e._a){t=e._isUTC?c(e._a):D(e._a);this._isDSTShifted=this.isValid()&&fa(e._a,t.toArray())>0}else this._isDSTShifted=false;return this._isDSTShifted}function xa(){return this.isValid()?!this._isUTC:false}function Ca(){return this.isValid()?this._isUTC:false}function Ta(){return this.isValid()?this._isUTC&&this._offset===0:false}d.updateOffset=function(){};var La=/^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,Oa=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function N(e,t){var n=e,a=null,r,o,i;if(ca(e))n={ms:e._milliseconds,d:e._days,M:e._months};else if(u(e)||!isNaN(+e)){n={};if(t)n[t]=+e;else n.milliseconds=+e}else if(a=La.exec(e)){r=a[1]==="-"?-1:1;n={y:0,d:y(a[S])*r,h:y(a[E])*r,m:y(a[x])*r,s:y(a[C])*r,ms:y(da(a[tt]*1e3))*r}}else if(a=Oa.exec(e)){r=a[1]==="-"?-1:1;n={y:Da(a[2],r),M:Da(a[3],r),w:Da(a[4],r),d:Da(a[5],r),h:Da(a[6],r),m:Da(a[7],r),s:Da(a[8],r)}}else if(n==null)n={};else if(typeof n==="object"&&("from"in n||"to"in n)){i=Pa(D(n.from),D(n.to));n={};n.ms=i.milliseconds;n.M=i.months}o=new ua(n);if(ca(e)&&l(e,"_locale"))o._locale=e._locale;if(ca(e)&&l(e,"_isValid"))o._isValid=e._isValid;return o}function Da(e,t){var n=e&&parseFloat(e.replace(",","."));return(isNaN(n)?0:n)*t}function Na(e,t){var n={};n.months=t.month()-e.month()+(t.year()-e.year())*12;if(e.clone().add(n.months,"M").isAfter(t))--n.months;n.milliseconds=+t-+e.clone().add(n.months,"M");return n}function Pa(e,t){var n;if(!(e.isValid()&&t.isValid()))return{milliseconds:0,months:0};t=ga(t,e);if(e.isBefore(t))n=Na(e,t);else{n=Na(t,e);n.milliseconds=-n.milliseconds;n.months=-n.months}return n}function ja(r,o){return function(e,t){var n,a;if(t!==null&&!isNaN(+t)){ee(o,"moment()."+o+"(period, number) is deprecated. Please use moment()."+o+"(number, period). "+"See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.");a=e;e=t;t=a}n=N(e,t);Ya(this,n,r);return this}}function Ya(e,t,n,a){var r=t._milliseconds,o=da(t._days),i=da(t._months);if(!e.isValid())return;a=a==null?true:a;if(i)mt(e,Oe(e,"Month")+i*n);if(o)De(e,"Date",Oe(e,"Date")+o*n);if(r)e._d.setTime(e._d.valueOf()+r*n);if(a)d.updateOffset(e,o||i)}N.fn=ua.prototype,N.invalid=la;var Ia=ja(1,"add"),Aa=ja(-1,"subtract");function Ra(e){return typeof e==="string"||e instanceof String}function Ha(e){return p(e)||z(e)||Ra(e)||u(e)||za(e)||Fa(e)||e===null||e===undefined}function Fa(e){var t=H(e)&&!F(e),n=false,a=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms"],r,o,i=a.length;for(r=0;rn.valueOf();else return n.valueOf()9999)return fe(n,t?"YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYYYY-MM-DD[T]HH:mm:ss.SSSZ");if(h(Date.prototype.toISOString))if(t)return this.toDate().toISOString();else return new Date(this.valueOf()+this.utcOffset()*60*1e3).toISOString().replace("Z",fe(n,"Z"));return fe(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")}function nr(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e="moment",t="",n,a,r,o;if(!this.isLocal()){e=this.utcOffset()===0?"moment.utc":"moment.parseZone";t="Z"}n="["+e+'("]';a=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY";r="-MM-DD[T]HH:mm:ss.SSS";o=t+'[")]';return this.format(n+a+r+o)}function ar(e){if(!e)e=this.isUtc()?d.defaultFormatUtc:d.defaultFormat;var t=fe(this,e);return this.localeData().postformat(t)}function rr(e,t){if(this.isValid()&&(p(e)&&e.isValid()||D(e).isValid()))return N({to:this,from:e}).locale(this.locale()).humanize(!t);else return this.localeData().invalidDate()}function or(e){return this.from(D(),e)}function ir(e,t){if(this.isValid()&&(p(e)&&e.isValid()||D(e).isValid()))return N({from:this,to:e}).locale(this.locale()).humanize(!t);else return this.localeData().invalidDate()}function sr(e){return this.to(D(),e)}function lr(e){var t;if(e===undefined)return this._locale._abbr;else{t=Mn(e);if(t!=null)this._locale=t;return this}}d.defaultFormat="YYYY-MM-DDTHH:mm:ssZ",d.defaultFormatUtc="YYYY-MM-DDTHH:mm:ss[Z]";var ur=e("moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.",function(e){if(e===undefined)return this.localeData();else return this.locale(e)});function cr(){return this._locale}var dr=1e3,fr=60*dr,pr=60*fr,hr=(365*400+97)*24*pr;function mr(e,t){return(e%t+t)%t}function gr(e,t,n){if(e<100&&e>=0)return new Date(e+400,t,n)-hr;else return new Date(e,t,n).valueOf()}function yr(e,t,n){if(e<100&&e>=0)return Date.UTC(e+400,t,n)-hr;else return Date.UTC(e,t,n)}function vr(e){var t,n;e=m(e);if(e===undefined||e==="millisecond"||!this.isValid())return this;n=this._isUTC?yr:gr;switch(e){case"year":t=n(this.year(),0,1);break;case"quarter":t=n(this.year(),this.month()-this.month()%3,1);break;case"month":t=n(this.year(),this.month(),1);break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday());break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1));break;case"day":case"date":t=n(this.year(),this.month(),this.date());break;case"hour":t=this._d.valueOf();t-=mr(t+(this._isUTC?0:this.utcOffset()*fr),pr);break;case"minute":t=this._d.valueOf();t-=mr(t,fr);break;case"second":t=this._d.valueOf();t-=mr(t,dr);break}this._d.setTime(t);d.updateOffset(this,true);return this}function _r(e){var t,n;e=m(e);if(e===undefined||e==="millisecond"||!this.isValid())return this;n=this._isUTC?yr:gr;switch(e){case"year":t=n(this.year()+1,0,1)-1;break;case"quarter":t=n(this.year(),this.month()-this.month()%3+3,1)-1;break;case"month":t=n(this.year(),this.month()+1,1)-1;break;case"week":t=n(this.year(),this.month(),this.date()-this.weekday()+7)-1;break;case"isoWeek":t=n(this.year(),this.month(),this.date()-(this.isoWeekday()-1)+7)-1;break;case"day":case"date":t=n(this.year(),this.month(),this.date()+1)-1;break;case"hour":t=this._d.valueOf();t+=pr-mr(t+(this._isUTC?0:this.utcOffset()*fr),pr)-1;break;case"minute":t=this._d.valueOf();t+=fr-mr(t,fr)-1;break;case"second":t=this._d.valueOf();t+=dr-mr(t,dr)-1;break}this._d.setTime(t);d.updateOffset(this,true);return this}function br(){return this._d.valueOf()-(this._offset||0)*6e4}function wr(){return Math.floor(this.valueOf()/1e3)}function Mr(){return new Date(this.valueOf())}function kr(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]}function Sr(){var e=this;return{years:e.year(),months:e.month(),date:e.date(),hours:e.hours(),minutes:e.minutes(),seconds:e.seconds(),milliseconds:e.milliseconds()}}function Er(){return this.isValid()?this.toISOString():null}function xr(){return V(this)}function Cr(){return B({},f(this))}function Tr(){return f(this).overflow}function Lr(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Or(e,t){var n,a,r,o=this._eras||Mn("en")._eras;for(n=0,a=o.length;n=0)return o[a]}}function Nr(e,t){var n=e.since<=e.until?+1:-1;if(t===undefined)return d(e.since).year();else return d(e.since).year()+(t-e.offset)*n}function Pr(){var e,t,n,a=this.localeData().eras();for(e=0,t=a.length;eo)t=o;return Zr.call(this,e,t,n,a,r)}}function Zr(e,t,n,a,r){var o=Ct(e,t,n,a,r),i=Et(o.year,0,o.dayOfYear);this.year(i.getUTCFullYear());this.month(i.getUTCMonth());this.date(i.getUTCDate());return this}function eo(e){return e==null?Math.ceil((this.month()+1)/3):this.month((e-1)*3+this.month()%3)}a("N",0,0,"eraAbbr"),a("NN",0,0,"eraAbbr"),a("NNN",0,0,"eraAbbr"),a("NNNN",0,0,"eraName"),a("NNNNN",0,0,"eraNarrow"),a("y",["y",1],"yo","eraYear"),a("y",["yy",2],0,"eraYear"),a("y",["yyy",3],0,"eraYear"),a("y",["yyyy",4],0,"eraYear"),_("N",Fr),_("NN",Fr),_("NNN",Fr),_("NNNN",zr),_("NNNNN",Wr),w(["N","NN","NNN","NNNN","NNNNN"],function(e,t,n,a){var r=n._locale.erasParse(e,a,n._strict);if(r)f(n).era=r;else f(n).invalidEra=e}),_("y",Be),_("yy",Be),_("yyy",Be),_("yyyy",Be),_("yo",Br),w(["y","yy","yyy","yyyy"],M),w(["yo"],function(e,t,n,a){var r;if(n._locale._eraYearOrdinalRegex)r=e.match(n._locale._eraYearOrdinalRegex);if(n._locale.eraYearOrdinalParse)t[M]=n._locale.eraYearOrdinalParse(e,r);else t[M]=parseInt(e,10)}),a(0,["gg",2],0,function(){return this.weekYear()%100}),a(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Vr("gggg","weekYear"),Vr("ggggg","weekYear"),Vr("GGGG","isoWeekYear"),Vr("GGGGG","isoWeekYear"),t("weekYear","gg"),t("isoWeekYear","GG"),n("weekYear",1),n("isoWeekYear",1),_("G",Ue),_("g",Ue),_("GG",v,r),_("gg",v,r),_("GGGG",ze,Ie),_("gggg",ze,Ie),_("GGGGG",We,Ae),_("ggggg",We,Ae),Ze(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,a){t[a.substr(0,2)]=y(e)}),Ze(["gg","GG"],function(e,t,n,a){t[a]=d.parseTwoDigitYear(e)}),a("Q",0,"Qo","quarter"),t("quarter","Q"),n("quarter",7),_("Q",je),w("Q",function(e,t){t[k]=(y(e)-1)*3}),a("D",["DD",2],"Do","date"),t("date","D"),n("date",9),_("D",v),_("DD",v,r),_("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),w(["D","DD"],S),w("Do",function(e,t){t[S]=y(e.match(v)[0])});var to=Le("Date",true);function no(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return e==null?t:this.add(e-t,"d")}a("DDD",["DDDD",3],"DDDo","dayOfYear"),t("dayOfYear","DDD"),n("dayOfYear",4),_("DDD",Fe),_("DDDD",Ye),w(["DDD","DDDD"],function(e,t,n){n._dayOfYear=y(e)}),a("m",["mm",2],0,"minute"),t("minute","m"),n("minute",14),_("m",v),_("mm",v,r),w(["m","mm"],x);var ao=Le("Minutes",false),ro=(a("s",["ss",2],0,"second"),t("second","s"),n("second",15),_("s",v),_("ss",v,r),w(["s","ss"],C),Le("Seconds",false)),oo,io;for(a("S",0,0,function(){return~~(this.millisecond()/100)}),a(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),a(0,["SSS",3],0,"millisecond"),a(0,["SSSS",4],0,function(){return this.millisecond()*10}),a(0,["SSSSS",5],0,function(){return this.millisecond()*100}),a(0,["SSSSSS",6],0,function(){return this.millisecond()*1e3}),a(0,["SSSSSSS",7],0,function(){return this.millisecond()*1e4}),a(0,["SSSSSSSS",8],0,function(){return this.millisecond()*1e5}),a(0,["SSSSSSSSS",9],0,function(){return this.millisecond()*1e6}),t("millisecond","ms"),n("millisecond",16),_("S",Fe,je),_("SS",Fe,r),_("SSS",Fe,Ye),oo="SSSS";oo.length<=9;oo+="S")_(oo,Be);function so(e,t){t[tt]=y(("0."+e)*1e3)}for(oo="S";oo.length<=9;oo+="S")w(oo,so);function lo(){return this._isUTC?"UTC":""}function uo(){return this._isUTC?"Coordinated Universal Time":""}io=Le("Milliseconds",false),a("z",0,0,"zoneAbbr"),a("zz",0,0,"zoneName");var P=J.prototype;if(P.add=Ia,P.calendar=Ua,P.clone=Va,P.diff=Xa,P.endOf=_r,P.format=ar,P.from=rr,P.fromNow=or,P.to=ir,P.toNow=sr,P.get=Ne,P.invalidAt=Tr,P.isAfter=Ka,P.isBefore=qa,P.isBetween=Ga,P.isSame=$a,P.isSameOrAfter=Ja,P.isSameOrBefore=Qa,P.isValid=xr,P.lang=ur,P.locale=lr,P.localeData=cr,P.max=ea,P.min=Zn,P.parsingFlags=Cr,P.set=Pe,P.startOf=vr,P.subtract=Aa,P.toArray=kr,P.toObject=Sr,P.toDate=Mr,P.toISOString=tr,P.inspect=nr,typeof Symbol!=="undefined"&&Symbol.for!=null)P[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"};function co(e){return D(e*1e3)}function fo(){return D.apply(null,arguments).parseZone()}function po(e){return e}P.toJSON=Er,P.toString=er,P.unix=wr,P.valueOf=br,P.creationData=Lr,P.eraName=Pr,P.eraNarrow=jr,P.eraAbbr=Yr,P.eraYear=Ir,P.year=Mt,P.isLeapYear=kt,P.weekYear=Kr,P.isoWeekYear=qr,P.quarter=P.quarters=eo,P.month=gt,P.daysInMonth=yt,P.week=P.weeks=Pt,P.isoWeek=P.isoWeeks=jt,P.weeksInYear=Jr,P.weeksInWeekYear=Qr,P.isoWeeksInYear=Gr,P.isoWeeksInISOWeekYear=$r,P.date=to,P.day=P.days=$t,P.weekday=Jt,P.isoWeekday=Qt,P.dayOfYear=no,P.hour=P.hours=un,P.minute=P.minutes=ao,P.second=P.seconds=ro,P.millisecond=P.milliseconds=io,P.utcOffset=va,P.utc=ba,P.local=wa,P.parseZone=Ma,P.hasAlignedHourOffset=ka,P.isDST=Sa,P.isLocal=xa,P.isUtcOffset=Ca,P.isUtc=Ta,P.isUTC=Ta,P.zoneAbbr=lo,P.zoneName=uo,P.dates=e("dates accessor is deprecated. Use date instead.",to),P.months=e("months accessor is deprecated. Use month instead",gt),P.years=e("years accessor is deprecated. Use year instead",Mt),P.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",_a),P.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Ea);var j=ae.prototype;function ho(e,t,n,a){var r=Mn(),o=c().set(a,t);return r[n](o,e)}function mo(e,t,n){if(u(e)){t=e;e=undefined}e=e||"";if(t!=null)return ho(e,t,n,"month");var a,r=[];for(a=0;a<12;a++)r[a]=ho(e,a,n,"month");return r}function go(e,t,n,a){if(typeof e==="boolean"){if(u(t)){n=t;t=undefined}t=t||""}else{t=e;n=t;e=false;if(u(t)){n=t;t=undefined}t=t||""}var r=Mn(),o=e?r._week.dow:0,i,s=[];if(n!=null)return ho(t,(n+o)%7,a,"day");for(i=0;i<7;i++)s[i]=ho(t,(i+o)%7,a,"day");return s}function yo(e,t){return mo(e,t,"months")}function vo(e,t){return mo(e,t,"monthsShort")}function _o(e,t,n){return go(e,t,n,"weekdays")}function bo(e,t,n){return go(e,t,n,"weekdaysShort")}function wo(e,t,n){return go(e,t,n,"weekdaysMin")}j.calendar=oe,j.longDateFormat=me,j.invalidDate=ye,j.ordinal=be,j.preparse=po,j.postformat=po,j.relativeTime=Me,j.pastFuture=ke,j.set=te,j.eras=Or,j.erasParse=Dr,j.erasConvertYear=Nr,j.erasAbbrRegex=Rr,j.erasNameRegex=Ar,j.erasNarrowRegex=Hr,j.months=dt,j.monthsShort=ft,j.monthsParse=ht,j.monthsRegex=_t,j.monthsShortRegex=vt,j.week=Lt,j.firstDayOfYear=Nt,j.firstDayOfWeek=Dt,j.weekdays=Ut,j.weekdaysMin=Kt,j.weekdaysShort=Vt,j.weekdaysParse=Gt,j.weekdaysRegex=Xt,j.weekdaysShortRegex=Zt,j.weekdaysMinRegex=en,j.isPM=sn,j.meridiem=cn,_n("en",{eras:[{since:"0001-01-01",until:+Infinity,offset:1,name:"Anno Domini",narrow:"AD",abbr:"AD"},{since:"0000-12-31",until:-Infinity,offset:1,name:"Before Christ",narrow:"BC",abbr:"BC"}],dayOfMonthOrdinalParse:/\d{1,2}(th|st|nd|rd)/,ordinal:function(e){var t=e%10,n=y(e%100/10)===1?"th":t===1?"st":t===2?"nd":t===3?"rd":"th";return e+n}}),d.lang=e("moment.lang is deprecated. Use moment.locale instead.",_n),d.langData=e("moment.langData is deprecated. Use moment.localeData instead.",Mn);var Mo=Math.abs;function ko(){var e=this._data;this._milliseconds=Mo(this._milliseconds);this._days=Mo(this._days);this._months=Mo(this._months);e.milliseconds=Mo(e.milliseconds);e.seconds=Mo(e.seconds);e.minutes=Mo(e.minutes);e.hours=Mo(e.hours);e.months=Mo(e.months);e.years=Mo(e.years);return this}function So(e,t,n,a){var r=N(t,n);e._milliseconds+=a*r._milliseconds;e._days+=a*r._days;e._months+=a*r._months;return e._bubble()}function Eo(e,t){return So(this,e,t,1)}function xo(e,t){return So(this,e,t,-1)}function Co(e){if(e<0)return Math.floor(e);else return Math.ceil(e)}function To(){var e=this._milliseconds,t=this._days,n=this._months,a=this._data,r,o,i,s,l;if(!(e>=0&&t>=0&&n>=0||e<=0&&t<=0&&n<=0)){e+=Co(Oo(n)+t)*864e5;t=0;n=0}a.milliseconds=e%1e3;r=g(e/1e3);a.seconds=r%60;o=g(r/60);a.minutes=o%60;i=g(o/60);a.hours=i%24;t+=g(i/24);l=g(Lo(t));n+=l;t-=Co(Oo(l));s=g(n/12);n%=12;a.days=t;a.months=n;a.years=s;return this}function Lo(e){return e*4800/146097}function Oo(e){return e*146097/4800}function Do(e){if(!this.isValid())return NaN;var t,n,a=this._milliseconds;e=m(e);if(e==="month"||e==="quarter"||e==="year"){t=this._days+a/864e5;n=this._months+Lo(t);switch(e){case"month":return n;case"quarter":return n/3;case"year":return n/12}}else{t=this._days+Math.round(Oo(this._months));switch(e){case"week":return t/7+a/6048e5;case"day":return t+a/864e5;case"hour":return t*24+a/36e5;case"minute":return t*1440+a/6e4;case"second":return t*86400+a/1e3;case"millisecond":return Math.floor(t*864e5)+a;default:throw new Error("Unknown unit "+e)}}}function No(){if(!this.isValid())return NaN;return this._milliseconds+this._days*864e5+this._months%12*2592e6+y(this._months/12)*31536e6}function Po(e){return function(){return this.as(e)}}var jo=Po("ms"),Yo=Po("s"),Io=Po("m"),Ao=Po("h"),Ro=Po("d"),Ho=Po("w"),Fo=Po("M"),zo=Po("Q"),Wo=Po("y");function Bo(){return N(this)}function Uo(e){e=m(e);return this.isValid()?this[e+"s"]():NaN}function Vo(e){return function(){return this.isValid()?this._data[e]:NaN}}var Ko=Vo("milliseconds"),qo=Vo("seconds"),Go=Vo("minutes"),$o=Vo("hours"),Jo=Vo("days"),Qo=Vo("months"),Xo=Vo("years");function Zo(){return g(this.days()/7)}var ei=Math.round,ti={ss:44,s:45,m:45,h:22,d:26,w:null,M:11};function ni(e,t,n,a,r){return r.relativeTime(t||1,!!n,e,a)}function ai(e,t,n,a){var r=N(e).abs(),o=ei(r.as("s")),i=ei(r.as("m")),s=ei(r.as("h")),l=ei(r.as("d")),u=ei(r.as("M")),c=ei(r.as("w")),d=ei(r.as("y")),f=o<=n.ss&&["s",o]||o0;f[4]=a;return ni.apply(null,f)}function ri(e){if(e===undefined)return ei;if(typeof e==="function"){ei=e;return true}return false}function oi(e,t){if(ti[e]===undefined)return false;if(t===undefined)return ti[e];ti[e]=t;if(e==="s")ti.ss=t-1;return true}function ii(e,t){if(!this.isValid())return this.localeData().invalidDate();var n=false,a=ti,r,o;if(typeof e==="object"){t=e;e=false}if(typeof e==="boolean")n=e;if(typeof t==="object"){a=Object.assign({},ti,t);if(t.s!=null&&t.ss==null)a.ss=t.s-1}r=this.localeData();o=ai(this,!n,a,r);if(n)o=r.pastFuture(+this,o);return r.postformat(o)}var si=Math.abs;function li(e){return(e>0)-(e<0)||+e}function ui(){if(!this.isValid())return this.localeData().invalidDate();var e=si(this._milliseconds)/1e3,t=si(this._days),n=si(this._months),a,r,o,i,s=this.asSeconds(),l,u,c,d;if(!s)return"P0D";a=g(e/60);r=g(a/60);e%=60;a%=60;o=g(n/12);n%=12;i=e?e.toFixed(3).replace(/\.?0+$/,""):"";l=s<0?"-":"";u=li(this._months)!==li(s)?"-":"";c=li(this._days)!==li(s)?"-":"";d=li(this._milliseconds)!==li(s)?"-":"";return l+"P"+(o?u+o+"Y":"")+(n?u+n+"M":"")+(t?c+t+"D":"")+(r||a||e?"T":"")+(r?d+r+"H":"")+(a?d+a+"M":"")+(e?d+i+"S":"")}var Y=ua.prototype;return Y.isValid=sa,Y.abs=ko,Y.add=Eo,Y.subtract=xo,Y.as=Do,Y.asMilliseconds=jo,Y.asSeconds=Yo,Y.asMinutes=Io,Y.asHours=Ao,Y.asDays=Ro,Y.asWeeks=Ho,Y.asMonths=Fo,Y.asQuarters=zo,Y.asYears=Wo,Y.valueOf=No,Y._bubble=To,Y.clone=Bo,Y.get=Uo,Y.milliseconds=Ko,Y.seconds=qo,Y.minutes=Go,Y.hours=$o,Y.days=Jo,Y.weeks=Zo,Y.months=Qo,Y.years=Xo,Y.humanize=ii,Y.toISOString=ui,Y.toString=ui,Y.toJSON=ui,Y.locale=lr,Y.localeData=cr,Y.toIsoString=e("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ui),Y.lang=ur,a("X",0,0,"unix"),a("x",0,0,"valueOf"),_("x",Ue),_("X",qe),w("X",function(e,t,n){n._d=new Date(parseFloat(e)*1e3)}),w("x",function(e,t,n){n._d=new Date(y(e))}), //! moment.js -d.version="2.29.4",R(D),d.fn=P,d.min=na,d.max=aa,d.now=ra,d.utc=c,d.unix=co,d.months=yo,d.isDate=z,d.locale=_n,d.invalid=K,d.duration=N,d.isMoment=p,d.weekdays=_o,d.parseZone=fo,d.localeData=Mn,d.isDuration=ca,d.monthsShort=vo,d.weekdaysMin=wo,d.defineLocale=bn,d.updateLocale=wn,d.locales=kn,d.weekdaysShort=bo,d.normalizeUnits=m,d.relativeTimeRounding=ri,d.relativeTimeThreshold=oi,d.calendarFormat=Ba,d.prototype=P,d.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},d}()}.call(this,fi(535)(e))},function(e,t,n){"use strict";t.__esModule=!0;var a=u(n(3)),r=u(n(16)),o=u(n(6)),i=u(n(373)),s=u(n(601)),l=u(n(602)),n=u(n(375));function u(e){return e&&e.__esModule?e:{default:e}}i.default.Password=o.default.config(s.default,{exportNames:["getInputNode","focus"],transform:function(e,t){var n;return"hasLimitHint"in e&&(t("hasLimitHint","showLimitHint","Input"),n=(t=e).hasLimitHint,t=(0,r.default)(t,["hasLimitHint"]),e=(0,a.default)({showLimitHint:n},t)),e}}),i.default.TextArea=o.default.config(l.default,{exportNames:["getInputNode","focus"],transform:function(e,t){var n;return"hasLimitHint"in e&&(t("hasLimitHint","showLimitHint","Input"),n=(t=e).hasLimitHint,t=(0,r.default)(t,["hasLimitHint"]),e=(0,a.default)({showLimitHint:n},t)),e}}),i.default.Group=n.default,t.default=o.default.config(i.default,{exportNames:["getInputNode","focus"],transform:function(e,t){var n;return"hasLimitHint"in e&&(t("hasLimitHint","showLimitHint","Input"),n=(t=e).hasLimitHint,t=(0,r.default)(t,["hasLimitHint"]),e=(0,a.default)({showLimitHint:n},t)),e}}),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0,t.pickAttrs=t.datejs=t.htmlId=t.KEYCODE=t.guid=t.focus=t.support=t.str=t.obj=t.log=t.func=t.events=t.env=t.dom=void 0;var a=y(n(215)),r=y(n(218)),o=y(n(518)),i=y(n(519)),s=y(n(217)),l=y(n(101)),u=y(n(216)),c=y(n(527)),d=y(n(528)),f=y(n(529)),p=g(n(530)),h=g(n(220)),m=g(n(164)),n=g(n(531));function g(e){return e&&e.__esModule?e:{default:e}}function y(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}t.dom=a,t.env=r,t.events=o,t.func=i,t.log=s,t.obj=l,t.str=u,t.support=c,t.focus=d,t.guid=p.default,t.KEYCODE=h.default,t.htmlId=f,t.datejs=m.default,t.pickAttrs=n.default},function(e,t,n){"use strict";function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}n.d(t,"a",function(){return a})},function(e,t,n){"use strict";function a(e,t){for(var n=0;n")),"shouldUpdatePosition"in r&&(delete t.shouldUpdatePosition,c.log.warning("Warning: [ shouldUpdatePosition ] is deprecated at [ ]")),"minMargin"in r&&o("minMargin","top/bottom",""),"isFullScreen"in r&&(r.overFlowScroll=!r.isFullScreen,delete t.isFullScreen,o("isFullScreen","overFlowScroll","")),t):(["target","offset","beforeOpen","onOpen","afterOpen","beforePosition","onPosition","cache","safeNode","wrapperClassName","container"].forEach(function(e){var t,n,a;e in r&&(o(e,"overlayProps."+e,"Dialog"),t=(n=r).overlayProps,n=(0,s.default)(n,["overlayProps"]),a=(0,i.default)(((a={})[e]=r[e],a),t||{}),delete n[e],r=(0,i.default)({overlayProps:a},n))}),r)}n.displayName="Dialog",n.Inner=p.default,n.show=function(e){return!1!==u.default.getContextProps(e,"Dialog").warning&&(e=v(e,c.log.deprecated)),(0,h.show)(e)},n.alert=function(e){return!1!==u.default.getContextProps(e,"Dialog").warning&&(e=v(e,c.log.deprecated)),(0,h.alert)(e)},n.confirm=function(e){return!1!==u.default.getContextProps(e,"Dialog").warning&&(e=v(e,c.log.deprecated)),(0,h.confirm)(e)},n.success=function(e){return(0,h.success)(e)},n.error=function(e){return(0,h.error)(e)},n.notice=function(e){return(0,h.notice)(e)},n.warning=function(e){return(0,h.warning)(e)},n.help=function(e){return(0,h.help)(e)},n.withContext=h.withContext,t.default=u.default.config(n,{transform:v}),e.exports=t.default},function(e,t,n){var a; +d.version="2.29.4",R(D),d.fn=P,d.min=na,d.max=aa,d.now=ra,d.utc=c,d.unix=co,d.months=yo,d.isDate=z,d.locale=_n,d.invalid=K,d.duration=N,d.isMoment=p,d.weekdays=_o,d.parseZone=fo,d.localeData=Mn,d.isDuration=ca,d.monthsShort=vo,d.weekdaysMin=wo,d.defineLocale=bn,d.updateLocale=wn,d.locales=kn,d.weekdaysShort=bo,d.normalizeUnits=m,d.relativeTimeRounding=ri,d.relativeTimeThreshold=oi,d.calendarFormat=Ba,d.prototype=P,d.HTML5_FMT={DATETIME_LOCAL:"YYYY-MM-DDTHH:mm",DATETIME_LOCAL_SECONDS:"YYYY-MM-DDTHH:mm:ss",DATETIME_LOCAL_MS:"YYYY-MM-DDTHH:mm:ss.SSS",DATE:"YYYY-MM-DD",TIME:"HH:mm",TIME_SECONDS:"HH:mm:ss",TIME_MS:"HH:mm:ss.SSS",WEEK:"GGGG-[W]WW",MONTH:"YYYY-MM"},d}()}.call(this,fi(535)(e))},function(e,t,n){"use strict";t.__esModule=!0;var a=u(n(3)),r=u(n(17)),o=u(n(6)),i=u(n(373)),s=u(n(601)),l=u(n(602)),n=u(n(375));function u(e){return e&&e.__esModule?e:{default:e}}i.default.Password=o.default.config(s.default,{exportNames:["getInputNode","focus"],transform:function(e,t){var n;return"hasLimitHint"in e&&(t("hasLimitHint","showLimitHint","Input"),n=(t=e).hasLimitHint,t=(0,r.default)(t,["hasLimitHint"]),e=(0,a.default)({showLimitHint:n},t)),e}}),i.default.TextArea=o.default.config(l.default,{exportNames:["getInputNode","focus"],transform:function(e,t){var n;return"hasLimitHint"in e&&(t("hasLimitHint","showLimitHint","Input"),n=(t=e).hasLimitHint,t=(0,r.default)(t,["hasLimitHint"]),e=(0,a.default)({showLimitHint:n},t)),e}}),i.default.Group=n.default,t.default=o.default.config(i.default,{exportNames:["getInputNode","focus"],transform:function(e,t){var n;return"hasLimitHint"in e&&(t("hasLimitHint","showLimitHint","Input"),n=(t=e).hasLimitHint,t=(0,r.default)(t,["hasLimitHint"]),e=(0,a.default)({showLimitHint:n},t)),e}}),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0,t.pickAttrs=t.datejs=t.htmlId=t.KEYCODE=t.guid=t.focus=t.support=t.str=t.obj=t.log=t.func=t.events=t.env=t.dom=void 0;var a=y(n(215)),r=y(n(218)),o=y(n(518)),i=y(n(519)),s=y(n(217)),l=y(n(101)),u=y(n(216)),c=y(n(527)),d=y(n(528)),f=y(n(529)),p=g(n(530)),h=g(n(220)),m=g(n(164)),n=g(n(531));function g(e){return e&&e.__esModule?e:{default:e}}function y(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n]);return t.default=e,t}t.dom=a,t.env=r,t.events=o,t.func=i,t.log=s,t.obj=l,t.str=u,t.support=c,t.focus=d,t.guid=p.default,t.KEYCODE=h.default,t.htmlId=f,t.datejs=m.default,t.pickAttrs=n.default},function(e,t,n){"use strict";n.d(t,"a",function(){return o});var a=n(94);function r(t,e){var n,a=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),a.push.apply(a,n)),a}function o(t){for(var e=1;e")),"shouldUpdatePosition"in r&&(delete t.shouldUpdatePosition,c.log.warning("Warning: [ shouldUpdatePosition ] is deprecated at [ ]")),"minMargin"in r&&o("minMargin","top/bottom",""),"isFullScreen"in r&&(r.overFlowScroll=!r.isFullScreen,delete t.isFullScreen,o("isFullScreen","overFlowScroll","")),t):(["target","offset","beforeOpen","onOpen","afterOpen","beforePosition","onPosition","cache","safeNode","wrapperClassName","container"].forEach(function(e){var t,n,a;e in r&&(o(e,"overlayProps."+e,"Dialog"),t=(n=r).overlayProps,n=(0,s.default)(n,["overlayProps"]),a=(0,i.default)(((a={})[e]=r[e],a),t||{}),delete n[e],r=(0,i.default)({overlayProps:a},n))}),r)}n.displayName="Dialog",n.Inner=p.default,n.show=function(e){return!1!==u.default.getContextProps(e,"Dialog").warning&&(e=v(e,c.log.deprecated)),(0,h.show)(e)},n.alert=function(e){return!1!==u.default.getContextProps(e,"Dialog").warning&&(e=v(e,c.log.deprecated)),(0,h.alert)(e)},n.confirm=function(e){return!1!==u.default.getContextProps(e,"Dialog").warning&&(e=v(e,c.log.deprecated)),(0,h.confirm)(e)},n.success=function(e){return(0,h.success)(e)},n.error=function(e){return(0,h.error)(e)},n.notice=function(e){return(0,h.notice)(e)},n.warning=function(e){return(0,h.warning)(e)},n.help=function(e){return(0,h.help)(e)},n.withContext=h.withContext,t.default=u.default.config(n,{transform:v}),e.exports=t.default},function(e,t,n){var a; /*! Copyright (c) 2018 Jed Watson. Licensed under the MIT License (MIT), see http://jedwatson.github.io/classnames */ -!function(){"use strict";var i={}.hasOwnProperty;function s(){for(var e=[],t=0;t 16.8.0")},p.prototype.validate=function(e,t){this.validateCallback(e,t)},p.prototype.reset=function(e){var t=1","Select");t=l(e,t);return e.onInputUpdate&&(t.onSearch=e.onInputUpdate,t.showSearch=!0),t}}),t.default=a.default.config(r.default,{transform:l,exportNames:["focusInput","handleSearchClear"]}),e.exports=t.default},function(M,e,t){"use strict";t.d(e,"a",function(){return a}),t.d(e,"b",function(){return U});var x=t(0),C=t.n(x),c=C.a.createContext(null);function l(){return n}var n=function(e){e()};var r={notify:function(){},get:function(){return[]}};function T(t,n){var o,i=r;function s(){e.onStateChange&&e.onStateChange()}function a(){var e,a,r;o||(o=n?n.addNestedSub(s):t.subscribe(s),e=l(),r=a=null,i={clear:function(){r=a=null},notify:function(){e(function(){for(var e=a;e;)e.callback(),e=e.next})},get:function(){for(var e=[],t=a;t;)e.push(t),t=t.next;return e},subscribe:function(e){var t=!0,n=r={callback:e,next:null,prev:r};return n.prev?n.prev.next=n:a=n,function(){t&&null!==a&&(t=!1,n.next?n.next.prev=n.prev:r=n.prev,n.prev?n.prev.next=n.next:a=n.next)}}})}var e={addNestedSub:function(e){return a(),i.subscribe(e)},notifyNestedSubs:function(){i.notify()},handleChangeWrapper:s,isSubscribed:function(){return Boolean(o)},trySubscribe:a,tryUnsubscribe:function(){o&&(o(),o=void 0,i.clear(),i=r)},getListeners:function(){return i}};return e}var o="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?x.useLayoutEffect:x.useEffect;var a=function(e){var t=e.store,n=e.context,e=e.children,a=Object(x.useMemo)(function(){var e=T(t);return{store:t,subscription:e}},[t]),r=Object(x.useMemo)(function(){return t.getState()},[t]),n=(o(function(){var e=a.subscription;return e.onStateChange=e.notifyNestedSubs,e.trySubscribe(),r!==t.getState()&&e.notifyNestedSubs(),function(){e.tryUnsubscribe(),e.onStateChange=null}},[a,r]),n||c);return C.a.createElement(n.Provider,{value:a},e)},L=t(41),O=t(56),e=t(107),d=t.n(e),D=t(430),f=["getDisplayName","methodName","renderCountProp","shouldHandleStateChanges","storeKey","withRef","forwardRef","context"],N=["reactReduxForwardedRef"],P=[],j=[null,null];function Y(e,t){e=e[1];return[t.payload,e+1]}function I(e,t,n){o(function(){return e.apply(void 0,t)},n)}function A(e,t,n,a,r,o,i){e.current=a,t.current=r,n.current=!1,o.current&&(o.current=null,i())}function R(e,a,t,r,o,i,s,l,u,c){var d,f;if(e)return d=!1,f=null,t.onStateChange=e=function(){if(!d){var e,t,n=a.getState();try{e=r(n,o.current)}catch(e){f=t=e}t||(f=null),e===i.current?s.current||u():(i.current=e,l.current=e,s.current=!0,c({type:"STORE_UPDATED",payload:{error:t}}))}},t.trySubscribe(),e(),function(){if(d=!0,t.tryUnsubscribe(),t.onStateChange=null,f)throw f}}var H=function(){return[null,0]};function i(k,e){var e=e=void 0===e?{}:e,t=e.getDisplayName,r=void 0===t?function(e){return"ConnectAdvanced("+e+")"}:t,t=e.methodName,o=void 0===t?"connectAdvanced":t,t=e.renderCountProp,i=void 0===t?void 0:t,t=e.shouldHandleStateChanges,S=void 0===t||t,t=e.storeKey,s=void 0===t?"store":t,t=(e.withRef,e.forwardRef),l=void 0!==t&&t,t=e.context,t=void 0===t?c:t,u=Object(O.a)(e,f),E=t;return function(b){var e=b.displayName||b.name||"Component",t=r(e),w=Object(L.a)({},u,{getDisplayName:r,methodName:o,renderCountProp:i,shouldHandleStateChanges:S,storeKey:s,displayName:t,wrappedComponentName:e,WrappedComponent:b}),e=u.pure;var M=e?x.useMemo:function(e){return e()};function n(n){var e=Object(x.useMemo)(function(){var e=n.reactReduxForwardedRef,t=Object(O.a)(n,N);return[n.context,e,t]},[n]),t=e[0],a=e[1],r=e[2],o=Object(x.useMemo)(function(){return t&&t.Consumer&&Object(D.isContextConsumer)(C.a.createElement(t.Consumer,null))?t:E},[t,E]),i=Object(x.useContext)(o),s=Boolean(n.store)&&Boolean(n.store.getState)&&Boolean(n.store.dispatch),l=(Boolean(i)&&Boolean(i.store),(s?n:i).store),u=Object(x.useMemo)(function(){return k(l.dispatch,w)},[l]),e=Object(x.useMemo)(function(){var e,t;return S?(t=(e=T(l,s?null:i.subscription)).notifyNestedSubs.bind(e),[e,t]):j},[l,s,i]),c=e[0],e=e[1],d=Object(x.useMemo)(function(){return s?i:Object(L.a)({},i,{subscription:c})},[s,i,c]),f=Object(x.useReducer)(Y,P,H),p=f[0][0],f=f[1];if(p&&p.error)throw p.error;var h=Object(x.useRef)(),m=Object(x.useRef)(r),g=Object(x.useRef)(),y=Object(x.useRef)(!1),v=M(function(){return g.current&&r===m.current?g.current:u(l.getState(),r)},[l,p,r]),_=(I(A,[m,h,y,r,v,g,e]),I(R,[S,l,c,u,m,h,y,g,e,f],[l,c,u]),Object(x.useMemo)(function(){return C.a.createElement(b,Object(L.a)({},v,{ref:a}))},[a,b,v]));return Object(x.useMemo)(function(){return S?C.a.createElement(o.Provider,{value:d},_):_},[o,_,d])}var a=e?C.a.memo(n):n;return a.WrappedComponent=b,a.displayName=n.displayName=t,l?((e=C.a.forwardRef(function(e,t){return C.a.createElement(a,Object(L.a)({},e,{reactReduxForwardedRef:t}))})).displayName=t,e.WrappedComponent=b,d()(e,b)):d()(a,b)}}function s(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function m(e,t){if(!s(e,t)){if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),a=Object.keys(t);if(n.length!==a.length)return!1;for(var r=0;re?t.splice(e,t.length-e,n):t.push(n),i({action:"PUSH",location:n,index:e,entries:t}))})},replace:function(e,t){var n=D(e,t,s(),u.location);o.confirmTransitionTo(n,"REPLACE",a,function(e){e&&i({action:"REPLACE",location:u.entries[u.index]=n})})},go:l,goBack:function(){l(-1)},goForward:function(){l(1)},canGo:function(e){return 0<=(e=u.index+e)&&ex',"Tag"),"readonly"!==n&&"interactive"!==n||r.log.warning("Warning: [ shape="+n+" ] is deprecated at [ Tag ]"),"secondary"===a&&r.log.warning("Warning: [ type=secondary ] is deprecated at [ Tag ]"),["count","marked","value","onChange"].forEach(function(e){e in t&&r.log.warning("Warning: [ "+e+" ] is deprecated at [ Tag ]")}),("selected"in t||"defaultSelected"in t)&&r.log.warning("Warning: [ selected|defaultSelected ] is deprecated at [ Tag ], use [ checked|defaultChecked ] at [ Tag.Selectable ] instead of it"),"closed"in t&&r.log.warning("Warning: [ closed ] is deprecated at [ Tag ], use [ onClose ] at [ Tag.Closeable ] instead of it"),"onSelect"in t&&e("onSelect","","Tag"),"afterClose"in t&&r.log.warning("Warning: [ afterClose ] is deprecated at [ Tag ], use [ afterClose ] at [ Tag.Closeable ] instead of it"),t}});o.Group=a.default.config(i.default),o.Selectable=a.default.config(s.default),o.Closable=a.default.config(n.default),o.Closeable=o.Closable,t.default=o,e.exports=t.default},function(e,t,n){"use strict";n(72),n(466)},function(e,t){e=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)},function(e,t){e=e.exports={version:"2.6.12"};"number"==typeof __e&&(__e=e)},function(e,t,n){e.exports=!n(113)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";t.__esModule=!0;var a=o(n(358)),r=o(n(545)),n=o(n(546));function o(e){return e&&e.__esModule?e:{default:e}}a.default.Expand=r.default,a.default.OverlayAnimate=n.default,t.default=a.default,e.exports=t.default},function(e,t,n){"use strict";n(43),n(72),n(114),n(115),n(559)},function(e,t,n){"use strict";t.__esModule=!0;var r=p(n(3)),o=p(n(16)),a=p(n(6)),i=p(n(648)),s=p(n(649)),l=p(n(395)),u=p(n(397)),c=p(n(650)),d=p(n(651)),f=p(n(396)),n=p(n(398));function p(e){return e&&e.__esModule?e:{default:e}}i.default.Header=s.default,i.default.Media=u.default,i.default.Divider=c.default,i.default.Content=d.default,i.default.Actions=n.default,i.default.BulletHeader=l.default,i.default.CollaspeContent=f.default,i.default.CollapseContent=f.default,t.default=a.default.config(i.default,{transform:function(e,t){var n,a;return"titlePrefixLine"in e&&(t("titlePrefixLine","showTitleBullet","Card"),a=(n=e).titlePrefixLine,n=(0,o.default)(n,["titlePrefixLine"]),e=(0,r.default)({showTitleBullet:a},n)),"titleBottomLine"in e&&(t("titleBottomLine","showHeadDivider","Card"),n=(a=e).titleBottomLine,a=(0,o.default)(a,["titleBottomLine"]),e=(0,r.default)({showHeadDivider:n},a)),"bodyHeight"in e&&(t("bodyHeight","contentHeight","Card"),a=(n=e).bodyHeight,t=(0,o.default)(n,["bodyHeight"]),e=(0,r.default)({contentHeight:a},t)),e}}),e.exports=t.default},function(e,t,n){"use strict";n.d(t,"b",function(){return s});var a=n(19),r=n(33),o=n(22),i={namespaces:[]},s=function(e){return function(n){return r.a.get("v1/console/namespaces",{params:e}).then(function(e){var t=e.code,e=e.data;n({type:o.b,data:200===t?e:[]})})}};t.a=function(){var e=0this.menuNode.clientHeight&&(this.menuNode.clientHeight+this.menuNode.scrollTop<(e=this.itemNode.offsetTop+this.itemNode.offsetHeight)?this.menuNode.scrollTop=e-this.menuNode.clientHeight:this.itemNode.offsetTope.length)&&(t=e.length);for(var n=0,a=new Array(t);n>16&255),o.push(r>>8&255),o.push(255&r)),r=r<<6|a.indexOf(t.charAt(i));return 0==(e=n%4*6)?(o.push(r>>16&255),o.push(r>>8&255),o.push(255&r)):18==e?(o.push(r>>10&255),o.push(r>>2&255)):12==e&&o.push(r>>4&255),new Uint8Array(o)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){for(var t,n="",a=0,r=e.length,o=g,i=0;i>18&63]+o[a>>12&63])+o[a>>6&63]+o[63&a]),a=(a<<8)+e[i];return 0==(t=r%3)?n=(n=n+o[a>>18&63]+o[a>>12&63])+o[a>>6&63]+o[63&a]:2==t?n=(n=n+o[a>>10&63]+o[a>>4&63])+o[a<<2&63]+o[64]:1==t&&(n=(n=n+o[a>>2&63]+o[a<<4&63])+o[64]+o[64]),n}}),V=Object.prototype.hasOwnProperty,K=Object.prototype.toString;var s=new a("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null!==e)for(var t,n,a,r=[],o=e,i=0,s=o.length;i>10),56320+(l-65536&1023)),e.position++}else x(e,"unknown escape sequence");n=a=e.position}else w(u)?(T(e,n,a,!0),P(e,D(e,!1,t)),n=a=e.position):e.position===e.lineStart&&N(e)?x(e,"unexpected end of the document within a double quoted scalar"):(e.position++,a=e.position)}x(e,"unexpected end of the stream within a double quoted scalar")}}function ge(e,t){var n,a,r=e.tag,o=e.anchor,i=[],s=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=i),a=e.input.charCodeAt(e.position);0!==a&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,x(e,"tab characters must not be used in indentation")),45===a)&&k(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,D(e,!0,-1)&&e.lineIndent<=t)i.push(null),a=e.input.charCodeAt(e.position);else if(n=e.line,j(e,t,Z,!1,!0),i.push(e.result),D(e,!0,-1),a=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==a)x(e,"bad indentation of a sequence entry");else if(e.lineIndentt?f=1:e.lineIndent===t?f=0:e.lineIndentt?f=1:e.lineIndent===t?f=0:e.lineIndentt)&&(y&&(i=e.line,s=e.lineStart,l=e.position),j(e,t,_,!0,r)&&(y?m=e.result:g=e.result),y||(L(e,f,p,h,m,g,i,s,l),h=m=g=null),D(e,!0,-1),u=e.input.charCodeAt(e.position)),(e.line===o||e.lineIndent>t)&&0!==u)x(e,"bad indentation of a mapping entry");else if(e.lineIndentl&&(l=e.lineIndent),w(d))u++;else{if(e.lineIndent=t){i=!0,f=e.input.charCodeAt(e.position);continue}e.position=o,e.line=s,e.lineStart=l,e.lineIndent=u;break}}i&&(T(e,r,o,!1),P(e,e.line-s),r=o=e.position,i=!1),M(f)||(o=e.position+1),f=e.input.charCodeAt(++e.position)}if(T(e,r,o,!1),e.result)return 1;e.kind=c,e.result=d}}(e,a,v===n)&&(h=!0,null===e.tag)&&(e.tag="?"):(h=!0,null===e.tag&&null===e.anchor||x(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===f&&(h=s&&ge(e,r))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&x(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),l=0,u=e.implicitTypes.length;l"),null!==e.result&&d.kind!==e.kind&&x(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+d.kind+'", not "'+e.kind+'"'),d.resolve(e.result,e.tag)?(e.result=d.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):x(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||h}function ye(e,t){t=t||{};var n=new de(e=0!==(e=String(e)).length&&(10!==e.charCodeAt(e.length-1)&&13!==e.charCodeAt(e.length-1)&&(e+="\n"),65279===e.charCodeAt(0))?e.slice(1):e,t),t=e.indexOf("\0");for(-1!==t&&(n.position=t,x(n,"null byte is not allowed in input")),n.input+="\0";32===n.input.charCodeAt(n.position);)n.lineIndent+=1,n.position+=1;for(;n.positiondocument.F=Object<\/script>"),e.close(),u=e.F;t--;)delete u[l][i[t]];return u()};e.exports=Object.create||function(e,t){var n;return null!==e?(a[l]=r(e),n=new a,a[l]=null,n[s]=e):n=u(),void 0===t?n:o(n,t)}},function(e,t,n){var a=n(89).f,r=n(90),o=n(100)("toStringTag");e.exports=function(e,t,n){e&&!r(e=n?e:e.prototype,o)&&a(e,o,{configurable:!0,value:t})}},function(e,t,n){t.f=n(100)},function(e,t,n){var a=n(80),r=n(81),o=n(128),i=n(162),s=n(89).f;e.exports=function(e){var t=r.Symbol||(r.Symbol=!o&&a.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:i.f(e)})}},function(e,t,n){"use strict";t.__esModule=!0;var a=c(n(219)),r=c(n(520)),o=c(n(521)),i=c(n(522)),s=c(n(523)),l=c(n(524)),u=c(n(525));function c(e){return e&&e.__esModule?e:{default:e}}n(526),a.default.extend(l.default),a.default.extend(s.default),a.default.extend(r.default),a.default.extend(o.default),a.default.extend(i.default),a.default.extend(u.default),a.default.locale("zh-cn");n=a.default;n.isSelf=a.default.isDayjs,a.default.localeData(),t.default=n,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var v=d(n(3)),o=d(n(4)),i=d(n(7)),a=d(n(8)),r=n(0),_=d(r),s=d(n(5)),l=n(31),b=d(n(18)),u=d(n(44)),w=d(n(26)),M=d(n(83)),c=d(n(6)),k=n(11);function d(e){return e&&e.__esModule?e:{default:e}}function f(){}p=r.Component,(0,a.default)(S,p),S.getDerivedStateFromProps=function(e){return"visible"in e?{visible:e.visible}:{}},S.prototype.render=function(){var e,t=this.props,n=t.prefix,a=(t.pure,t.className),r=t.style,o=t.type,i=t.shape,s=t.size,l=t.title,u=t.children,c=(t.defaultVisible,t.visible,t.iconType),d=t.closeable,f=(t.onClose,t.afterClose),p=t.animation,h=t.rtl,t=t.locale,m=(0,v.default)({},k.obj.pickOthers(Object.keys(S.propTypes),this.props)),g=this.state.visible,y=n+"message",o=(0,b.default)(((e={})[y]=!0,e[n+"message-"+o]=o,e[""+n+i]=i,e[""+n+s]=s,e[n+"title-content"]=!!l,e[n+"only-content"]=!l&&!!u,e[a]=a,e)),i=g?_.default.createElement("div",(0,v.default)({role:"alert",style:r},m,{className:o,dir:h?"rtl":void 0}),d?_.default.createElement("a",{role:"button","aria-label":t.closeAriaLabel,className:y+"-close",onClick:this.onClose},_.default.createElement(w.default,{type:"close"})):null,!1!==c?_.default.createElement(w.default,{className:y+"-symbol "+(!c&&y+"-symbol-icon"),type:c}):null,l?_.default.createElement("div",{className:y+"-title"},l):null,u?_.default.createElement("div",{className:y+"-content"},u):null):null;return p?_.default.createElement(M.default.Expand,{animationAppear:!1,afterLeave:f},i):i},r=n=S,n.propTypes={prefix:s.default.string,pure:s.default.bool,className:s.default.string,style:s.default.object,type:s.default.oneOf(["success","warning","error","notice","help","loading"]),shape:s.default.oneOf(["inline","addon","toast"]),size:s.default.oneOf(["medium","large"]),title:s.default.node,children:s.default.node,defaultVisible:s.default.bool,visible:s.default.bool,iconType:s.default.oneOfType([s.default.string,s.default.bool]),closeable:s.default.bool,onClose:s.default.func,afterClose:s.default.func,animation:s.default.bool,locale:s.default.object,rtl:s.default.bool},n.defaultProps={prefix:"next-",pure:!1,type:"success",shape:"inline",size:"medium",defaultVisible:!0,closeable:!1,onClose:f,afterClose:f,animation:!0,locale:u.default.Message};var p,a=r;function S(){var e,t;(0,o.default)(this,S);for(var n=arguments.length,a=Array(n),r=0;r=n.length?(l=!!(d=h(o,u)))&&"get"in d&&!("originalValue"in d.get)?d.get:o[u]:(l=_(o,u),o[u]),l&&!i&&(g[c]=o)}}return o}},function(e,t,n){"use strict";n=n(625);e.exports=Function.prototype.bind||n},function(e,t,n){"use strict";var a=String.prototype.replace,r=/%20/g,o="RFC1738",i="RFC3986";e.exports={default:i,formatters:{RFC1738:function(e){return a.call(e,r,"+")},RFC3986:function(e){return String(e)}},RFC1738:o,RFC3986:i}},function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var c=l(n(3)),a=l(n(4)),r=l(n(7)),o=l(n(8)),d=n(0),f=l(d),i=l(n(5)),p=l(n(18)),h=l(n(26)),s=n(11),m=l(n(104));function l(e){return e&&e.__esModule?e:{default:e}}var u,g=s.func.bindCtx,y=s.obj.pickOthers,i=(u=d.Component,(0,o.default)(v,u),v.prototype.getSelected=function(){var e=this.props,t=e._key,n=e.root,e=e.selected,a=n.props.selectMode,n=n.state.selectedKeys;return e||!!a&&-1e.length&&e.every(function(e,t){return e===n[t]})},t.isAvailablePos=function(e,t,n){var n=n[t],a=n.type,n=n.disabled;return r(e,t)&&("item"===a&&!n||"submenu"===a)});t.getFirstAvaliablelChildKey=function(t,n){var e=Object.keys(n).find(function(e){return a(t+"-0",e,n)});return e?n[e].key:null},t.getChildSelected=function(e){var t,n=e.selectMode,a=e.selectedKeys,r=e._k2n,e=e._key;return!!r&&(t=(r[e]&&r[e].pos)+"-",!!n)&&a.some(function(e){return r[e]&&0===r[e].pos.indexOf(t)})}},function(e,t,n){"use strict";n(43),n(72),n(654)},function(e,t,n){"use strict";t.__esModule=!0;var g=d(n(16)),y=d(n(3)),a=d(n(4)),r=d(n(7)),o=d(n(8)),i=n(0),v=d(i),s=d(n(5)),_=d(n(18)),l=d(n(83)),u=d(n(26)),b=n(11),c=d(n(44)),n=d(n(6));function d(e){return e&&e.__esModule?e:{default:e}}var f,p=b.func.noop,h=b.func.bindCtx,m=/blue|green|orange|red|turquoise|yellow/,s=(f=i.Component,(0,o.default)(w,f),w.prototype.componentWillUnmount=function(){this.__destroyed=!0},w.prototype.handleClose=function(e){var t=this,n=this.props,a=n.animation,n=n.onClose,r=b.support.animation&&a;!1===n(e,this.tagNode)||this.__destroyed||this.setState({visible:!1},function(){r||t.props.afterClose(t.tagNode)})},w.prototype.handleBodyClick=function(e){var t=this.props,n=t.closable,a=t.closeArea,t=t.onClick,r=e.currentTarget;if(r&&(r===e.target||r.contains(e.target))&&(n&&"tag"===a&&this.handleClose("tag"),"function"==typeof t))return t(e)},w.prototype.handleTailClick=function(e){e&&e.preventDefault(),e&&e.stopPropagation(),this.handleClose("tail")},w.prototype.handleAnimationInit=function(e){this.props.afterAppear(e)},w.prototype.handleAnimationEnd=function(e){this.props.afterClose(e)},w.prototype.renderAnimatedTag=function(e,t){return v.default.createElement(l.default,{animation:t,afterAppear:this.handleAnimationInit,afterLeave:this.handleAnimationEnd},e)},w.prototype.renderTailNode=function(){var e=this.props,t=e.prefix,n=e.closable,e=e.locale;return n?v.default.createElement("span",{className:t+"tag-close-btn",onClick:this.handleTailClick,role:"button","aria-label":e.delete},v.default.createElement(u.default,{type:"close"})):null},w.prototype.isPresetColor=function(){var e=this.props.color;return!!e&&m.test(e)},w.prototype.getTagStyle=function(){var e=this.props,t=e.color,t=void 0===t?"":t,e=e.style,n=this.isPresetColor();return(0,y.default)({},t&&!n?{backgroundColor:t,borderColor:t,color:"#fff"}:null,e)},w.prototype.render=function(){var t=this,e=this.props,n=e.prefix,a=e.type,r=e.size,o=e.color,i=e._shape,s=e.closable,l=e.closeArea,u=e.className,c=e.children,d=e.animation,f=e.disabled,e=e.rtl,p=this.state.visible,h=this.isPresetColor(),m=b.obj.pickOthers(w.propTypes,this.props),m=(m.style,(0,g.default)(m,["style"])),r=(0,_.default)([n+"tag",n+"tag-"+(s?"closable":i),n+"tag-"+r],((i={})[n+"tag-level-"+a]=!o,i[n+"tag-closable"]=s,i[n+"tag-body-pointer"]=s&&"tag"===l,i[n+"tag-"+o]=o&&h&&"primary"===a,i[n+"tag-"+o+"-inverse"]=o&&h&&"normal"===a,i),u),s=this.renderTailNode(),l=p?v.default.createElement("div",(0,y.default)({className:r,onClick:this.handleBodyClick,onKeyDown:this.onKeyDown,tabIndex:f?"":"0",role:"button","aria-disabled":f,disabled:f,dir:e?"rtl":void 0,ref:function(e){return t.tagNode=e},style:this.getTagStyle()},m),v.default.createElement("span",{className:n+"tag-body"},c),s):null;return d&&b.support.animation?this.renderAnimatedTag(l,n+"tag-zoom"):l},o=i=w,i.propTypes={prefix:s.default.string,type:s.default.oneOf(["normal","primary"]),size:s.default.oneOf(["small","medium","large"]),color:s.default.string,animation:s.default.bool,closeArea:s.default.oneOf(["tag","tail"]),closable:s.default.bool,onClose:s.default.func,afterClose:s.default.func,afterAppear:s.default.func,className:s.default.any,children:s.default.node,onClick:s.default.func,_shape:s.default.oneOf(["default","closable","checkable"]),disabled:s.default.bool,rtl:s.default.bool,locale:s.default.object},i.defaultProps={prefix:"next-",type:"normal",size:"medium",closeArea:"tail",animation:!1,onClose:p,afterClose:p,afterAppear:p,onClick:p,_shape:"default",disabled:!1,rtl:!1,locale:c.default.Tag},o);function w(e){(0,a.default)(this,w);var o=(0,r.default)(this,f.call(this,e));return o.onKeyDown=function(e){var t=o.props,n=t.closable,a=t.closeArea,r=t.onClick,t=t.disabled;e.keyCode!==b.KEYCODE.SPACE||t||(e.preventDefault(),e.stopPropagation(),n?o.handleClose(a):"function"==typeof r&&r(e))},o.state={visible:!0},h(o,["handleBodyClick","handleTailClick","handleAnimationInit","handleAnimationEnd","renderTailNode"]),o}s.displayName="Tag",t.default=n.default.config(s),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var f=r(n(16)),p=r(n(42)),h=r(n(3)),a=(t.isSingle=function(e){return!e||"single"===e},t.isNull=s,t.escapeForReg=o,t.filter=function(e,t){e=o(""+e),e=new RegExp("("+e+")","ig");return e.test(""+t.value)||e.test(""+t.label)},t.loopMap=i,t.parseDataSourceFromChildren=function i(e){var s=1{var t=new TomlError(e.message);return t.code=e.code,t.wrapped=e,t},module.exports.TomlError=TomlError;const createDateTime=__webpack_require__(697),createDateTimeFloat=__webpack_require__(698),createDate=__webpack_require__(699),createTime=__webpack_require__(700),CTRL_I=9,CTRL_J=10,CTRL_M=13,CTRL_CHAR_BOUNDARY=31,CHAR_SP=32,CHAR_QUOT=34,CHAR_NUM=35,CHAR_APOS=39,CHAR_PLUS=43,CHAR_COMMA=44,CHAR_HYPHEN=45,CHAR_PERIOD=46,CHAR_0=48,CHAR_1=49,CHAR_7=55,CHAR_9=57,CHAR_COLON=58,CHAR_EQUALS=61,CHAR_A=65,CHAR_E=69,CHAR_F=70,CHAR_T=84,CHAR_U=85,CHAR_Z=90,CHAR_LOWBAR=95,CHAR_a=97,CHAR_b=98,CHAR_e=101,CHAR_f=102,CHAR_i=105,CHAR_l=108,CHAR_n=110,CHAR_o=111,CHAR_r=114,CHAR_s=115,CHAR_t=116,CHAR_u=117,CHAR_x=120,CHAR_z=122,CHAR_LCUB=123,CHAR_RCUB=125,CHAR_LSQB=91,CHAR_BSOL=92,CHAR_RSQB=93,CHAR_DEL=127,SURROGATE_FIRST=55296,SURROGATE_LAST=57343,escapes={[CHAR_b]:"\b",[CHAR_t]:"\t",[CHAR_n]:"\n",[CHAR_f]:"\f",[CHAR_r]:"\r",[CHAR_QUOT]:'"',[CHAR_BSOL]:"\\"};function isDigit(e){return e>=CHAR_0&&e<=CHAR_9}function isHexit(e){return e>=CHAR_A&&e<=CHAR_F||e>=CHAR_a&&e<=CHAR_f||e>=CHAR_0&&e<=CHAR_9}function isBit(e){return e===CHAR_1||e===CHAR_0}function isOctit(e){return e>=CHAR_0&&e<=CHAR_7}function isAlphaNumQuoteHyphen(e){return e>=CHAR_A&&e<=CHAR_Z||e>=CHAR_a&&e<=CHAR_z||e>=CHAR_0&&e<=CHAR_9||e===CHAR_APOS||e===CHAR_QUOT||e===CHAR_LOWBAR||e===CHAR_HYPHEN}function isAlphaNumHyphen(e){return e>=CHAR_A&&e<=CHAR_Z||e>=CHAR_a&&e<=CHAR_z||e>=CHAR_0&&e<=CHAR_9||e===CHAR_LOWBAR||e===CHAR_HYPHEN}const _type=Symbol("type"),_declared=Symbol("declared"),hasOwnProperty=Object.prototype.hasOwnProperty,defineProperty=Object.defineProperty,descriptor={configurable:!0,enumerable:!0,writable:!0,value:void 0};function hasKey(e,t){if(hasOwnProperty.call(e,t))return 1;"__proto__"===t&&defineProperty(e,"__proto__",descriptor)}const INLINE_TABLE=Symbol("inline-table");function InlineTable(){return Object.defineProperties({},{[_type]:{value:INLINE_TABLE}})}function isInlineTable(e){return null!==e&&"object"==typeof e&&e[_type]===INLINE_TABLE}const TABLE=Symbol("table");function Table(){return Object.defineProperties({},{[_type]:{value:TABLE},[_declared]:{value:!1,writable:!0}})}function isTable(e){return null!==e&&"object"==typeof e&&e[_type]===TABLE}const _contentType=Symbol("content-type"),INLINE_LIST=Symbol("inline-list");function InlineList(e){return Object.defineProperties([],{[_type]:{value:INLINE_LIST},[_contentType]:{value:e}})}function isInlineList(e){return null!==e&&"object"==typeof e&&e[_type]===INLINE_LIST}const LIST=Symbol("list");function List(){return Object.defineProperties([],{[_type]:{value:LIST}})}function isList(e){return null!==e&&"object"==typeof e&&e[_type]===LIST}let _custom;try{const utilInspect=eval("require('util').inspect");_custom=utilInspect.custom}catch(_){}const _inspect=_custom||"inspect";class BoxedBigInt{constructor(e){try{this.value=global.BigInt.asIntN(64,e)}catch(e){this.value=null}Object.defineProperty(this,_type,{value:INTEGER})}isNaN(){return null===this.value}toString(){return String(this.value)}[_inspect](){return`[BigInt: ${this.toString()}]}`}valueOf(){return this.value}}const INTEGER=Symbol("integer");function Integer(e){let t=Number(e);return Object.is(t,-0)&&(t=0),global.BigInt&&!Number.isSafeInteger(t)?new BoxedBigInt(e):Object.defineProperties(new Number(t),{isNaN:{value:function(){return isNaN(this)}},[_type]:{value:INTEGER},[_inspect]:{value:()=>`[Integer: ${e}]`}})}function isInteger(e){return null!==e&&"object"==typeof e&&e[_type]===INTEGER}const FLOAT=Symbol("float");function Float(e){return Object.defineProperties(new Number(e),{[_type]:{value:FLOAT},[_inspect]:{value:()=>`[Float: ${e}]`}})}function isFloat(e){return null!==e&&"object"==typeof e&&e[_type]===FLOAT}function tomlType(e){var t=typeof e;if("object"==t){if(null===e)return"null";if(e instanceof Date)return"datetime";if(_type in e)switch(e[_type]){case INLINE_TABLE:return"inline-table";case INLINE_LIST:return"inline-list";case TABLE:return"table";case LIST:return"list";case FLOAT:return"float";case INTEGER:return"integer"}}return t}function makeParserClass(e){class t extends e{constructor(){super(),this.ctx=this.obj=Table()}atEndOfWord(){return this.char===CHAR_NUM||this.char===CTRL_I||this.char===CHAR_SP||this.atEndOfLine()}atEndOfLine(){return this.char===e.END||this.char===CTRL_J||this.char===CTRL_M}parseStart(){if(this.char===e.END)return null;if(this.char===CHAR_LSQB)return this.call(this.parseTableOrList);if(this.char===CHAR_NUM)return this.call(this.parseComment);if(this.char===CTRL_J||this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M)return null;if(isAlphaNumQuoteHyphen(this.char))return this.callNow(this.parseAssignStatement);throw this.error(new TomlError(`Unknown character "${this.char}"`))}parseWhitespaceToEOL(){if(this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M)return null;if(this.char===CHAR_NUM)return this.goto(this.parseComment);if(this.char===e.END||this.char===CTRL_J)return this.return();throw this.error(new TomlError("Unexpected character, expected only whitespace or comments till end of line"))}parseAssignStatement(){return this.callNow(this.parseAssign,this.recordAssignStatement)}recordAssignStatement(e){let t=this.ctx;var n,a=e.key.pop();for(n of e.key){if(hasKey(t,n)&&!isTable(t[n]))throw this.error(new TomlError("Can't redefine existing key"));t=t[n]=t[n]||Table()}if(hasKey(t,a))throw this.error(new TomlError("Can't redefine existing key"));return t[_declared]=!0,isInteger(e.value)||isFloat(e.value)?t[a]=e.value.valueOf():t[a]=e.value,this.goto(this.parseWhitespaceToEOL)}parseAssign(){return this.callNow(this.parseKeyword,this.recordAssignKeyword)}recordAssignKeyword(e){return this.state.resultTable?this.state.resultTable.push(e):this.state.resultTable=[e],this.goto(this.parseAssignKeywordPreDot)}parseAssignKeywordPreDot(){return this.char===CHAR_PERIOD?this.next(this.parseAssignKeywordPostDot):this.char!==CHAR_SP&&this.char!==CTRL_I?this.goto(this.parseAssignEqual):void 0}parseAssignKeywordPostDot(){if(this.char!==CHAR_SP&&this.char!==CTRL_I)return this.callNow(this.parseKeyword,this.recordAssignKeyword)}parseAssignEqual(){if(this.char===CHAR_EQUALS)return this.next(this.parseAssignPreValue);throw this.error(new TomlError('Invalid character, expected "="'))}parseAssignPreValue(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseValue,this.recordAssignValue)}recordAssignValue(e){return this.returnNow({key:this.state.resultTable,value:e})}parseComment(){do{if(this.char===e.END||this.char===CTRL_J)return this.return();if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("comments")}while(this.nextChar())}parseTableOrList(){if(this.char!==CHAR_LSQB)return this.goto(this.parseTable);this.next(this.parseList)}parseTable(){return this.ctx=this.obj,this.goto(this.parseTableNext)}parseTableNext(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseKeyword,this.parseTableMore)}parseTableMore(e){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CHAR_RSQB){if(!hasKey(this.ctx,e)||isTable(this.ctx[e])&&!this.ctx[e][_declared])return this.ctx=this.ctx[e]=this.ctx[e]||Table(),this.ctx[_declared]=!0,this.next(this.parseWhitespaceToEOL);throw this.error(new TomlError("Can't redefine existing key"))}if(this.char!==CHAR_PERIOD)throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"));if(hasKey(this.ctx,e))if(isTable(this.ctx[e]))this.ctx=this.ctx[e];else{if(!isList(this.ctx[e]))throw this.error(new TomlError("Can't redefine existing key"));this.ctx=this.ctx[e][this.ctx[e].length-1]}else this.ctx=this.ctx[e]=Table();return this.next(this.parseTableNext)}parseList(){return this.ctx=this.obj,this.goto(this.parseListNext)}parseListNext(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseKeyword,this.parseListMore)}parseListMore(e){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CHAR_RSQB){if(hasKey(this.ctx,e)||(this.ctx[e]=List()),isInlineList(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline array"));var t;if(isList(this.ctx[e]))return t=Table(),this.ctx[e].push(t),this.ctx=t,this.next(this.parseListEnd);throw this.error(new TomlError("Can't redefine an existing key"))}if(this.char!==CHAR_PERIOD)throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"));if(hasKey(this.ctx,e)){if(isInlineList(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline array"));if(isInlineTable(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline table"));if(isList(this.ctx[e]))this.ctx=this.ctx[e][this.ctx[e].length-1];else{if(!isTable(this.ctx[e]))throw this.error(new TomlError("Can't redefine an existing key"));this.ctx=this.ctx[e]}}else this.ctx=this.ctx[e]=Table();return this.next(this.parseListNext)}parseListEnd(e){if(this.char===CHAR_RSQB)return this.next(this.parseWhitespaceToEOL);throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"))}parseValue(){if(this.char===e.END)throw this.error(new TomlError("Key without value"));if(this.char===CHAR_QUOT)return this.next(this.parseDoubleString);if(this.char===CHAR_APOS)return this.next(this.parseSingleString);if(this.char===CHAR_HYPHEN||this.char===CHAR_PLUS)return this.goto(this.parseNumberSign);if(this.char===CHAR_i)return this.next(this.parseInf);if(this.char===CHAR_n)return this.next(this.parseNan);if(isDigit(this.char))return this.goto(this.parseNumberOrDateTime);if(this.char===CHAR_t||this.char===CHAR_f)return this.goto(this.parseBoolean);if(this.char===CHAR_LSQB)return this.call(this.parseInlineList,this.recordValue);if(this.char===CHAR_LCUB)return this.call(this.parseInlineTable,this.recordValue);throw this.error(new TomlError("Unexpected character, expecting string, number, datetime, boolean, inline array or inline table"))}recordValue(e){return this.returnNow(e)}parseInf(){if(this.char===CHAR_n)return this.next(this.parseInf2);throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))}parseInf2(){if(this.char===CHAR_f)return"-"===this.state.buf?this.return(-1/0):this.return(1/0);throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))}parseNan(){if(this.char===CHAR_a)return this.next(this.parseNan2);throw this.error(new TomlError('Unexpected character, expected "nan"'))}parseNan2(){if(this.char===CHAR_n)return this.return(NaN);throw this.error(new TomlError('Unexpected character, expected "nan"'))}parseKeyword(){return this.char===CHAR_QUOT?this.next(this.parseBasicString):this.char===CHAR_APOS?this.next(this.parseLiteralString):this.goto(this.parseBareKey)}parseBareKey(){do{if(this.char===e.END)throw this.error(new TomlError("Key ended without value"));if(!isAlphaNumHyphen(this.char)){if(0===this.state.buf.length)throw this.error(new TomlError("Empty bare keys are not allowed"));return this.returnNow()}}while(this.consume(),this.nextChar())}parseSingleString(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiStringMaybe):this.goto(this.parseLiteralString)}parseLiteralString(){do{if(this.char===CHAR_APOS)return this.return();if(this.atEndOfLine())throw this.error(new TomlError("Unterminated string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}parseLiteralMultiStringMaybe(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiString):this.returnNow()}parseLiteralMultiString(){return this.char===CTRL_M?null:this.char===CTRL_J?this.next(this.parseLiteralMultiStringContent):this.goto(this.parseLiteralMultiStringContent)}parseLiteralMultiStringContent(){do{if(this.char===CHAR_APOS)return this.next(this.parseLiteralMultiEnd);if(this.char===e.END)throw this.error(new TomlError("Unterminated multi-line string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I&&this.char!==CTRL_J&&this.char!==CTRL_M)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}parseLiteralMultiEnd(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiEnd2):(this.state.buf+="'",this.goto(this.parseLiteralMultiStringContent))}parseLiteralMultiEnd2(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiEnd3):(this.state.buf+="''",this.goto(this.parseLiteralMultiStringContent))}parseLiteralMultiEnd3(){return this.char===CHAR_APOS?(this.state.buf+="'",this.next(this.parseLiteralMultiEnd4)):this.returnNow()}parseLiteralMultiEnd4(){return this.char===CHAR_APOS?(this.state.buf+="'",this.return()):this.returnNow()}parseDoubleString(){return this.char===CHAR_QUOT?this.next(this.parseMultiStringMaybe):this.goto(this.parseBasicString)}parseBasicString(){do{if(this.char===CHAR_BSOL)return this.call(this.parseEscape,this.recordEscapeReplacement);if(this.char===CHAR_QUOT)return this.return();if(this.atEndOfLine())throw this.error(new TomlError("Unterminated string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}recordEscapeReplacement(e){return this.state.buf+=e,this.goto(this.parseBasicString)}parseMultiStringMaybe(){return this.char===CHAR_QUOT?this.next(this.parseMultiString):this.returnNow()}parseMultiString(){return this.char===CTRL_M?null:this.char===CTRL_J?this.next(this.parseMultiStringContent):this.goto(this.parseMultiStringContent)}parseMultiStringContent(){do{if(this.char===CHAR_BSOL)return this.call(this.parseMultiEscape,this.recordMultiEscapeReplacement);if(this.char===CHAR_QUOT)return this.next(this.parseMultiEnd);if(this.char===e.END)throw this.error(new TomlError("Unterminated multi-line string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I&&this.char!==CTRL_J&&this.char!==CTRL_M)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}errorControlCharIn(e){let t="\\u00";return this.char<16&&(t+="0"),t+=this.char.toString(16),this.error(new TomlError(`Control characters (codes < 0x1f and 0x7f) are not allowed in ${e}, use ${t} instead`))}recordMultiEscapeReplacement(e){return this.state.buf+=e,this.goto(this.parseMultiStringContent)}parseMultiEnd(){return this.char===CHAR_QUOT?this.next(this.parseMultiEnd2):(this.state.buf+='"',this.goto(this.parseMultiStringContent))}parseMultiEnd2(){return this.char===CHAR_QUOT?this.next(this.parseMultiEnd3):(this.state.buf+='""',this.goto(this.parseMultiStringContent))}parseMultiEnd3(){return this.char===CHAR_QUOT?(this.state.buf+='"',this.next(this.parseMultiEnd4)):this.returnNow()}parseMultiEnd4(){return this.char===CHAR_QUOT?(this.state.buf+='"',this.return()):this.returnNow()}parseMultiEscape(){return this.char===CTRL_M||this.char===CTRL_J?this.next(this.parseMultiTrim):this.char===CHAR_SP||this.char===CTRL_I?this.next(this.parsePreMultiTrim):this.goto(this.parseEscape)}parsePreMultiTrim(){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CTRL_M||this.char===CTRL_J)return this.next(this.parseMultiTrim);throw this.error(new TomlError("Can't escape whitespace"))}parseMultiTrim(){return this.char===CTRL_J||this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M?null:this.returnNow()}parseEscape(){if(this.char in escapes)return this.return(escapes[this.char]);if(this.char===CHAR_u)return this.call(this.parseSmallUnicode,this.parseUnicodeReturn);if(this.char===CHAR_U)return this.call(this.parseLargeUnicode,this.parseUnicodeReturn);throw this.error(new TomlError("Unknown escape character: "+this.char))}parseUnicodeReturn(e){try{var t=parseInt(e,16);if(t>=SURROGATE_FIRST&&t<=SURROGATE_LAST)throw this.error(new TomlError("Invalid unicode, character in range 0xD800 - 0xDFFF is reserved"));return this.returnNow(String.fromCodePoint(t))}catch(e){throw this.error(TomlError.wrap(e))}}parseSmallUnicode(){if(!isHexit(this.char))throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"));if(this.consume(),4<=this.state.buf.length)return this.return()}parseLargeUnicode(){if(!isHexit(this.char))throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"));if(this.consume(),8<=this.state.buf.length)return this.return()}parseNumberSign(){return this.consume(),this.next(this.parseMaybeSignedInfOrNan)}parseMaybeSignedInfOrNan(){return this.char===CHAR_i?this.next(this.parseInf):this.char===CHAR_n?this.next(this.parseNan):this.callNow(this.parseNoUnder,this.parseNumberIntegerStart)}parseNumberIntegerStart(){return this.char===CHAR_0?(this.consume(),this.next(this.parseNumberIntegerExponentOrDecimal)):this.goto(this.parseNumberInteger)}parseNumberIntegerExponentOrDecimal(){return this.char===CHAR_PERIOD?(this.consume(),this.call(this.parseNoUnder,this.parseNumberFloat)):this.char===CHAR_E||this.char===CHAR_e?(this.consume(),this.next(this.parseNumberExponentSign)):this.returnNow(Integer(this.state.buf))}parseNumberInteger(){if(!isDigit(this.char)){if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnder);if(this.char===CHAR_E||this.char===CHAR_e)return this.consume(),this.next(this.parseNumberExponentSign);if(this.char===CHAR_PERIOD)return this.consume(),this.call(this.parseNoUnder,this.parseNumberFloat);var e=Integer(this.state.buf);if(e.isNaN())throw this.error(new TomlError("Invalid number"));return this.returnNow(e)}this.consume()}parseNoUnder(){if(this.char===CHAR_LOWBAR||this.char===CHAR_PERIOD||this.char===CHAR_E||this.char===CHAR_e)throw this.error(new TomlError("Unexpected character, expected digit"));if(this.atEndOfWord())throw this.error(new TomlError("Incomplete number"));return this.returnNow()}parseNoUnderHexOctBinLiteral(){if(this.char===CHAR_LOWBAR||this.char===CHAR_PERIOD)throw this.error(new TomlError("Unexpected character, expected digit"));if(this.atEndOfWord())throw this.error(new TomlError("Incomplete number"));return this.returnNow()}parseNumberFloat(){return this.char===CHAR_LOWBAR?this.call(this.parseNoUnder,this.parseNumberFloat):isDigit(this.char)?void this.consume():this.char===CHAR_E||this.char===CHAR_e?(this.consume(),this.next(this.parseNumberExponentSign)):this.returnNow(Float(this.state.buf))}parseNumberExponentSign(){if(isDigit(this.char))return this.goto(this.parseNumberExponent);if(this.char!==CHAR_HYPHEN&&this.char!==CHAR_PLUS)throw this.error(new TomlError("Unexpected character, expected -, + or digit"));this.consume(),this.call(this.parseNoUnder,this.parseNumberExponent)}parseNumberExponent(){if(!isDigit(this.char))return this.char===CHAR_LOWBAR?this.call(this.parseNoUnder):this.returnNow(Float(this.state.buf));this.consume()}parseNumberOrDateTime(){return this.char===CHAR_0?(this.consume(),this.next(this.parseNumberBaseOrDateTime)):this.goto(this.parseNumberOrDateTimeOnly)}parseNumberOrDateTimeOnly(){return this.char===CHAR_LOWBAR?this.call(this.parseNoUnder,this.parseNumberInteger):isDigit(this.char)?(this.consume(),void(4{for(t=String(t);t.length "+o[t]+"\n")+(n+" ");for(let e=0;er&&!o.warned&&(o.warned=!0,(a=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit")).name="MaxListenersExceededWarning",a.emitter=e,a.type=t,a.count=o.length,n=a,console)&&console.warn&&console.warn(n)),e}function f(e,t,n){e={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},t=function(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}.bind(e);return t.listener=n,e.wrapFn=t}function p(e,t,n){e=e._events;if(void 0===e)return[];e=e[t];if(void 0===e)return[];if("function"==typeof e)return n?[e.listener||e]:[e];if(n){for(var a=e,r=new Array(a.length),o=0;o=u,u=(0,D.default)(((u={})[n+"upload-inner"]=!0,u[n+"hidden"]=x,u)),C=this.props.children;return"card"===r&&(r=(0,D.default)(((r={})[n+"upload-card"]=!0,r[n+"disabled"]=l,r)),C=O.default.createElement("div",{className:r},O.default.createElement(P.default,{size:"large",type:"add",className:n+"upload-add-icon"}),O.default.createElement("div",{tabIndex:"0",role:"button",className:n+"upload-text"},C))),b?"function"==typeof w?(b=(0,D.default)(((r={})[n+"form-preview"]=!0,r[o]=!!o,r)),O.default.createElement("div",{style:i,className:b},w(this.state.value,this.props))):t?O.default.createElement(Y.default,{isPreview:!0,listType:t,style:i,className:o,value:this.state.value,onPreview:m}):null:(n=l?N.func.prevent:p,r=N.obj.pickAttrsWith(this.props,"data-"),O.default.createElement("div",(0,T.default)({className:f,style:i},r),O.default.createElement(j.default,(0,T.default)({},e,{name:M,beforeUpload:d,dragable:a,disabled:l||x,className:u,onSelect:this.onSelect,onDrop:this.onDrop,onProgress:this.onProgress,onSuccess:this.onSuccess,onError:this.onError,ref:this.saveUploaderRef}),C),t||g?O.default.createElement(Y.default,{useDataURL:s,fileNameRender:k,actionRender:S,uploader:this,listType:t,value:this.state.value,closable:c,onRemove:n,progressProps:v,onCancel:h,onPreview:m,extraRender:y,rtl:_,previewOnFileName:E}):null))},i=u=h,u.displayName="Upload",u.propTypes=(0,T.default)({},c.default.propTypes,Y.default.propTypes,{prefix:s.default.string.isRequired,action:s.default.string,value:s.default.array,defaultValue:s.default.array,shape:s.default.oneOf(["card"]),listType:s.default.oneOf(["text","image","card"]),list:s.default.any,name:s.default.string,data:s.default.oneOfType([s.default.object,s.default.func]),formatter:s.default.func,limit:s.default.number,timeout:s.default.number,dragable:s.default.bool,closable:s.default.bool,useDataURL:s.default.bool,disabled:s.default.bool,onSelect:s.default.func,onProgress:s.default.func,onChange:s.default.func,onSuccess:s.default.func,afterSelect:s.default.func,onRemove:s.default.func,onError:s.default.func,beforeUpload:s.default.func,onDrop:s.default.func,className:s.default.string,style:s.default.object,children:s.default.node,autoUpload:s.default.bool,request:s.default.func,progressProps:s.default.object,rtl:s.default.bool,isPreview:s.default.bool,renderPreview:s.default.func,fileKeyName:s.default.string,fileNameRender:s.default.func,actionRender:s.default.func,previewOnFileName:s.default.bool}),u.defaultProps=(0,T.default)({},c.default.defaultProps,{prefix:"next-",limit:1/0,autoUpload:!0,closable:!0,onSelect:n,onProgress:n,onChange:n,onSuccess:n,onRemove:n,onError:n,onDrop:n,beforeUpload:n,afterSelect:n,previewOnFileName:!1}),a=function(){var u=this;this.onSelect=function(e){var t,n,a=u.props,r=a.autoUpload,o=a.afterSelect,i=a.onSelect,a=a.limit,s=u.state.value.length+e.length,l=a-u.state.value.length;l<=0||(t=e=e.map(function(e){e=(0,d.fileToObject)(e);return e.state="selected",e}),n=[],ai||s+a.width>o):t<0||e<0||t+a.height>u.height||e+a.width>u.width}function L(e,t,n,a){var r=a.overlayInfo,a=a.containerInfo,n=n.split("");return 1===n.length&&n.push(""),t<0&&(n=[n[0].replace("t","b"),n[1].replace("b","t")]),e<0&&(n=[n[0].replace("l","r"),n[1].replace("r","l")]),t+r.height>a.height&&(n=[n[0].replace("b","t"),n[1].replace("t","b")]),(n=e+r.width>a.width?[n[0].replace("r","l"),n[1].replace("l","r")]:n).join("")}function O(e,t,n){var a=n.overlayInfo,n=n.containerInfo;return(t=t<0?0:t)+a.height>n.height&&(t=n.height-a.height),{left:e=(e=e<0?0:e)+a.width>n.width?n.width-a.width:e,top:t}}function be(e){var r,o,i,s,t,n,a,l,u,c,d,f=e.target,p=e.overlay,h=e.container,m=e.scrollNode,g=e.placement,y=e.placementOffset,y=void 0===y?0:y,v=e.points,v=void 0===v?["tl","bl"]:v,_=e.offset,_=void 0===_?[0,0]:_,b=e.position,b=void 0===b?"absolute":b,w=e.beforePosition,M=e.autoAdjust,M=void 0===M||M,k=e.autoHideScrollOverflow,k=void 0===k||k,e=e.rtl,S="offsetWidth"in(S=p)&&"offsetHeight"in S?{width:S.offsetWidth,height:S.offsetHeight}:{width:(S=S.getBoundingClientRect()).width,height:S.height},E=S.width,S=S.height;return"fixed"===b?(l={config:{placement:void 0,points:void 0},style:{position:b,left:_[0],top:_[1]}},w?w(l,{overlay:{node:p,width:E,height:S}}):l):(l=f.getBoundingClientRect(),r=l.width,o=l.height,i=l.left,s=l.top,t=(l=x(h)).left,l=l.top,u=h.scrollWidth,c=h.scrollHeight,n=h.scrollTop,a=h.scrollLeft,u=(l=C(g,t={targetInfo:{width:r,height:o,left:i,top:s},containerInfo:{left:t,top:l,width:u,height:c,scrollTop:n,scrollLeft:a},overlayInfo:{width:E,height:S},points:v,placementOffset:y,offset:_,container:h,rtl:e})).left,c=l.top,n=l.points,a=function(e){for(var t=e;t;){var n=he(t,"overflow");if(null!=n&&n.match(/auto|scroll|hidden/))return t;t=t.parentNode}return document.documentElement}(h),M&&g&&T(u,c,a,t)&&(g!==(v=L(u,c,g,t))&&(c=T(_=(y=C(v,t)).left,e=y.top,a,t)&&v!==(l=L(_,e,v,t))?(u=(M=O((h=C(g=l,t)).left,h.top,t)).left,M.top):(g=v,u=_,e)),u=(y=O(u,c,t)).left,c=y.top),d={config:{placement:g,points:n},style:{position:b,left:Math.round(u),top:Math.round(c)}},k&&g&&null!=m&&m.length&&m.forEach(function(e){var e=e.getBoundingClientRect(),t=e.top,n=e.left,a=e.width,e=e.height;d.style.display=s+o=e.length?{done:!0}:{done:!1,value:e[n++]}};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,a=new Array(t);nn.clientHeight&&0 16.8.0")},p.prototype.validate=function(e,t){this.validateCallback(e,t)},p.prototype.reset=function(e){var t=1","Select");t=l(e,t);return e.onInputUpdate&&(t.onSearch=e.onInputUpdate,t.showSearch=!0),t}}),t.default=a.default.config(r.default,{transform:l,exportNames:["focusInput","handleSearchClear"]}),e.exports=t.default},function(M,e,t){"use strict";t.d(e,"a",function(){return a}),t.d(e,"b",function(){return U});var x=t(0),C=t.n(x),c=C.a.createContext(null);function l(){return n}var n=function(e){e()};var r={notify:function(){},get:function(){return[]}};function T(t,n){var o,i=r;function s(){e.onStateChange&&e.onStateChange()}function a(){var e,a,r;o||(o=n?n.addNestedSub(s):t.subscribe(s),e=l(),r=a=null,i={clear:function(){r=a=null},notify:function(){e(function(){for(var e=a;e;)e.callback(),e=e.next})},get:function(){for(var e=[],t=a;t;)e.push(t),t=t.next;return e},subscribe:function(e){var t=!0,n=r={callback:e,next:null,prev:r};return n.prev?n.prev.next=n:a=n,function(){t&&null!==a&&(t=!1,n.next?n.next.prev=n.prev:r=n.prev,n.prev?n.prev.next=n.next:a=n.next)}}})}var e={addNestedSub:function(e){return a(),i.subscribe(e)},notifyNestedSubs:function(){i.notify()},handleChangeWrapper:s,isSubscribed:function(){return Boolean(o)},trySubscribe:a,tryUnsubscribe:function(){o&&(o(),o=void 0,i.clear(),i=r)},getListeners:function(){return i}};return e}var o="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement?x.useLayoutEffect:x.useEffect;var a=function(e){var t=e.store,n=e.context,e=e.children,a=Object(x.useMemo)(function(){var e=T(t);return{store:t,subscription:e}},[t]),r=Object(x.useMemo)(function(){return t.getState()},[t]),n=(o(function(){var e=a.subscription;return e.onStateChange=e.notifyNestedSubs,e.trySubscribe(),r!==t.getState()&&e.notifyNestedSubs(),function(){e.tryUnsubscribe(),e.onStateChange=null}},[a,r]),n||c);return C.a.createElement(n.Provider,{value:a},e)},L=t(41),O=t(56),e=t(107),d=t.n(e),D=t(430),f=["getDisplayName","methodName","renderCountProp","shouldHandleStateChanges","storeKey","withRef","forwardRef","context"],N=["reactReduxForwardedRef"],P=[],j=[null,null];function Y(e,t){e=e[1];return[t.payload,e+1]}function I(e,t,n){o(function(){return e.apply(void 0,t)},n)}function A(e,t,n,a,r,o,i){e.current=a,t.current=r,n.current=!1,o.current&&(o.current=null,i())}function R(e,a,t,r,o,i,s,l,u,c){var d,f;if(e)return d=!1,f=null,t.onStateChange=e=function(){if(!d){var e,t,n=a.getState();try{e=r(n,o.current)}catch(e){f=t=e}t||(f=null),e===i.current?s.current||u():(i.current=e,l.current=e,s.current=!0,c({type:"STORE_UPDATED",payload:{error:t}}))}},t.trySubscribe(),e(),function(){if(d=!0,t.tryUnsubscribe(),t.onStateChange=null,f)throw f}}var H=function(){return[null,0]};function i(k,e){var e=e=void 0===e?{}:e,t=e.getDisplayName,r=void 0===t?function(e){return"ConnectAdvanced("+e+")"}:t,t=e.methodName,o=void 0===t?"connectAdvanced":t,t=e.renderCountProp,i=void 0===t?void 0:t,t=e.shouldHandleStateChanges,S=void 0===t||t,t=e.storeKey,s=void 0===t?"store":t,t=(e.withRef,e.forwardRef),l=void 0!==t&&t,t=e.context,t=void 0===t?c:t,u=Object(O.a)(e,f),E=t;return function(b){var e=b.displayName||b.name||"Component",t=r(e),w=Object(L.a)({},u,{getDisplayName:r,methodName:o,renderCountProp:i,shouldHandleStateChanges:S,storeKey:s,displayName:t,wrappedComponentName:e,WrappedComponent:b}),e=u.pure;var M=e?x.useMemo:function(e){return e()};function n(n){var e=Object(x.useMemo)(function(){var e=n.reactReduxForwardedRef,t=Object(O.a)(n,N);return[n.context,e,t]},[n]),t=e[0],a=e[1],r=e[2],o=Object(x.useMemo)(function(){return t&&t.Consumer&&Object(D.isContextConsumer)(C.a.createElement(t.Consumer,null))?t:E},[t,E]),i=Object(x.useContext)(o),s=Boolean(n.store)&&Boolean(n.store.getState)&&Boolean(n.store.dispatch),l=(Boolean(i)&&Boolean(i.store),(s?n:i).store),u=Object(x.useMemo)(function(){return k(l.dispatch,w)},[l]),e=Object(x.useMemo)(function(){var e,t;return S?(t=(e=T(l,s?null:i.subscription)).notifyNestedSubs.bind(e),[e,t]):j},[l,s,i]),c=e[0],e=e[1],d=Object(x.useMemo)(function(){return s?i:Object(L.a)({},i,{subscription:c})},[s,i,c]),f=Object(x.useReducer)(Y,P,H),p=f[0][0],f=f[1];if(p&&p.error)throw p.error;var h=Object(x.useRef)(),m=Object(x.useRef)(r),g=Object(x.useRef)(),y=Object(x.useRef)(!1),v=M(function(){return g.current&&r===m.current?g.current:u(l.getState(),r)},[l,p,r]),_=(I(A,[m,h,y,r,v,g,e]),I(R,[S,l,c,u,m,h,y,g,e,f],[l,c,u]),Object(x.useMemo)(function(){return C.a.createElement(b,Object(L.a)({},v,{ref:a}))},[a,b,v]));return Object(x.useMemo)(function(){return S?C.a.createElement(o.Provider,{value:d},_):_},[o,_,d])}var a=e?C.a.memo(n):n;return a.WrappedComponent=b,a.displayName=n.displayName=t,l?((e=C.a.forwardRef(function(e,t){return C.a.createElement(a,Object(L.a)({},e,{reactReduxForwardedRef:t}))})).displayName=t,e.WrappedComponent=b,d()(e,b)):d()(a,b)}}function s(e,t){return e===t?0!==e||0!==t||1/e==1/t:e!=e&&t!=t}function m(e,t){if(!s(e,t)){if("object"!=typeof e||null===e||"object"!=typeof t||null===t)return!1;var n=Object.keys(e),a=Object.keys(t);if(n.length!==a.length)return!1;for(var r=0;re?t.splice(e,t.length-e,n):t.push(n),i({action:"PUSH",location:n,index:e,entries:t}))})},replace:function(e,t){var n=D(e,t,s(),u.location);o.confirmTransitionTo(n,"REPLACE",a,function(e){e&&i({action:"REPLACE",location:u.entries[u.index]=n})})},go:l,goBack:function(){l(-1)},goForward:function(){l(1)},canGo:function(e){return 0<=(e=u.index+e)&&ex',"Tag"),"readonly"!==n&&"interactive"!==n||r.log.warning("Warning: [ shape="+n+" ] is deprecated at [ Tag ]"),"secondary"===a&&r.log.warning("Warning: [ type=secondary ] is deprecated at [ Tag ]"),["count","marked","value","onChange"].forEach(function(e){e in t&&r.log.warning("Warning: [ "+e+" ] is deprecated at [ Tag ]")}),("selected"in t||"defaultSelected"in t)&&r.log.warning("Warning: [ selected|defaultSelected ] is deprecated at [ Tag ], use [ checked|defaultChecked ] at [ Tag.Selectable ] instead of it"),"closed"in t&&r.log.warning("Warning: [ closed ] is deprecated at [ Tag ], use [ onClose ] at [ Tag.Closeable ] instead of it"),"onSelect"in t&&e("onSelect","","Tag"),"afterClose"in t&&r.log.warning("Warning: [ afterClose ] is deprecated at [ Tag ], use [ afterClose ] at [ Tag.Closeable ] instead of it"),t}});o.Group=a.default.config(i.default),o.Selectable=a.default.config(s.default),o.Closable=a.default.config(n.default),o.Closeable=o.Closable,t.default=o,e.exports=t.default},function(e,t,n){"use strict";n(72),n(466)},function(e,t){e=e.exports="undefined"!=typeof window&&window.Math==Math?window:"undefined"!=typeof self&&self.Math==Math?self:Function("return this")();"number"==typeof __g&&(__g=e)},function(e,t){e=e.exports={version:"2.6.12"};"number"==typeof __e&&(__e=e)},function(e,t,n){e.exports=!n(113)(function(){return 7!=Object.defineProperty({},"a",{get:function(){return 7}}).a})},function(e,t,n){"use strict";t.__esModule=!0;var a=o(n(358)),r=o(n(545)),n=o(n(546));function o(e){return e&&e.__esModule?e:{default:e}}a.default.Expand=r.default,a.default.OverlayAnimate=n.default,t.default=a.default,e.exports=t.default},function(e,t,n){"use strict";n(43),n(72),n(114),n(115),n(559)},function(e,t,n){"use strict";t.__esModule=!0;var r=p(n(3)),o=p(n(17)),a=p(n(6)),i=p(n(648)),s=p(n(649)),l=p(n(395)),u=p(n(397)),c=p(n(650)),d=p(n(651)),f=p(n(396)),n=p(n(398));function p(e){return e&&e.__esModule?e:{default:e}}i.default.Header=s.default,i.default.Media=u.default,i.default.Divider=c.default,i.default.Content=d.default,i.default.Actions=n.default,i.default.BulletHeader=l.default,i.default.CollaspeContent=f.default,i.default.CollapseContent=f.default,t.default=a.default.config(i.default,{transform:function(e,t){var n,a;return"titlePrefixLine"in e&&(t("titlePrefixLine","showTitleBullet","Card"),a=(n=e).titlePrefixLine,n=(0,o.default)(n,["titlePrefixLine"]),e=(0,r.default)({showTitleBullet:a},n)),"titleBottomLine"in e&&(t("titleBottomLine","showHeadDivider","Card"),n=(a=e).titleBottomLine,a=(0,o.default)(a,["titleBottomLine"]),e=(0,r.default)({showHeadDivider:n},a)),"bodyHeight"in e&&(t("bodyHeight","contentHeight","Card"),a=(n=e).bodyHeight,t=(0,o.default)(n,["bodyHeight"]),e=(0,r.default)({contentHeight:a},t)),e}}),e.exports=t.default},function(e,t,n){"use strict";n.d(t,"b",function(){return s});var a=n(12),r=n(33),o=n(22),i={namespaces:[]},s=function(e){return function(n){return r.a.get("v3/console/core/namespace/list",{params:e}).then(function(e){var t=e.code,e=e.data;n({type:o.b,data:0===t?e:[]})})}};t.a=function(){var e=0this.menuNode.clientHeight&&(this.menuNode.clientHeight+this.menuNode.scrollTop<(e=this.itemNode.offsetTop+this.itemNode.offsetHeight)?this.menuNode.scrollTop=e-this.menuNode.clientHeight:this.itemNode.offsetTope.length)&&(t=e.length);for(var n=0,a=new Array(t);n>16&255),o.push(r>>8&255),o.push(255&r)),r=r<<6|a.indexOf(t.charAt(i));return 0==(e=n%4*6)?(o.push(r>>16&255),o.push(r>>8&255),o.push(255&r)):18==e?(o.push(r>>10&255),o.push(r>>2&255)):12==e&&o.push(r>>4&255),new Uint8Array(o)},predicate:function(e){return"[object Uint8Array]"===Object.prototype.toString.call(e)},represent:function(e){for(var t,n="",a=0,r=e.length,o=g,i=0;i>18&63]+o[a>>12&63])+o[a>>6&63]+o[63&a]),a=(a<<8)+e[i];return 0==(t=r%3)?n=(n=n+o[a>>18&63]+o[a>>12&63])+o[a>>6&63]+o[63&a]:2==t?n=(n=n+o[a>>10&63]+o[a>>4&63])+o[a<<2&63]+o[64]:1==t&&(n=(n=n+o[a>>2&63]+o[a<<4&63])+o[64]+o[64]),n}}),V=Object.prototype.hasOwnProperty,K=Object.prototype.toString;var s=new a("tag:yaml.org,2002:omap",{kind:"sequence",resolve:function(e){if(null!==e)for(var t,n,a,r=[],o=e,i=0,s=o.length;i>10),56320+(l-65536&1023)),e.position++}else x(e,"unknown escape sequence");n=a=e.position}else w(u)?(T(e,n,a,!0),P(e,D(e,!1,t)),n=a=e.position):e.position===e.lineStart&&N(e)?x(e,"unexpected end of the document within a double quoted scalar"):(e.position++,a=e.position)}x(e,"unexpected end of the stream within a double quoted scalar")}}function ge(e,t){var n,a,r=e.tag,o=e.anchor,i=[],s=!1;if(-1!==e.firstTabInLine)return!1;for(null!==e.anchor&&(e.anchorMap[e.anchor]=i),a=e.input.charCodeAt(e.position);0!==a&&(-1!==e.firstTabInLine&&(e.position=e.firstTabInLine,x(e,"tab characters must not be used in indentation")),45===a)&&k(e.input.charCodeAt(e.position+1));)if(s=!0,e.position++,D(e,!0,-1)&&e.lineIndent<=t)i.push(null),a=e.input.charCodeAt(e.position);else if(n=e.line,j(e,t,Z,!1,!0),i.push(e.result),D(e,!0,-1),a=e.input.charCodeAt(e.position),(e.line===n||e.lineIndent>t)&&0!==a)x(e,"bad indentation of a sequence entry");else if(e.lineIndentt?f=1:e.lineIndent===t?f=0:e.lineIndentt?f=1:e.lineIndent===t?f=0:e.lineIndentt)&&(y&&(i=e.line,s=e.lineStart,l=e.position),j(e,t,_,!0,r)&&(y?m=e.result:g=e.result),y||(L(e,f,p,h,m,g,i,s,l),h=m=g=null),D(e,!0,-1),u=e.input.charCodeAt(e.position)),(e.line===o||e.lineIndent>t)&&0!==u)x(e,"bad indentation of a mapping entry");else if(e.lineIndentl&&(l=e.lineIndent),w(d))u++;else{if(e.lineIndent=t){i=!0,f=e.input.charCodeAt(e.position);continue}e.position=o,e.line=s,e.lineStart=l,e.lineIndent=u;break}}i&&(T(e,r,o,!1),P(e,e.line-s),r=o=e.position,i=!1),M(f)||(o=e.position+1),f=e.input.charCodeAt(++e.position)}if(T(e,r,o,!1),e.result)return 1;e.kind=c,e.result=d}}(e,a,v===n)&&(h=!0,null===e.tag)&&(e.tag="?"):(h=!0,null===e.tag&&null===e.anchor||x(e,"alias node should not have any properties")),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):0===f&&(h=s&&ge(e,r))),null===e.tag)null!==e.anchor&&(e.anchorMap[e.anchor]=e.result);else if("?"===e.tag){for(null!==e.result&&"scalar"!==e.kind&&x(e,'unacceptable node kind for ! tag; it should be "scalar", not "'+e.kind+'"'),l=0,u=e.implicitTypes.length;l"),null!==e.result&&d.kind!==e.kind&&x(e,"unacceptable node kind for !<"+e.tag+'> tag; it should be "'+d.kind+'", not "'+e.kind+'"'),d.resolve(e.result,e.tag)?(e.result=d.construct(e.result,e.tag),null!==e.anchor&&(e.anchorMap[e.anchor]=e.result)):x(e,"cannot resolve a node with !<"+e.tag+"> explicit tag")}return null!==e.listener&&e.listener("close",e),null!==e.tag||null!==e.anchor||h}function ye(e,t){t=t||{};var n=new de(e=0!==(e=String(e)).length&&(10!==e.charCodeAt(e.length-1)&&13!==e.charCodeAt(e.length-1)&&(e+="\n"),65279===e.charCodeAt(0))?e.slice(1):e,t),t=e.indexOf("\0");for(-1!==t&&(n.position=t,x(n,"null byte is not allowed in input")),n.input+="\0";32===n.input.charCodeAt(n.position);)n.lineIndent+=1,n.position+=1;for(;n.positiondocument.F=Object<\/script>"),e.close(),u=e.F;t--;)delete u[l][i[t]];return u()};e.exports=Object.create||function(e,t){var n;return null!==e?(a[l]=r(e),n=new a,a[l]=null,n[s]=e):n=u(),void 0===t?n:o(n,t)}},function(e,t,n){var a=n(89).f,r=n(90),o=n(100)("toStringTag");e.exports=function(e,t,n){e&&!r(e=n?e:e.prototype,o)&&a(e,o,{configurable:!0,value:t})}},function(e,t,n){t.f=n(100)},function(e,t,n){var a=n(80),r=n(81),o=n(128),i=n(162),s=n(89).f;e.exports=function(e){var t=r.Symbol||(r.Symbol=!o&&a.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:i.f(e)})}},function(e,t,n){"use strict";t.__esModule=!0;var a=c(n(219)),r=c(n(520)),o=c(n(521)),i=c(n(522)),s=c(n(523)),l=c(n(524)),u=c(n(525));function c(e){return e&&e.__esModule?e:{default:e}}n(526),a.default.extend(l.default),a.default.extend(s.default),a.default.extend(r.default),a.default.extend(o.default),a.default.extend(i.default),a.default.extend(u.default),a.default.locale("zh-cn");n=a.default;n.isSelf=a.default.isDayjs,a.default.localeData(),t.default=n,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var v=d(n(3)),o=d(n(4)),i=d(n(7)),a=d(n(8)),r=n(0),_=d(r),s=d(n(5)),l=n(31),b=d(n(19)),u=d(n(44)),w=d(n(26)),M=d(n(83)),c=d(n(6)),k=n(11);function d(e){return e&&e.__esModule?e:{default:e}}function f(){}p=r.Component,(0,a.default)(S,p),S.getDerivedStateFromProps=function(e){return"visible"in e?{visible:e.visible}:{}},S.prototype.render=function(){var e,t=this.props,n=t.prefix,a=(t.pure,t.className),r=t.style,o=t.type,i=t.shape,s=t.size,l=t.title,u=t.children,c=(t.defaultVisible,t.visible,t.iconType),d=t.closeable,f=(t.onClose,t.afterClose),p=t.animation,h=t.rtl,t=t.locale,m=(0,v.default)({},k.obj.pickOthers(Object.keys(S.propTypes),this.props)),g=this.state.visible,y=n+"message",o=(0,b.default)(((e={})[y]=!0,e[n+"message-"+o]=o,e[""+n+i]=i,e[""+n+s]=s,e[n+"title-content"]=!!l,e[n+"only-content"]=!l&&!!u,e[a]=a,e)),i=g?_.default.createElement("div",(0,v.default)({role:"alert",style:r},m,{className:o,dir:h?"rtl":void 0}),d?_.default.createElement("a",{role:"button","aria-label":t.closeAriaLabel,className:y+"-close",onClick:this.onClose},_.default.createElement(w.default,{type:"close"})):null,!1!==c?_.default.createElement(w.default,{className:y+"-symbol "+(!c&&y+"-symbol-icon"),type:c}):null,l?_.default.createElement("div",{className:y+"-title"},l):null,u?_.default.createElement("div",{className:y+"-content"},u):null):null;return p?_.default.createElement(M.default.Expand,{animationAppear:!1,afterLeave:f},i):i},r=n=S,n.propTypes={prefix:s.default.string,pure:s.default.bool,className:s.default.string,style:s.default.object,type:s.default.oneOf(["success","warning","error","notice","help","loading"]),shape:s.default.oneOf(["inline","addon","toast"]),size:s.default.oneOf(["medium","large"]),title:s.default.node,children:s.default.node,defaultVisible:s.default.bool,visible:s.default.bool,iconType:s.default.oneOfType([s.default.string,s.default.bool]),closeable:s.default.bool,onClose:s.default.func,afterClose:s.default.func,animation:s.default.bool,locale:s.default.object,rtl:s.default.bool},n.defaultProps={prefix:"next-",pure:!1,type:"success",shape:"inline",size:"medium",defaultVisible:!0,closeable:!1,onClose:f,afterClose:f,animation:!0,locale:u.default.Message};var p,a=r;function S(){var e,t;(0,o.default)(this,S);for(var n=arguments.length,a=Array(n),r=0;r=n.length?(l=!!(d=h(o,u)))&&"get"in d&&!("originalValue"in d.get)?d.get:o[u]:(l=_(o,u),o[u]),l&&!i&&(g[c]=o)}}return o}},function(e,t,n){"use strict";n=n(625);e.exports=Function.prototype.bind||n},function(e,t,n){"use strict";var a=String.prototype.replace,r=/%20/g,o="RFC1738",i="RFC3986";e.exports={default:i,formatters:{RFC1738:function(e){return a.call(e,r,"+")},RFC3986:function(e){return String(e)}},RFC1738:o,RFC3986:i}},function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var c=l(n(3)),a=l(n(4)),r=l(n(7)),o=l(n(8)),d=n(0),f=l(d),i=l(n(5)),p=l(n(19)),h=l(n(26)),s=n(11),m=l(n(104));function l(e){return e&&e.__esModule?e:{default:e}}var u,g=s.func.bindCtx,y=s.obj.pickOthers,i=(u=d.Component,(0,o.default)(v,u),v.prototype.getSelected=function(){var e=this.props,t=e._key,n=e.root,e=e.selected,a=n.props.selectMode,n=n.state.selectedKeys;return e||!!a&&-1e.length&&e.every(function(e,t){return e===n[t]})},t.isAvailablePos=function(e,t,n){var n=n[t],a=n.type,n=n.disabled;return r(e,t)&&("item"===a&&!n||"submenu"===a)});t.getFirstAvaliablelChildKey=function(t,n){var e=Object.keys(n).find(function(e){return a(t+"-0",e,n)});return e?n[e].key:null},t.getChildSelected=function(e){var t,n=e.selectMode,a=e.selectedKeys,r=e._k2n,e=e._key;return!!r&&(t=(r[e]&&r[e].pos)+"-",!!n)&&a.some(function(e){return r[e]&&0===r[e].pos.indexOf(t)})}},function(e,t,n){"use strict";n(43),n(72),n(654)},function(e,t,n){"use strict";t.__esModule=!0;var g=d(n(17)),y=d(n(3)),a=d(n(4)),r=d(n(7)),o=d(n(8)),i=n(0),v=d(i),s=d(n(5)),_=d(n(19)),l=d(n(83)),u=d(n(26)),b=n(11),c=d(n(44)),n=d(n(6));function d(e){return e&&e.__esModule?e:{default:e}}var f,p=b.func.noop,h=b.func.bindCtx,m=/blue|green|orange|red|turquoise|yellow/,s=(f=i.Component,(0,o.default)(w,f),w.prototype.componentWillUnmount=function(){this.__destroyed=!0},w.prototype.handleClose=function(e){var t=this,n=this.props,a=n.animation,n=n.onClose,r=b.support.animation&&a;!1===n(e,this.tagNode)||this.__destroyed||this.setState({visible:!1},function(){r||t.props.afterClose(t.tagNode)})},w.prototype.handleBodyClick=function(e){var t=this.props,n=t.closable,a=t.closeArea,t=t.onClick,r=e.currentTarget;if(r&&(r===e.target||r.contains(e.target))&&(n&&"tag"===a&&this.handleClose("tag"),"function"==typeof t))return t(e)},w.prototype.handleTailClick=function(e){e&&e.preventDefault(),e&&e.stopPropagation(),this.handleClose("tail")},w.prototype.handleAnimationInit=function(e){this.props.afterAppear(e)},w.prototype.handleAnimationEnd=function(e){this.props.afterClose(e)},w.prototype.renderAnimatedTag=function(e,t){return v.default.createElement(l.default,{animation:t,afterAppear:this.handleAnimationInit,afterLeave:this.handleAnimationEnd},e)},w.prototype.renderTailNode=function(){var e=this.props,t=e.prefix,n=e.closable,e=e.locale;return n?v.default.createElement("span",{className:t+"tag-close-btn",onClick:this.handleTailClick,role:"button","aria-label":e.delete},v.default.createElement(u.default,{type:"close"})):null},w.prototype.isPresetColor=function(){var e=this.props.color;return!!e&&m.test(e)},w.prototype.getTagStyle=function(){var e=this.props,t=e.color,t=void 0===t?"":t,e=e.style,n=this.isPresetColor();return(0,y.default)({},t&&!n?{backgroundColor:t,borderColor:t,color:"#fff"}:null,e)},w.prototype.render=function(){var t=this,e=this.props,n=e.prefix,a=e.type,r=e.size,o=e.color,i=e._shape,s=e.closable,l=e.closeArea,u=e.className,c=e.children,d=e.animation,f=e.disabled,e=e.rtl,p=this.state.visible,h=this.isPresetColor(),m=b.obj.pickOthers(w.propTypes,this.props),m=(m.style,(0,g.default)(m,["style"])),r=(0,_.default)([n+"tag",n+"tag-"+(s?"closable":i),n+"tag-"+r],((i={})[n+"tag-level-"+a]=!o,i[n+"tag-closable"]=s,i[n+"tag-body-pointer"]=s&&"tag"===l,i[n+"tag-"+o]=o&&h&&"primary"===a,i[n+"tag-"+o+"-inverse"]=o&&h&&"normal"===a,i),u),s=this.renderTailNode(),l=p?v.default.createElement("div",(0,y.default)({className:r,onClick:this.handleBodyClick,onKeyDown:this.onKeyDown,tabIndex:f?"":"0",role:"button","aria-disabled":f,disabled:f,dir:e?"rtl":void 0,ref:function(e){return t.tagNode=e},style:this.getTagStyle()},m),v.default.createElement("span",{className:n+"tag-body"},c),s):null;return d&&b.support.animation?this.renderAnimatedTag(l,n+"tag-zoom"):l},o=i=w,i.propTypes={prefix:s.default.string,type:s.default.oneOf(["normal","primary"]),size:s.default.oneOf(["small","medium","large"]),color:s.default.string,animation:s.default.bool,closeArea:s.default.oneOf(["tag","tail"]),closable:s.default.bool,onClose:s.default.func,afterClose:s.default.func,afterAppear:s.default.func,className:s.default.any,children:s.default.node,onClick:s.default.func,_shape:s.default.oneOf(["default","closable","checkable"]),disabled:s.default.bool,rtl:s.default.bool,locale:s.default.object},i.defaultProps={prefix:"next-",type:"normal",size:"medium",closeArea:"tail",animation:!1,onClose:p,afterClose:p,afterAppear:p,onClick:p,_shape:"default",disabled:!1,rtl:!1,locale:c.default.Tag},o);function w(e){(0,a.default)(this,w);var o=(0,r.default)(this,f.call(this,e));return o.onKeyDown=function(e){var t=o.props,n=t.closable,a=t.closeArea,r=t.onClick,t=t.disabled;e.keyCode!==b.KEYCODE.SPACE||t||(e.preventDefault(),e.stopPropagation(),n?o.handleClose(a):"function"==typeof r&&r(e))},o.state={visible:!0},h(o,["handleBodyClick","handleTailClick","handleAnimationInit","handleAnimationEnd","renderTailNode"]),o}s.displayName="Tag",t.default=n.default.config(s),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var f=r(n(17)),p=r(n(42)),h=r(n(3)),a=(t.isSingle=function(e){return!e||"single"===e},t.isNull=s,t.escapeForReg=o,t.filter=function(e,t){e=o(""+e),e=new RegExp("("+e+")","ig");return e.test(""+t.value)||e.test(""+t.label)},t.loopMap=i,t.parseDataSourceFromChildren=function i(e){var s=1{var t=new TomlError(e.message);return t.code=e.code,t.wrapped=e,t},module.exports.TomlError=TomlError;const createDateTime=__webpack_require__(697),createDateTimeFloat=__webpack_require__(698),createDate=__webpack_require__(699),createTime=__webpack_require__(700),CTRL_I=9,CTRL_J=10,CTRL_M=13,CTRL_CHAR_BOUNDARY=31,CHAR_SP=32,CHAR_QUOT=34,CHAR_NUM=35,CHAR_APOS=39,CHAR_PLUS=43,CHAR_COMMA=44,CHAR_HYPHEN=45,CHAR_PERIOD=46,CHAR_0=48,CHAR_1=49,CHAR_7=55,CHAR_9=57,CHAR_COLON=58,CHAR_EQUALS=61,CHAR_A=65,CHAR_E=69,CHAR_F=70,CHAR_T=84,CHAR_U=85,CHAR_Z=90,CHAR_LOWBAR=95,CHAR_a=97,CHAR_b=98,CHAR_e=101,CHAR_f=102,CHAR_i=105,CHAR_l=108,CHAR_n=110,CHAR_o=111,CHAR_r=114,CHAR_s=115,CHAR_t=116,CHAR_u=117,CHAR_x=120,CHAR_z=122,CHAR_LCUB=123,CHAR_RCUB=125,CHAR_LSQB=91,CHAR_BSOL=92,CHAR_RSQB=93,CHAR_DEL=127,SURROGATE_FIRST=55296,SURROGATE_LAST=57343,escapes={[CHAR_b]:"\b",[CHAR_t]:"\t",[CHAR_n]:"\n",[CHAR_f]:"\f",[CHAR_r]:"\r",[CHAR_QUOT]:'"',[CHAR_BSOL]:"\\"};function isDigit(e){return e>=CHAR_0&&e<=CHAR_9}function isHexit(e){return e>=CHAR_A&&e<=CHAR_F||e>=CHAR_a&&e<=CHAR_f||e>=CHAR_0&&e<=CHAR_9}function isBit(e){return e===CHAR_1||e===CHAR_0}function isOctit(e){return e>=CHAR_0&&e<=CHAR_7}function isAlphaNumQuoteHyphen(e){return e>=CHAR_A&&e<=CHAR_Z||e>=CHAR_a&&e<=CHAR_z||e>=CHAR_0&&e<=CHAR_9||e===CHAR_APOS||e===CHAR_QUOT||e===CHAR_LOWBAR||e===CHAR_HYPHEN}function isAlphaNumHyphen(e){return e>=CHAR_A&&e<=CHAR_Z||e>=CHAR_a&&e<=CHAR_z||e>=CHAR_0&&e<=CHAR_9||e===CHAR_LOWBAR||e===CHAR_HYPHEN}const _type=Symbol("type"),_declared=Symbol("declared"),hasOwnProperty=Object.prototype.hasOwnProperty,defineProperty=Object.defineProperty,descriptor={configurable:!0,enumerable:!0,writable:!0,value:void 0};function hasKey(e,t){if(hasOwnProperty.call(e,t))return 1;"__proto__"===t&&defineProperty(e,"__proto__",descriptor)}const INLINE_TABLE=Symbol("inline-table");function InlineTable(){return Object.defineProperties({},{[_type]:{value:INLINE_TABLE}})}function isInlineTable(e){return null!==e&&"object"==typeof e&&e[_type]===INLINE_TABLE}const TABLE=Symbol("table");function Table(){return Object.defineProperties({},{[_type]:{value:TABLE},[_declared]:{value:!1,writable:!0}})}function isTable(e){return null!==e&&"object"==typeof e&&e[_type]===TABLE}const _contentType=Symbol("content-type"),INLINE_LIST=Symbol("inline-list");function InlineList(e){return Object.defineProperties([],{[_type]:{value:INLINE_LIST},[_contentType]:{value:e}})}function isInlineList(e){return null!==e&&"object"==typeof e&&e[_type]===INLINE_LIST}const LIST=Symbol("list");function List(){return Object.defineProperties([],{[_type]:{value:LIST}})}function isList(e){return null!==e&&"object"==typeof e&&e[_type]===LIST}let _custom;try{const utilInspect=eval("require('util').inspect");_custom=utilInspect.custom}catch(_){}const _inspect=_custom||"inspect";class BoxedBigInt{constructor(e){try{this.value=global.BigInt.asIntN(64,e)}catch(e){this.value=null}Object.defineProperty(this,_type,{value:INTEGER})}isNaN(){return null===this.value}toString(){return String(this.value)}[_inspect](){return`[BigInt: ${this.toString()}]}`}valueOf(){return this.value}}const INTEGER=Symbol("integer");function Integer(e){let t=Number(e);return Object.is(t,-0)&&(t=0),global.BigInt&&!Number.isSafeInteger(t)?new BoxedBigInt(e):Object.defineProperties(new Number(t),{isNaN:{value:function(){return isNaN(this)}},[_type]:{value:INTEGER},[_inspect]:{value:()=>`[Integer: ${e}]`}})}function isInteger(e){return null!==e&&"object"==typeof e&&e[_type]===INTEGER}const FLOAT=Symbol("float");function Float(e){return Object.defineProperties(new Number(e),{[_type]:{value:FLOAT},[_inspect]:{value:()=>`[Float: ${e}]`}})}function isFloat(e){return null!==e&&"object"==typeof e&&e[_type]===FLOAT}function tomlType(e){var t=typeof e;if("object"==t){if(null===e)return"null";if(e instanceof Date)return"datetime";if(_type in e)switch(e[_type]){case INLINE_TABLE:return"inline-table";case INLINE_LIST:return"inline-list";case TABLE:return"table";case LIST:return"list";case FLOAT:return"float";case INTEGER:return"integer"}}return t}function makeParserClass(e){class t extends e{constructor(){super(),this.ctx=this.obj=Table()}atEndOfWord(){return this.char===CHAR_NUM||this.char===CTRL_I||this.char===CHAR_SP||this.atEndOfLine()}atEndOfLine(){return this.char===e.END||this.char===CTRL_J||this.char===CTRL_M}parseStart(){if(this.char===e.END)return null;if(this.char===CHAR_LSQB)return this.call(this.parseTableOrList);if(this.char===CHAR_NUM)return this.call(this.parseComment);if(this.char===CTRL_J||this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M)return null;if(isAlphaNumQuoteHyphen(this.char))return this.callNow(this.parseAssignStatement);throw this.error(new TomlError(`Unknown character "${this.char}"`))}parseWhitespaceToEOL(){if(this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M)return null;if(this.char===CHAR_NUM)return this.goto(this.parseComment);if(this.char===e.END||this.char===CTRL_J)return this.return();throw this.error(new TomlError("Unexpected character, expected only whitespace or comments till end of line"))}parseAssignStatement(){return this.callNow(this.parseAssign,this.recordAssignStatement)}recordAssignStatement(e){let t=this.ctx;var n,a=e.key.pop();for(n of e.key){if(hasKey(t,n)&&!isTable(t[n]))throw this.error(new TomlError("Can't redefine existing key"));t=t[n]=t[n]||Table()}if(hasKey(t,a))throw this.error(new TomlError("Can't redefine existing key"));return t[_declared]=!0,isInteger(e.value)||isFloat(e.value)?t[a]=e.value.valueOf():t[a]=e.value,this.goto(this.parseWhitespaceToEOL)}parseAssign(){return this.callNow(this.parseKeyword,this.recordAssignKeyword)}recordAssignKeyword(e){return this.state.resultTable?this.state.resultTable.push(e):this.state.resultTable=[e],this.goto(this.parseAssignKeywordPreDot)}parseAssignKeywordPreDot(){return this.char===CHAR_PERIOD?this.next(this.parseAssignKeywordPostDot):this.char!==CHAR_SP&&this.char!==CTRL_I?this.goto(this.parseAssignEqual):void 0}parseAssignKeywordPostDot(){if(this.char!==CHAR_SP&&this.char!==CTRL_I)return this.callNow(this.parseKeyword,this.recordAssignKeyword)}parseAssignEqual(){if(this.char===CHAR_EQUALS)return this.next(this.parseAssignPreValue);throw this.error(new TomlError('Invalid character, expected "="'))}parseAssignPreValue(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseValue,this.recordAssignValue)}recordAssignValue(e){return this.returnNow({key:this.state.resultTable,value:e})}parseComment(){do{if(this.char===e.END||this.char===CTRL_J)return this.return();if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("comments")}while(this.nextChar())}parseTableOrList(){if(this.char!==CHAR_LSQB)return this.goto(this.parseTable);this.next(this.parseList)}parseTable(){return this.ctx=this.obj,this.goto(this.parseTableNext)}parseTableNext(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseKeyword,this.parseTableMore)}parseTableMore(e){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CHAR_RSQB){if(!hasKey(this.ctx,e)||isTable(this.ctx[e])&&!this.ctx[e][_declared])return this.ctx=this.ctx[e]=this.ctx[e]||Table(),this.ctx[_declared]=!0,this.next(this.parseWhitespaceToEOL);throw this.error(new TomlError("Can't redefine existing key"))}if(this.char!==CHAR_PERIOD)throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"));if(hasKey(this.ctx,e))if(isTable(this.ctx[e]))this.ctx=this.ctx[e];else{if(!isList(this.ctx[e]))throw this.error(new TomlError("Can't redefine existing key"));this.ctx=this.ctx[e][this.ctx[e].length-1]}else this.ctx=this.ctx[e]=Table();return this.next(this.parseTableNext)}parseList(){return this.ctx=this.obj,this.goto(this.parseListNext)}parseListNext(){return this.char===CHAR_SP||this.char===CTRL_I?null:this.callNow(this.parseKeyword,this.parseListMore)}parseListMore(e){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CHAR_RSQB){if(hasKey(this.ctx,e)||(this.ctx[e]=List()),isInlineList(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline array"));var t;if(isList(this.ctx[e]))return t=Table(),this.ctx[e].push(t),this.ctx=t,this.next(this.parseListEnd);throw this.error(new TomlError("Can't redefine an existing key"))}if(this.char!==CHAR_PERIOD)throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"));if(hasKey(this.ctx,e)){if(isInlineList(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline array"));if(isInlineTable(this.ctx[e]))throw this.error(new TomlError("Can't extend an inline table"));if(isList(this.ctx[e]))this.ctx=this.ctx[e][this.ctx[e].length-1];else{if(!isTable(this.ctx[e]))throw this.error(new TomlError("Can't redefine an existing key"));this.ctx=this.ctx[e]}}else this.ctx=this.ctx[e]=Table();return this.next(this.parseListNext)}parseListEnd(e){if(this.char===CHAR_RSQB)return this.next(this.parseWhitespaceToEOL);throw this.error(new TomlError("Unexpected character, expected whitespace, . or ]"))}parseValue(){if(this.char===e.END)throw this.error(new TomlError("Key without value"));if(this.char===CHAR_QUOT)return this.next(this.parseDoubleString);if(this.char===CHAR_APOS)return this.next(this.parseSingleString);if(this.char===CHAR_HYPHEN||this.char===CHAR_PLUS)return this.goto(this.parseNumberSign);if(this.char===CHAR_i)return this.next(this.parseInf);if(this.char===CHAR_n)return this.next(this.parseNan);if(isDigit(this.char))return this.goto(this.parseNumberOrDateTime);if(this.char===CHAR_t||this.char===CHAR_f)return this.goto(this.parseBoolean);if(this.char===CHAR_LSQB)return this.call(this.parseInlineList,this.recordValue);if(this.char===CHAR_LCUB)return this.call(this.parseInlineTable,this.recordValue);throw this.error(new TomlError("Unexpected character, expecting string, number, datetime, boolean, inline array or inline table"))}recordValue(e){return this.returnNow(e)}parseInf(){if(this.char===CHAR_n)return this.next(this.parseInf2);throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))}parseInf2(){if(this.char===CHAR_f)return"-"===this.state.buf?this.return(-1/0):this.return(1/0);throw this.error(new TomlError('Unexpected character, expected "inf", "+inf" or "-inf"'))}parseNan(){if(this.char===CHAR_a)return this.next(this.parseNan2);throw this.error(new TomlError('Unexpected character, expected "nan"'))}parseNan2(){if(this.char===CHAR_n)return this.return(NaN);throw this.error(new TomlError('Unexpected character, expected "nan"'))}parseKeyword(){return this.char===CHAR_QUOT?this.next(this.parseBasicString):this.char===CHAR_APOS?this.next(this.parseLiteralString):this.goto(this.parseBareKey)}parseBareKey(){do{if(this.char===e.END)throw this.error(new TomlError("Key ended without value"));if(!isAlphaNumHyphen(this.char)){if(0===this.state.buf.length)throw this.error(new TomlError("Empty bare keys are not allowed"));return this.returnNow()}}while(this.consume(),this.nextChar())}parseSingleString(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiStringMaybe):this.goto(this.parseLiteralString)}parseLiteralString(){do{if(this.char===CHAR_APOS)return this.return();if(this.atEndOfLine())throw this.error(new TomlError("Unterminated string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}parseLiteralMultiStringMaybe(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiString):this.returnNow()}parseLiteralMultiString(){return this.char===CTRL_M?null:this.char===CTRL_J?this.next(this.parseLiteralMultiStringContent):this.goto(this.parseLiteralMultiStringContent)}parseLiteralMultiStringContent(){do{if(this.char===CHAR_APOS)return this.next(this.parseLiteralMultiEnd);if(this.char===e.END)throw this.error(new TomlError("Unterminated multi-line string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I&&this.char!==CTRL_J&&this.char!==CTRL_M)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}parseLiteralMultiEnd(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiEnd2):(this.state.buf+="'",this.goto(this.parseLiteralMultiStringContent))}parseLiteralMultiEnd2(){return this.char===CHAR_APOS?this.next(this.parseLiteralMultiEnd3):(this.state.buf+="''",this.goto(this.parseLiteralMultiStringContent))}parseLiteralMultiEnd3(){return this.char===CHAR_APOS?(this.state.buf+="'",this.next(this.parseLiteralMultiEnd4)):this.returnNow()}parseLiteralMultiEnd4(){return this.char===CHAR_APOS?(this.state.buf+="'",this.return()):this.returnNow()}parseDoubleString(){return this.char===CHAR_QUOT?this.next(this.parseMultiStringMaybe):this.goto(this.parseBasicString)}parseBasicString(){do{if(this.char===CHAR_BSOL)return this.call(this.parseEscape,this.recordEscapeReplacement);if(this.char===CHAR_QUOT)return this.return();if(this.atEndOfLine())throw this.error(new TomlError("Unterminated string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}recordEscapeReplacement(e){return this.state.buf+=e,this.goto(this.parseBasicString)}parseMultiStringMaybe(){return this.char===CHAR_QUOT?this.next(this.parseMultiString):this.returnNow()}parseMultiString(){return this.char===CTRL_M?null:this.char===CTRL_J?this.next(this.parseMultiStringContent):this.goto(this.parseMultiStringContent)}parseMultiStringContent(){do{if(this.char===CHAR_BSOL)return this.call(this.parseMultiEscape,this.recordMultiEscapeReplacement);if(this.char===CHAR_QUOT)return this.next(this.parseMultiEnd);if(this.char===e.END)throw this.error(new TomlError("Unterminated multi-line string"));if(this.char===CHAR_DEL||this.char<=CTRL_CHAR_BOUNDARY&&this.char!==CTRL_I&&this.char!==CTRL_J&&this.char!==CTRL_M)throw this.errorControlCharIn("strings")}while(this.consume(),this.nextChar())}errorControlCharIn(e){let t="\\u00";return this.char<16&&(t+="0"),t+=this.char.toString(16),this.error(new TomlError(`Control characters (codes < 0x1f and 0x7f) are not allowed in ${e}, use ${t} instead`))}recordMultiEscapeReplacement(e){return this.state.buf+=e,this.goto(this.parseMultiStringContent)}parseMultiEnd(){return this.char===CHAR_QUOT?this.next(this.parseMultiEnd2):(this.state.buf+='"',this.goto(this.parseMultiStringContent))}parseMultiEnd2(){return this.char===CHAR_QUOT?this.next(this.parseMultiEnd3):(this.state.buf+='""',this.goto(this.parseMultiStringContent))}parseMultiEnd3(){return this.char===CHAR_QUOT?(this.state.buf+='"',this.next(this.parseMultiEnd4)):this.returnNow()}parseMultiEnd4(){return this.char===CHAR_QUOT?(this.state.buf+='"',this.return()):this.returnNow()}parseMultiEscape(){return this.char===CTRL_M||this.char===CTRL_J?this.next(this.parseMultiTrim):this.char===CHAR_SP||this.char===CTRL_I?this.next(this.parsePreMultiTrim):this.goto(this.parseEscape)}parsePreMultiTrim(){if(this.char===CHAR_SP||this.char===CTRL_I)return null;if(this.char===CTRL_M||this.char===CTRL_J)return this.next(this.parseMultiTrim);throw this.error(new TomlError("Can't escape whitespace"))}parseMultiTrim(){return this.char===CTRL_J||this.char===CHAR_SP||this.char===CTRL_I||this.char===CTRL_M?null:this.returnNow()}parseEscape(){if(this.char in escapes)return this.return(escapes[this.char]);if(this.char===CHAR_u)return this.call(this.parseSmallUnicode,this.parseUnicodeReturn);if(this.char===CHAR_U)return this.call(this.parseLargeUnicode,this.parseUnicodeReturn);throw this.error(new TomlError("Unknown escape character: "+this.char))}parseUnicodeReturn(e){try{var t=parseInt(e,16);if(t>=SURROGATE_FIRST&&t<=SURROGATE_LAST)throw this.error(new TomlError("Invalid unicode, character in range 0xD800 - 0xDFFF is reserved"));return this.returnNow(String.fromCodePoint(t))}catch(e){throw this.error(TomlError.wrap(e))}}parseSmallUnicode(){if(!isHexit(this.char))throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"));if(this.consume(),4<=this.state.buf.length)return this.return()}parseLargeUnicode(){if(!isHexit(this.char))throw this.error(new TomlError("Invalid character in unicode sequence, expected hex"));if(this.consume(),8<=this.state.buf.length)return this.return()}parseNumberSign(){return this.consume(),this.next(this.parseMaybeSignedInfOrNan)}parseMaybeSignedInfOrNan(){return this.char===CHAR_i?this.next(this.parseInf):this.char===CHAR_n?this.next(this.parseNan):this.callNow(this.parseNoUnder,this.parseNumberIntegerStart)}parseNumberIntegerStart(){return this.char===CHAR_0?(this.consume(),this.next(this.parseNumberIntegerExponentOrDecimal)):this.goto(this.parseNumberInteger)}parseNumberIntegerExponentOrDecimal(){return this.char===CHAR_PERIOD?(this.consume(),this.call(this.parseNoUnder,this.parseNumberFloat)):this.char===CHAR_E||this.char===CHAR_e?(this.consume(),this.next(this.parseNumberExponentSign)):this.returnNow(Integer(this.state.buf))}parseNumberInteger(){if(!isDigit(this.char)){if(this.char===CHAR_LOWBAR)return this.call(this.parseNoUnder);if(this.char===CHAR_E||this.char===CHAR_e)return this.consume(),this.next(this.parseNumberExponentSign);if(this.char===CHAR_PERIOD)return this.consume(),this.call(this.parseNoUnder,this.parseNumberFloat);var e=Integer(this.state.buf);if(e.isNaN())throw this.error(new TomlError("Invalid number"));return this.returnNow(e)}this.consume()}parseNoUnder(){if(this.char===CHAR_LOWBAR||this.char===CHAR_PERIOD||this.char===CHAR_E||this.char===CHAR_e)throw this.error(new TomlError("Unexpected character, expected digit"));if(this.atEndOfWord())throw this.error(new TomlError("Incomplete number"));return this.returnNow()}parseNoUnderHexOctBinLiteral(){if(this.char===CHAR_LOWBAR||this.char===CHAR_PERIOD)throw this.error(new TomlError("Unexpected character, expected digit"));if(this.atEndOfWord())throw this.error(new TomlError("Incomplete number"));return this.returnNow()}parseNumberFloat(){return this.char===CHAR_LOWBAR?this.call(this.parseNoUnder,this.parseNumberFloat):isDigit(this.char)?void this.consume():this.char===CHAR_E||this.char===CHAR_e?(this.consume(),this.next(this.parseNumberExponentSign)):this.returnNow(Float(this.state.buf))}parseNumberExponentSign(){if(isDigit(this.char))return this.goto(this.parseNumberExponent);if(this.char!==CHAR_HYPHEN&&this.char!==CHAR_PLUS)throw this.error(new TomlError("Unexpected character, expected -, + or digit"));this.consume(),this.call(this.parseNoUnder,this.parseNumberExponent)}parseNumberExponent(){if(!isDigit(this.char))return this.char===CHAR_LOWBAR?this.call(this.parseNoUnder):this.returnNow(Float(this.state.buf));this.consume()}parseNumberOrDateTime(){return this.char===CHAR_0?(this.consume(),this.next(this.parseNumberBaseOrDateTime)):this.goto(this.parseNumberOrDateTimeOnly)}parseNumberOrDateTimeOnly(){return this.char===CHAR_LOWBAR?this.call(this.parseNoUnder,this.parseNumberInteger):isDigit(this.char)?(this.consume(),void(4{for(t=String(t);t.length "+o[t]+"\n")+(n+" ");for(let e=0;er&&!o.warned&&(o.warned=!0,(a=new Error("Possible EventEmitter memory leak detected. "+o.length+" "+String(t)+" listeners added. Use emitter.setMaxListeners() to increase limit")).name="MaxListenersExceededWarning",a.emitter=e,a.type=t,a.count=o.length,n=a,console)&&console.warn&&console.warn(n)),e}function f(e,t,n){e={fired:!1,wrapFn:void 0,target:e,type:t,listener:n},t=function(){if(!this.fired)return this.target.removeListener(this.type,this.wrapFn),this.fired=!0,0===arguments.length?this.listener.call(this.target):this.listener.apply(this.target,arguments)}.bind(e);return t.listener=n,e.wrapFn=t}function p(e,t,n){e=e._events;if(void 0===e)return[];e=e[t];if(void 0===e)return[];if("function"==typeof e)return n?[e.listener||e]:[e];if(n){for(var a=e,r=new Array(a.length),o=0;o=u,u=(0,D.default)(((u={})[n+"upload-inner"]=!0,u[n+"hidden"]=x,u)),C=this.props.children;return"card"===r&&(r=(0,D.default)(((r={})[n+"upload-card"]=!0,r[n+"disabled"]=l,r)),C=O.default.createElement("div",{className:r},O.default.createElement(P.default,{size:"large",type:"add",className:n+"upload-add-icon"}),O.default.createElement("div",{tabIndex:"0",role:"button",className:n+"upload-text"},C))),b?"function"==typeof w?(b=(0,D.default)(((r={})[n+"form-preview"]=!0,r[o]=!!o,r)),O.default.createElement("div",{style:i,className:b},w(this.state.value,this.props))):t?O.default.createElement(Y.default,{isPreview:!0,listType:t,style:i,className:o,value:this.state.value,onPreview:m}):null:(n=l?N.func.prevent:p,r=N.obj.pickAttrsWith(this.props,"data-"),O.default.createElement("div",(0,T.default)({className:f,style:i},r),O.default.createElement(j.default,(0,T.default)({},e,{name:M,beforeUpload:d,dragable:a,disabled:l||x,className:u,onSelect:this.onSelect,onDrop:this.onDrop,onProgress:this.onProgress,onSuccess:this.onSuccess,onError:this.onError,ref:this.saveUploaderRef}),C),t||g?O.default.createElement(Y.default,{useDataURL:s,fileNameRender:k,actionRender:S,uploader:this,listType:t,value:this.state.value,closable:c,onRemove:n,progressProps:v,onCancel:h,onPreview:m,extraRender:y,rtl:_,previewOnFileName:E}):null))},i=u=h,u.displayName="Upload",u.propTypes=(0,T.default)({},c.default.propTypes,Y.default.propTypes,{prefix:s.default.string.isRequired,action:s.default.string,value:s.default.array,defaultValue:s.default.array,shape:s.default.oneOf(["card"]),listType:s.default.oneOf(["text","image","card"]),list:s.default.any,name:s.default.string,data:s.default.oneOfType([s.default.object,s.default.func]),formatter:s.default.func,limit:s.default.number,timeout:s.default.number,dragable:s.default.bool,closable:s.default.bool,useDataURL:s.default.bool,disabled:s.default.bool,onSelect:s.default.func,onProgress:s.default.func,onChange:s.default.func,onSuccess:s.default.func,afterSelect:s.default.func,onRemove:s.default.func,onError:s.default.func,beforeUpload:s.default.func,onDrop:s.default.func,className:s.default.string,style:s.default.object,children:s.default.node,autoUpload:s.default.bool,request:s.default.func,progressProps:s.default.object,rtl:s.default.bool,isPreview:s.default.bool,renderPreview:s.default.func,fileKeyName:s.default.string,fileNameRender:s.default.func,actionRender:s.default.func,previewOnFileName:s.default.bool}),u.defaultProps=(0,T.default)({},c.default.defaultProps,{prefix:"next-",limit:1/0,autoUpload:!0,closable:!0,onSelect:n,onProgress:n,onChange:n,onSuccess:n,onRemove:n,onError:n,onDrop:n,beforeUpload:n,afterSelect:n,previewOnFileName:!1}),a=function(){var u=this;this.onSelect=function(e){var t,n,a=u.props,r=a.autoUpload,o=a.afterSelect,i=a.onSelect,a=a.limit,s=u.state.value.length+e.length,l=a-u.state.value.length;l<=0||(t=e=e.map(function(e){e=(0,d.fileToObject)(e);return e.state="selected",e}),n=[],ai||s+a.width>o):t<0||e<0||t+a.height>u.height||e+a.width>u.width}function L(e,t,n,a){var r=a.overlayInfo,a=a.containerInfo,n=n.split("");return 1===n.length&&n.push(""),t<0&&(n=[n[0].replace("t","b"),n[1].replace("b","t")]),e<0&&(n=[n[0].replace("l","r"),n[1].replace("r","l")]),t+r.height>a.height&&(n=[n[0].replace("b","t"),n[1].replace("t","b")]),(n=e+r.width>a.width?[n[0].replace("r","l"),n[1].replace("l","r")]:n).join("")}function O(e,t,n){var a=n.overlayInfo,n=n.containerInfo;return(t=t<0?0:t)+a.height>n.height&&(t=n.height-a.height),{left:e=(e=e<0?0:e)+a.width>n.width?n.width-a.width:e,top:t}}function be(e){var r,o,i,s,t,n,a,l,u,c,d,f=e.target,p=e.overlay,h=e.container,m=e.scrollNode,g=e.placement,y=e.placementOffset,y=void 0===y?0:y,v=e.points,v=void 0===v?["tl","bl"]:v,_=e.offset,_=void 0===_?[0,0]:_,b=e.position,b=void 0===b?"absolute":b,w=e.beforePosition,M=e.autoAdjust,M=void 0===M||M,k=e.autoHideScrollOverflow,k=void 0===k||k,e=e.rtl,S="offsetWidth"in(S=p)&&"offsetHeight"in S?{width:S.offsetWidth,height:S.offsetHeight}:{width:(S=S.getBoundingClientRect()).width,height:S.height},E=S.width,S=S.height;return"fixed"===b?(l={config:{placement:void 0,points:void 0},style:{position:b,left:_[0],top:_[1]}},w?w(l,{overlay:{node:p,width:E,height:S}}):l):(l=f.getBoundingClientRect(),r=l.width,o=l.height,i=l.left,s=l.top,t=(l=x(h)).left,l=l.top,u=h.scrollWidth,c=h.scrollHeight,n=h.scrollTop,a=h.scrollLeft,u=(l=C(g,t={targetInfo:{width:r,height:o,left:i,top:s},containerInfo:{left:t,top:l,width:u,height:c,scrollTop:n,scrollLeft:a},overlayInfo:{width:E,height:S},points:v,placementOffset:y,offset:_,container:h,rtl:e})).left,c=l.top,n=l.points,a=function(e){for(var t=e;t;){var n=he(t,"overflow");if(null!=n&&n.match(/auto|scroll|hidden/))return t;t=t.parentNode}return document.documentElement}(h),M&&g&&T(u,c,a,t)&&(g!==(v=L(u,c,g,t))&&(c=T(_=(y=C(v,t)).left,e=y.top,a,t)&&v!==(l=L(_,e,v,t))?(u=(M=O((h=C(g=l,t)).left,h.top,t)).left,M.top):(g=v,u=_,e)),u=(y=O(u,c,t)).left,c=y.top),d={config:{placement:g,points:n},style:{position:b,left:Math.round(u),top:Math.round(c)}},k&&g&&null!=m&&m.length&&m.forEach(function(e){var e=e.getBoundingClientRect(),t=e.top,n=e.left,a=e.width,e=e.height;d.style.display=s+o=e.length?{done:!0}:{done:!1,value:e[n++]}};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,a=new Array(t);nn.clientHeight&&0=11?e:e+12;else if(t==="下午"||t==="晚上")return e+12},meridiem:function(e,t,n){var a=e*100+t;if(a<600)return"凌晨";else if(a<900)return"早上";else if(a<1130)return"上午";else if(a<1230)return"中午";else if(a<1800)return"下午";else return"晚上"},calendar:{sameDay:"[今天] LT",nextDay:"[明天] LT",nextWeek:"[下]dddd LT",lastDay:"[昨天] LT",lastWeek:"[上]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|週)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"週";default:return e}},relativeTime:{future:"%s內",past:"%s前",s:"幾秒",ss:"%d 秒",m:"1 分鐘",mm:"%d 分鐘",h:"1 小時",hh:"%d 小時",d:"1 天",dd:"%d 天",M:"1 個月",MM:"%d 個月",y:"1 年",yy:"%d 年"}})}(n(9))},function(e,t,n){!function(e){"use strict"; //! moment.js locale configuration -var t;e.defineLocale("zh-tw",{months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"週日_週一_週二_週三_週四_週五_週六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),longDateFormat:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日 HH:mm",LLLL:"YYYY年M月D日dddd HH:mm",l:"YYYY/M/D",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},meridiemParse:/凌晨|早上|上午|中午|下午|晚上/,meridiemHour:function(e,t){if(e===12)e=0;if(t==="凌晨"||t==="早上"||t==="上午")return e;else if(t==="中午")return e>=11?e:e+12;else if(t==="下午"||t==="晚上")return e+12},meridiem:function(e,t,n){var a=e*100+t;if(a<600)return"凌晨";else if(a<900)return"早上";else if(a<1130)return"上午";else if(a<1230)return"中午";else if(a<1800)return"下午";else return"晚上"},calendar:{sameDay:"[今天] LT",nextDay:"[明天] LT",nextWeek:"[下]dddd LT",lastDay:"[昨天] LT",lastWeek:"[上]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|週)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"週";default:return e}},relativeTime:{future:"%s後",past:"%s前",s:"幾秒",ss:"%d 秒",m:"1 分鐘",mm:"%d 分鐘",h:"1 小時",hh:"%d 小時",d:"1 天",dd:"%d 天",M:"1 個月",MM:"%d 個月",y:"1 年",yy:"%d 年"}})}(n(9))},function(e,t,n){"use strict";t.__esModule=!0;var u=p(n(3)),a=p(n(4)),r=p(n(7)),o=p(n(8)),i=n(0),c=p(i),s=p(n(5)),d=p(n(18)),l=p(n(6)),f=n(11);function p(e){return e&&e.__esModule?e:{default:e}}h=i.Component,(0,o.default)(m,h),m.prototype.render=function(){var e,t=this.props,n=t.prefix,a=t.type,r=t.size,o=t.className,i=t.rtl,s=t.style,t=t.children,l=f.obj.pickOthers((0,u.default)({},m.propTypes),this.props),n=(0,d.default)(((e={})[n+"icon"]=!0,e[n+"icon-"+a]=!!a,e[""+n+r]=!!r&&"string"==typeof r,e[o]=!!o,e)),o=(i&&-1!==["arrow-left","arrow-right","arrow-double-left","arrow-double-right","switch","sorting","descending","ascending"].indexOf(a)&&(l.dir="rtl"),"number"==typeof r?{width:r,height:r,lineHeight:r+"px",fontSize:r}:{});return c.default.createElement("i",(0,u.default)({},l,{style:(0,u.default)({},o,s),className:n}),t)},i=n=m,n.propTypes=(0,u.default)({},l.default.propTypes,{type:s.default.string,children:s.default.node,size:s.default.oneOfType([s.default.oneOf(["xxs","xs","small","medium","large","xl","xxl","xxxl","inherit"]),s.default.number]),className:s.default.string,style:s.default.object}),n.defaultProps={prefix:"next-",size:"medium"},n._typeMark="icon";var h,o=i;function m(){return(0,a.default)(this,m),(0,r.default)(this,h.apply(this,arguments))}o.displayName="Icon",t.default=o,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var g=l(n(3)),y=l(n(16)),a=l(n(42)),r=l(n(4)),o=l(n(7)),i=l(n(8)),v=n(0),_=l(v),s=l(n(5)),b=n(166),w=l(n(544));function l(e){return e&&e.__esModule?e:{default:e}}function u(){}function M(e){return _.default.Children.toArray(e.children)[0]||null}c=v.Component,(0,i.default)(d,c),d.prototype.normalizeNames=function(e){return"string"==typeof e?{appear:e+"-appear",appearActive:e+"-appear-active",enter:e+"-enter",enterActive:e+"-enter-active",leave:e+"-leave",leaveActive:e+"-leave-active"}:"object"===(void 0===e?"undefined":(0,a.default)(e))?{appear:e.appear,appearActive:e.appear+"-active",enter:""+e.enter,enterActive:e.enter+"-active",leave:""+e.leave,leaveActive:e.leave+"-active"}:void 0},d.prototype.render=function(){var t=this,e=this.props,n=e.animation,a=e.children,r=e.animationAppear,o=e.singleMode,i=e.component,s=e.beforeAppear,l=e.onAppear,u=e.afterAppear,c=e.beforeEnter,d=e.onEnter,f=e.afterEnter,p=e.beforeLeave,h=e.onLeave,m=e.afterLeave,e=(0,y.default)(e,["animation","children","animationAppear","singleMode","component","beforeAppear","onAppear","afterAppear","beforeEnter","onEnter","afterEnter","beforeLeave","onLeave","afterLeave"]),a=v.Children.map(a,function(e){return _.default.createElement(w.default,{key:e.key,names:t.normalizeNames(n),onAppear:s,onAppearing:l,onAppeared:u,onEnter:c,onEntering:d,onEntered:f,onExit:p,onExiting:h,onExited:m},e)});return _.default.createElement(b.TransitionGroup,(0,g.default)({appear:r,component:o?M:i},e),a)},i=n=d,n.propTypes={animation:s.default.oneOfType([s.default.string,s.default.object]),animationAppear:s.default.bool,component:s.default.any,singleMode:s.default.bool,children:s.default.oneOfType([s.default.element,s.default.arrayOf(s.default.element)]),beforeAppear:s.default.func,onAppear:s.default.func,afterAppear:s.default.func,beforeEnter:s.default.func,onEnter:s.default.func,afterEnter:s.default.func,beforeLeave:s.default.func,onLeave:s.default.func,afterLeave:s.default.func},n.defaultProps={animationAppear:!0,component:"div",singleMode:!0,beforeAppear:u,onAppear:u,afterAppear:u,beforeEnter:u,onEnter:u,afterEnter:u,beforeLeave:u,onLeave:u,afterLeave:u};var c,s=i;function d(){return(0,r.default)(this,d),(0,o.default)(this,c.apply(this,arguments))}s.displayName="Animate",t.default=s,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0,t.default=t.EXITING=t.ENTERED=t.ENTERING=t.EXITED=t.UNMOUNTED=void 0;var a=function(e){{if(e&&e.__esModule)return e;var t,n={};if(null!=e)for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&((t=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,a):{}).get||t.set?Object.defineProperty(n,a,t):n[a]=e[a]);return n.default=e,n}}(n(5)),o=s(n(0)),i=s(n(24)),r=n(31);n(360);function s(e){return e&&e.__esModule?e:{default:e}}var l="unmounted",u=(t.UNMOUNTED=l,"exited"),c=(t.EXITED=u,"entering"),d=(t.ENTERING=c,"entered"),f=(t.ENTERED=d,"exiting"),n=(t.EXITING=f,function(r){var e;function t(e,t){var n,a=r.call(this,e,t)||this,t=t.transitionGroup,t=t&&!t.isMounting?e.enter:e.appear;return a.appearStatus=null,e.in?t?(n=u,a.appearStatus=c):n=d:n=e.unmountOnExit||e.mountOnEnter?l:u,a.state={status:n},a.nextCallback=null,a}e=r,(n=t).prototype=Object.create(e.prototype),(n.prototype.constructor=n).__proto__=e;var n=t.prototype;return n.getChildContext=function(){return{transitionGroup:null}},t.getDerivedStateFromProps=function(e,t){return e.in&&t.status===l?{status:u}:null},n.componentDidMount=function(){this.updateStatus(!0,this.appearStatus)},n.componentDidUpdate=function(e){var t=null;e!==this.props&&(e=this.state.status,this.props.in?e!==c&&e!==d&&(t=c):e!==c&&e!==d||(t=f)),this.updateStatus(!1,t)},n.componentWillUnmount=function(){this.cancelNextCallback()},n.getTimeouts=function(){var e,t,n=this.props.timeout,a=e=t=n;return null!=n&&"number"!=typeof n&&(a=n.exit,e=n.enter,t=void 0!==n.appear?n.appear:e),{exit:a,enter:e,appear:t}},n.updateStatus=function(e,t){var n;void 0===e&&(e=!1),null!==t?(this.cancelNextCallback(),n=i.default.findDOMNode(this),t===c?this.performEnter(n,e):this.performExit(n)):this.props.unmountOnExit&&this.state.status===u&&this.setState({status:l})},n.performEnter=function(e,t){var n=this,a=this.props.enter,r=this.context.transitionGroup?this.context.transitionGroup.isMounting:t,o=this.getTimeouts(),i=r?o.appear:o.enter;t||a?(this.props.onEnter(e,r),this.safeSetState({status:c},function(){n.props.onEntering(e,r),n.onTransitionEnd(e,i,function(){n.safeSetState({status:d},function(){n.props.onEntered(e,r)})})})):this.safeSetState({status:d},function(){n.props.onEntered(e)})},n.performExit=function(e){var t=this,n=this.props.exit,a=this.getTimeouts();n?(this.props.onExit(e),this.safeSetState({status:f},function(){t.props.onExiting(e),t.onTransitionEnd(e,a.exit,function(){t.safeSetState({status:u},function(){t.props.onExited(e)})})})):this.safeSetState({status:u},function(){t.props.onExited(e)})},n.cancelNextCallback=function(){null!==this.nextCallback&&(this.nextCallback.cancel(),this.nextCallback=null)},n.safeSetState=function(e,t){t=this.setNextCallback(t),this.setState(e,t)},n.setNextCallback=function(t){var n=this,a=!0;return this.nextCallback=function(e){a&&(a=!1,n.nextCallback=null,t(e))},this.nextCallback.cancel=function(){a=!1},this.nextCallback},n.onTransitionEnd=function(e,t,n){this.setNextCallback(n);n=null==t&&!this.props.addEndListener;!e||n?setTimeout(this.nextCallback,0):(this.props.addEndListener&&this.props.addEndListener(e,this.nextCallback),null!=t&&setTimeout(this.nextCallback,t))},n.render=function(){var e,t,n=this.state.status;return n===l?null:(e=(t=this.props).children,delete(t=function(e,t){if(null==e)return{};for(var n,a={},r=Object.keys(e),o=0;o 16.8.0"),null)},e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var L=l(n(3)),a=l(n(4)),r=l(n(7)),o=l(n(8)),p=n(0),O=l(p),i=l(n(5)),D=l(n(18)),h=l(n(26)),N=n(11),s=l(n(374)),P=l(n(375));function l(e){return e&&e.__esModule?e:{default:e}}function m(e){e.preventDefault()}u=s.default,(0,o.default)(j,u),j.prototype.getValueLength=function(e){var e=""+e,t=this.props.getValueLength(e);return t="number"!=typeof t?e.length:t},j.prototype.renderControl=function(){var e=this,t=this.props,n=t.hasClear,a=t.readOnly,r=t.state,o=t.prefix,i=t.hint,s=t.extra,l=t.locale,u=t.disabled,t=t.hoverShowClear,c=this.renderLength(),d=null,f=("success"===r?d=O.default.createElement(h.default,{type:"success-filling",className:o+"input-success-icon"}):"loading"===r?d=O.default.createElement(h.default,{type:"loading",className:o+"input-loading-icon"}):"warning"===r&&(d=O.default.createElement(h.default,{type:"warning",className:o+"input-warning-icon"})),null),a=n&&!a&&!!(""+this.state.value)&&!u;return(i||a)&&(u=null,u=i?"string"==typeof i?O.default.createElement(h.default,{type:i,className:o+"input-hint"}):(0,p.isValidElement)(i)?(0,p.cloneElement)(i,{className:(0,D.default)(i.props.className,o+"input-hint")}):i:(t=(0,D.default)(((a={})[o+"input-hint"]=!0,a[o+"input-clear-icon"]=!0,a[o+"input-hover-show"]=t,a)),O.default.createElement(h.default,{type:"delete-filling",role:"button",tabIndex:"0",className:t,"aria-label":l.clear,onClick:this.onClear.bind(this),onMouseDown:m,onKeyDown:this.handleKeyDownFromClear})),f=O.default.createElement("span",{className:o+"input-hint-wrap"},n&&i?O.default.createElement(h.default,{type:"delete-filling",role:"button",tabIndex:"0",className:o+"input-clear "+o+"input-clear-icon","aria-label":l.clear,onClick:this.onClear.bind(this),onMouseDown:m,onKeyDown:this.handleKeyDownFromClear}):null,u)),(f="loading"===r?null:f)||c||d||s?O.default.createElement("span",{onClick:function(){return e.focus()},className:o+"input-control"},f,c,d,s):null},j.prototype.renderLabel=function(){var e=this.props,t=e.label,n=e.prefix,e=e.id;return t?O.default.createElement("label",{className:n+"input-label",htmlFor:e},t):null},j.prototype.renderInner=function(e,t){return e?O.default.createElement("span",{className:t},e):null},j.prototype.onClear=function(e){this.props.disabled||("value"in this.props||this.setState({value:""}),this.props.onChange("",e,"clear"),this.focus())},j.prototype.render=function(){var e,t=this.props,n=t.size,a=t.htmlType,r=t.htmlSize,o=t.autoComplete,i=t.autoFocus,s=t.disabled,l=t.style,u=t.innerBefore,c=t.innerAfter,d=t.innerBeforeClassName,f=t.innerAfterClassName,p=t.className,h=t.hasBorder,m=t.prefix,g=t.isPreview,y=t.renderPreview,v=t.addonBefore,_=t.addonAfter,b=t.addonTextBefore,w=t.addonTextAfter,M=t.inputRender,k=t.rtl,t=t.composition,S=v||_||b||w,h=(0,D.default)(this.getClass(),((E={})[""+m+n]=!0,E[m+"hidden"]="hidden"===this.props.htmlType,E[m+"noborder"]=!h||"file"===this.props.htmlType,E[m+"input-group-auto-width"]=S,E[m+"disabled"]=s,E[p]=!!p&&!S,E)),E=m+"input-inner",d=(0,D.default)(((x={})[E]=!0,x[m+"before"]=!0,x[d]=d,x)),E=(0,D.default)(((x={})[E]=!0,x[m+"after"]=!0,x[m+"input-inner-text"]="string"==typeof c,x[f]=f,x)),x=(0,D.default)(((f={})[m+"form-preview"]=!0,f[p]=!!p,f)),f=this.getProps(),C=N.obj.pickAttrsWith(this.props,"data-"),T=N.obj.pickOthers((0,L.default)({},C,j.propTypes),this.props);return g?(g=f.value,e=this.props.label,"function"==typeof y?O.default.createElement("div",(0,L.default)({},T,{className:x}),y(g,this.props)):O.default.createElement("div",(0,L.default)({},T,{className:x}),v||b,e,u,g,c,_||w)):(y={},t&&(y.onCompositionStart=this.handleCompositionStart,y.onCompositionEnd=this.handleCompositionEnd),x=O.default.createElement("input",(0,L.default)({},T,f,y,{height:"100%",type:a,size:r,autoFocus:i,autoComplete:o,onKeyDown:this.handleKeyDown,ref:this.saveRef})),e=O.default.createElement("span",(0,L.default)({},C,{dir:k?"rtl":void 0,className:h,style:S?void 0:l}),this.renderLabel(),this.renderInner(u,d),M(x),this.renderInner(c,E),this.renderControl()),t=(0,D.default)(((g={})[m+"input-group-text"]=!0,g[""+m+n]=!!n,g[m+"disabled"]=s,g)),f=(0,D.default)(((T={})[t]=b,T)),a=(0,D.default)(((y={})[t]=w,y)),S?O.default.createElement(P.default,(0,L.default)({},C,{prefix:m,className:p,style:l,disabled:s,addonBefore:v||b,addonBeforeClassName:f,addonAfter:_||w,addonAfterClassName:a}),e):e)},o=n=j,n.displayName="Input",n.getDerivedStateFromProps=s.default.getDerivedStateFromProps,n.propTypes=(0,L.default)({},s.default.propTypes,{label:i.default.node,hasClear:i.default.bool,hasBorder:i.default.bool,state:i.default.oneOf(["error","loading","success","warning"]),onPressEnter:i.default.func,htmlType:i.default.string,htmlSize:i.default.string,hint:i.default.oneOfType([i.default.string,i.default.node]),innerBefore:i.default.node,innerAfter:i.default.node,addonBefore:i.default.node,addonAfter:i.default.node,addonTextBefore:i.default.node,addonTextAfter:i.default.node,autoComplete:i.default.string,autoFocus:i.default.bool,inputRender:i.default.func,extra:i.default.node,innerBeforeClassName:i.default.string,innerAfterClassName:i.default.string,isPreview:i.default.bool,renderPreview:i.default.func,hoverShowClear:i.default.bool}),n.defaultProps=(0,L.default)({},s.default.defaultProps,{autoComplete:"off",hasBorder:!0,isPreview:!1,hoverShowClear:!1,onPressEnter:N.func.noop,inputRender:function(e){return e}});var u,i=o;function j(e){(0,a.default)(this,j);var t=(0,r.default)(this,u.call(this,e)),n=(t.handleKeyDown=function(e){13===e.keyCode&&t.props.onPressEnter(e),t.onKeyDown(e)},t.handleKeyDownFromClear=function(e){13===e.keyCode&&t.onClear(e)},void 0),n="value"in e?e.value:e.defaultValue;return t.state={value:void 0===n?"":n},t}t.default=i,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var a,r=h(n(3)),o=h(n(4)),i=h(n(7)),s=h(n(8)),l=h(n(0)),u=h(n(5)),c=h(n(18)),d=n(31),f=h(n(6)),p=n(11),n=h(n(44));function h(e){return e&&e.__esModule?e:{default:e}}m=l.default.Component,(0,s.default)(g,m),g.getDerivedStateFromProps=function(e,t){return"value"in e&&e.value!==t.value&&!t.composition?{value:null==(t=e.value)?"":t}:null},g.prototype.ieHack=function(e){return e},g.prototype.onChange=function(e){"stopPropagation"in e?e.stopPropagation():"cancelBubble"in e&&e.cancelBubble();var t=e.target.value;this.props.trim&&(t=t.trim()),t=this.ieHack(t),"value"in this.props&&!this.state.composition||this.setState({value:t}),this.state.composition||(t&&"number"===this.props.htmlType&&(t=Number(t)),this.props.onChange(t,e))},g.prototype.onKeyDown=function(e){var t=e.target.value,n=this.props.maxLength,t=0>6]+c[128|63&l]:l<55296||57344<=l?i+=c[224|l>>12]+c[128|l>>6&63]+c[128|63&l]:(s+=1,l=65536+((1023&l)<<10|1023&o.charCodeAt(s)),i+=c[240|l>>18]+c[128|l>>12&63]+c[128|l>>6&63]+c[128|63&l])}return i},isBuffer:function(e){return!(!e||"object"!=typeof e||!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e)))},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},maybeMap:function(e,t){if(m(e)){for(var n=[],a=0;athis.popupNode.offsetWidth&&p(this.popupNode,"width",s.offsetWidth+"px"),"outside"!==a||"hoz"===r&&1===n||(p(this.popupNode,"height",u.offsetHeight+"px"),this.popupNode.firstElementChild&&p(this.popupNode.firstElementChild,"overflow-y","auto")),this.popupProps);c.onOpen&&c.onOpen()}catch(e){return null}},S.prototype.handlePopupClose=function(){var e=this.props.root.popupNodes,t=e.indexOf(this.popupNode),e=(-1t?r[t+1]:r[0])}),n[a]||(o=r[0]),i.onSort(a,o)},i.keydownHandler=function(e){e.preventDefault(),e.stopPropagation(),e.keyCode===s.KEYCODE.ENTER&&i.handleClick()},i.onSort=function(e,t){var n={};i.props.onSort(e,n[e]=t,n)},(0,o.default)(i,e)}i.displayName="Sort",t.default=i,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var r=d(n(3)),a=d(n(4)),o=d(n(7)),i=d(n(8)),s=d(n(0)),l=d(n(5)),u=d(n(18)),c=d(n(405));function d(e){return e&&e.__esModule?e:{default:e}}f=s.default.Component,(0,i.default)(p,f),p.prototype.render=function(){var e=this.props,t=e.className,n=e.record,e=e.primaryKey,a=this.context.selectedRowKeys,n=(0,u.default)(((a={selected:-1t.highWaterMark&&(t.highWaterMark=(p<=(n=e)?n=p:(n--,n=(n=(n=(n=(n|=n>>>1)|n>>>2)|n>>>4)|n>>>8)|n>>>16,n++),n)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function b(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(_("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?y.nextTick(w,e):w(e))}function w(e){_("emit readable"),e.emit("readable"),x(e)}function M(e,t){t.readingMore||(t.readingMore=!0,y.nextTick(k,e,t))}function k(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var a;eo.length?o.length:e;if(i===o.length?r+=o:r+=o.slice(0,e),0===(e-=i)){i===o.length?(++a,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n).data=o.slice(i);break}++a}return t.length-=a,r}:function(e,t){var n=c.allocUnsafe(e),a=t.head,r=1;a.data.copy(n),e-=a.data.length;for(;a=a.next;){var o=a.data,i=e>o.length?o.length:e;if(o.copy(n,n.length-e,0,i),0===(e-=i)){i===o.length?(++r,a.next?t.head=a.next:t.head=t.tail=null):(t.head=a).data=o.slice(i);break}++r}return t.length-=r,n})(e,t);return a}(e,t.buffer,t.decoder),n)}function T(e){var t=e._readableState;if(0=n.highWaterMark||n.ended)?(_("read: emitReadable",n.length,n.ended),(0===n.length&&n.ended?T:b)(this),null):0===(e=h(e,n))&&n.ended?(0===n.length&&T(this),null):(t=n.needReadable,_("need readable",t),(0===n.length||n.length-e=11?e:e+12;else if(t==="下午"||t==="晚上")return e+12},meridiem:function(e,t,n){var a=e*100+t;if(a<600)return"凌晨";else if(a<900)return"早上";else if(a<1130)return"上午";else if(a<1230)return"中午";else if(a<1800)return"下午";else return"晚上"},calendar:{sameDay:"[今天] LT",nextDay:"[明天] LT",nextWeek:"[下]dddd LT",lastDay:"[昨天] LT",lastWeek:"[上]dddd LT",sameElse:"L"},dayOfMonthOrdinalParse:/\d{1,2}(日|月|週)/,ordinal:function(e,t){switch(t){case"d":case"D":case"DDD":return e+"日";case"M":return e+"月";case"w":case"W":return e+"週";default:return e}},relativeTime:{future:"%s後",past:"%s前",s:"幾秒",ss:"%d 秒",m:"1 分鐘",mm:"%d 分鐘",h:"1 小時",hh:"%d 小時",d:"1 天",dd:"%d 天",M:"1 個月",MM:"%d 個月",y:"1 年",yy:"%d 年"}})}(n(9))},function(e,t,n){"use strict";t.__esModule=!0;var u=p(n(3)),a=p(n(4)),r=p(n(7)),o=p(n(8)),i=n(0),c=p(i),s=p(n(5)),d=p(n(19)),l=p(n(6)),f=n(11);function p(e){return e&&e.__esModule?e:{default:e}}h=i.Component,(0,o.default)(m,h),m.prototype.render=function(){var e,t=this.props,n=t.prefix,a=t.type,r=t.size,o=t.className,i=t.rtl,s=t.style,t=t.children,l=f.obj.pickOthers((0,u.default)({},m.propTypes),this.props),n=(0,d.default)(((e={})[n+"icon"]=!0,e[n+"icon-"+a]=!!a,e[""+n+r]=!!r&&"string"==typeof r,e[o]=!!o,e)),o=(i&&-1!==["arrow-left","arrow-right","arrow-double-left","arrow-double-right","switch","sorting","descending","ascending"].indexOf(a)&&(l.dir="rtl"),"number"==typeof r?{width:r,height:r,lineHeight:r+"px",fontSize:r}:{});return c.default.createElement("i",(0,u.default)({},l,{style:(0,u.default)({},o,s),className:n}),t)},i=n=m,n.propTypes=(0,u.default)({},l.default.propTypes,{type:s.default.string,children:s.default.node,size:s.default.oneOfType([s.default.oneOf(["xxs","xs","small","medium","large","xl","xxl","xxxl","inherit"]),s.default.number]),className:s.default.string,style:s.default.object}),n.defaultProps={prefix:"next-",size:"medium"},n._typeMark="icon";var h,o=i;function m(){return(0,a.default)(this,m),(0,r.default)(this,h.apply(this,arguments))}o.displayName="Icon",t.default=o,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var g=l(n(3)),y=l(n(17)),a=l(n(42)),r=l(n(4)),o=l(n(7)),i=l(n(8)),v=n(0),_=l(v),s=l(n(5)),b=n(166),w=l(n(544));function l(e){return e&&e.__esModule?e:{default:e}}function u(){}function M(e){return _.default.Children.toArray(e.children)[0]||null}c=v.Component,(0,i.default)(d,c),d.prototype.normalizeNames=function(e){return"string"==typeof e?{appear:e+"-appear",appearActive:e+"-appear-active",enter:e+"-enter",enterActive:e+"-enter-active",leave:e+"-leave",leaveActive:e+"-leave-active"}:"object"===(void 0===e?"undefined":(0,a.default)(e))?{appear:e.appear,appearActive:e.appear+"-active",enter:""+e.enter,enterActive:e.enter+"-active",leave:""+e.leave,leaveActive:e.leave+"-active"}:void 0},d.prototype.render=function(){var t=this,e=this.props,n=e.animation,a=e.children,r=e.animationAppear,o=e.singleMode,i=e.component,s=e.beforeAppear,l=e.onAppear,u=e.afterAppear,c=e.beforeEnter,d=e.onEnter,f=e.afterEnter,p=e.beforeLeave,h=e.onLeave,m=e.afterLeave,e=(0,y.default)(e,["animation","children","animationAppear","singleMode","component","beforeAppear","onAppear","afterAppear","beforeEnter","onEnter","afterEnter","beforeLeave","onLeave","afterLeave"]),a=v.Children.map(a,function(e){return _.default.createElement(w.default,{key:e.key,names:t.normalizeNames(n),onAppear:s,onAppearing:l,onAppeared:u,onEnter:c,onEntering:d,onEntered:f,onExit:p,onExiting:h,onExited:m},e)});return _.default.createElement(b.TransitionGroup,(0,g.default)({appear:r,component:o?M:i},e),a)},i=n=d,n.propTypes={animation:s.default.oneOfType([s.default.string,s.default.object]),animationAppear:s.default.bool,component:s.default.any,singleMode:s.default.bool,children:s.default.oneOfType([s.default.element,s.default.arrayOf(s.default.element)]),beforeAppear:s.default.func,onAppear:s.default.func,afterAppear:s.default.func,beforeEnter:s.default.func,onEnter:s.default.func,afterEnter:s.default.func,beforeLeave:s.default.func,onLeave:s.default.func,afterLeave:s.default.func},n.defaultProps={animationAppear:!0,component:"div",singleMode:!0,beforeAppear:u,onAppear:u,afterAppear:u,beforeEnter:u,onEnter:u,afterEnter:u,beforeLeave:u,onLeave:u,afterLeave:u};var c,s=i;function d(){return(0,r.default)(this,d),(0,o.default)(this,c.apply(this,arguments))}s.displayName="Animate",t.default=s,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0,t.default=t.EXITING=t.ENTERED=t.ENTERING=t.EXITED=t.UNMOUNTED=void 0;var a=function(e){{if(e&&e.__esModule)return e;var t,n={};if(null!=e)for(var a in e)Object.prototype.hasOwnProperty.call(e,a)&&((t=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,a):{}).get||t.set?Object.defineProperty(n,a,t):n[a]=e[a]);return n.default=e,n}}(n(5)),o=s(n(0)),i=s(n(25)),r=n(31);n(360);function s(e){return e&&e.__esModule?e:{default:e}}var l="unmounted",u=(t.UNMOUNTED=l,"exited"),c=(t.EXITED=u,"entering"),d=(t.ENTERING=c,"entered"),f=(t.ENTERED=d,"exiting"),n=(t.EXITING=f,function(r){var e;function t(e,t){var n,a=r.call(this,e,t)||this,t=t.transitionGroup,t=t&&!t.isMounting?e.enter:e.appear;return a.appearStatus=null,e.in?t?(n=u,a.appearStatus=c):n=d:n=e.unmountOnExit||e.mountOnEnter?l:u,a.state={status:n},a.nextCallback=null,a}e=r,(n=t).prototype=Object.create(e.prototype),(n.prototype.constructor=n).__proto__=e;var n=t.prototype;return n.getChildContext=function(){return{transitionGroup:null}},t.getDerivedStateFromProps=function(e,t){return e.in&&t.status===l?{status:u}:null},n.componentDidMount=function(){this.updateStatus(!0,this.appearStatus)},n.componentDidUpdate=function(e){var t=null;e!==this.props&&(e=this.state.status,this.props.in?e!==c&&e!==d&&(t=c):e!==c&&e!==d||(t=f)),this.updateStatus(!1,t)},n.componentWillUnmount=function(){this.cancelNextCallback()},n.getTimeouts=function(){var e,t,n=this.props.timeout,a=e=t=n;return null!=n&&"number"!=typeof n&&(a=n.exit,e=n.enter,t=void 0!==n.appear?n.appear:e),{exit:a,enter:e,appear:t}},n.updateStatus=function(e,t){var n;void 0===e&&(e=!1),null!==t?(this.cancelNextCallback(),n=i.default.findDOMNode(this),t===c?this.performEnter(n,e):this.performExit(n)):this.props.unmountOnExit&&this.state.status===u&&this.setState({status:l})},n.performEnter=function(e,t){var n=this,a=this.props.enter,r=this.context.transitionGroup?this.context.transitionGroup.isMounting:t,o=this.getTimeouts(),i=r?o.appear:o.enter;t||a?(this.props.onEnter(e,r),this.safeSetState({status:c},function(){n.props.onEntering(e,r),n.onTransitionEnd(e,i,function(){n.safeSetState({status:d},function(){n.props.onEntered(e,r)})})})):this.safeSetState({status:d},function(){n.props.onEntered(e)})},n.performExit=function(e){var t=this,n=this.props.exit,a=this.getTimeouts();n?(this.props.onExit(e),this.safeSetState({status:f},function(){t.props.onExiting(e),t.onTransitionEnd(e,a.exit,function(){t.safeSetState({status:u},function(){t.props.onExited(e)})})})):this.safeSetState({status:u},function(){t.props.onExited(e)})},n.cancelNextCallback=function(){null!==this.nextCallback&&(this.nextCallback.cancel(),this.nextCallback=null)},n.safeSetState=function(e,t){t=this.setNextCallback(t),this.setState(e,t)},n.setNextCallback=function(t){var n=this,a=!0;return this.nextCallback=function(e){a&&(a=!1,n.nextCallback=null,t(e))},this.nextCallback.cancel=function(){a=!1},this.nextCallback},n.onTransitionEnd=function(e,t,n){this.setNextCallback(n);n=null==t&&!this.props.addEndListener;!e||n?setTimeout(this.nextCallback,0):(this.props.addEndListener&&this.props.addEndListener(e,this.nextCallback),null!=t&&setTimeout(this.nextCallback,t))},n.render=function(){var e,t,n=this.state.status;return n===l?null:(e=(t=this.props).children,delete(t=function(e,t){if(null==e)return{};for(var n,a={},r=Object.keys(e),o=0;o 16.8.0"),null)},e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var L=l(n(3)),a=l(n(4)),r=l(n(7)),o=l(n(8)),p=n(0),O=l(p),i=l(n(5)),D=l(n(19)),h=l(n(26)),N=n(11),s=l(n(374)),P=l(n(375));function l(e){return e&&e.__esModule?e:{default:e}}function m(e){e.preventDefault()}u=s.default,(0,o.default)(j,u),j.prototype.getValueLength=function(e){var e=""+e,t=this.props.getValueLength(e);return t="number"!=typeof t?e.length:t},j.prototype.renderControl=function(){var e=this,t=this.props,n=t.hasClear,a=t.readOnly,r=t.state,o=t.prefix,i=t.hint,s=t.extra,l=t.locale,u=t.disabled,t=t.hoverShowClear,c=this.renderLength(),d=null,f=("success"===r?d=O.default.createElement(h.default,{type:"success-filling",className:o+"input-success-icon"}):"loading"===r?d=O.default.createElement(h.default,{type:"loading",className:o+"input-loading-icon"}):"warning"===r&&(d=O.default.createElement(h.default,{type:"warning",className:o+"input-warning-icon"})),null),a=n&&!a&&!!(""+this.state.value)&&!u;return(i||a)&&(u=null,u=i?"string"==typeof i?O.default.createElement(h.default,{type:i,className:o+"input-hint"}):(0,p.isValidElement)(i)?(0,p.cloneElement)(i,{className:(0,D.default)(i.props.className,o+"input-hint")}):i:(t=(0,D.default)(((a={})[o+"input-hint"]=!0,a[o+"input-clear-icon"]=!0,a[o+"input-hover-show"]=t,a)),O.default.createElement(h.default,{type:"delete-filling",role:"button",tabIndex:"0",className:t,"aria-label":l.clear,onClick:this.onClear.bind(this),onMouseDown:m,onKeyDown:this.handleKeyDownFromClear})),f=O.default.createElement("span",{className:o+"input-hint-wrap"},n&&i?O.default.createElement(h.default,{type:"delete-filling",role:"button",tabIndex:"0",className:o+"input-clear "+o+"input-clear-icon","aria-label":l.clear,onClick:this.onClear.bind(this),onMouseDown:m,onKeyDown:this.handleKeyDownFromClear}):null,u)),(f="loading"===r?null:f)||c||d||s?O.default.createElement("span",{onClick:function(){return e.focus()},className:o+"input-control"},f,c,d,s):null},j.prototype.renderLabel=function(){var e=this.props,t=e.label,n=e.prefix,e=e.id;return t?O.default.createElement("label",{className:n+"input-label",htmlFor:e},t):null},j.prototype.renderInner=function(e,t){return e?O.default.createElement("span",{className:t},e):null},j.prototype.onClear=function(e){this.props.disabled||("value"in this.props||this.setState({value:""}),this.props.onChange("",e,"clear"),this.focus())},j.prototype.render=function(){var e,t=this.props,n=t.size,a=t.htmlType,r=t.htmlSize,o=t.autoComplete,i=t.autoFocus,s=t.disabled,l=t.style,u=t.innerBefore,c=t.innerAfter,d=t.innerBeforeClassName,f=t.innerAfterClassName,p=t.className,h=t.hasBorder,m=t.prefix,g=t.isPreview,y=t.renderPreview,v=t.addonBefore,_=t.addonAfter,b=t.addonTextBefore,w=t.addonTextAfter,M=t.inputRender,k=t.rtl,t=t.composition,S=v||_||b||w,h=(0,D.default)(this.getClass(),((E={})[""+m+n]=!0,E[m+"hidden"]="hidden"===this.props.htmlType,E[m+"noborder"]=!h||"file"===this.props.htmlType,E[m+"input-group-auto-width"]=S,E[m+"disabled"]=s,E[p]=!!p&&!S,E)),E=m+"input-inner",d=(0,D.default)(((x={})[E]=!0,x[m+"before"]=!0,x[d]=d,x)),E=(0,D.default)(((x={})[E]=!0,x[m+"after"]=!0,x[m+"input-inner-text"]="string"==typeof c,x[f]=f,x)),x=(0,D.default)(((f={})[m+"form-preview"]=!0,f[p]=!!p,f)),f=this.getProps(),C=N.obj.pickAttrsWith(this.props,"data-"),T=N.obj.pickOthers((0,L.default)({},C,j.propTypes),this.props);return g?(g=f.value,e=this.props.label,"function"==typeof y?O.default.createElement("div",(0,L.default)({},T,{className:x}),y(g,this.props)):O.default.createElement("div",(0,L.default)({},T,{className:x}),v||b,e,u,g,c,_||w)):(y={},t&&(y.onCompositionStart=this.handleCompositionStart,y.onCompositionEnd=this.handleCompositionEnd),x=O.default.createElement("input",(0,L.default)({},T,f,y,{height:"100%",type:a,size:r,autoFocus:i,autoComplete:o,onKeyDown:this.handleKeyDown,ref:this.saveRef})),e=O.default.createElement("span",(0,L.default)({},C,{dir:k?"rtl":void 0,className:h,style:S?void 0:l}),this.renderLabel(),this.renderInner(u,d),M(x),this.renderInner(c,E),this.renderControl()),t=(0,D.default)(((g={})[m+"input-group-text"]=!0,g[""+m+n]=!!n,g[m+"disabled"]=s,g)),f=(0,D.default)(((T={})[t]=b,T)),a=(0,D.default)(((y={})[t]=w,y)),S?O.default.createElement(P.default,(0,L.default)({},C,{prefix:m,className:p,style:l,disabled:s,addonBefore:v||b,addonBeforeClassName:f,addonAfter:_||w,addonAfterClassName:a}),e):e)},o=n=j,n.displayName="Input",n.getDerivedStateFromProps=s.default.getDerivedStateFromProps,n.propTypes=(0,L.default)({},s.default.propTypes,{label:i.default.node,hasClear:i.default.bool,hasBorder:i.default.bool,state:i.default.oneOf(["error","loading","success","warning"]),onPressEnter:i.default.func,htmlType:i.default.string,htmlSize:i.default.string,hint:i.default.oneOfType([i.default.string,i.default.node]),innerBefore:i.default.node,innerAfter:i.default.node,addonBefore:i.default.node,addonAfter:i.default.node,addonTextBefore:i.default.node,addonTextAfter:i.default.node,autoComplete:i.default.string,autoFocus:i.default.bool,inputRender:i.default.func,extra:i.default.node,innerBeforeClassName:i.default.string,innerAfterClassName:i.default.string,isPreview:i.default.bool,renderPreview:i.default.func,hoverShowClear:i.default.bool}),n.defaultProps=(0,L.default)({},s.default.defaultProps,{autoComplete:"off",hasBorder:!0,isPreview:!1,hoverShowClear:!1,onPressEnter:N.func.noop,inputRender:function(e){return e}});var u,i=o;function j(e){(0,a.default)(this,j);var t=(0,r.default)(this,u.call(this,e)),n=(t.handleKeyDown=function(e){13===e.keyCode&&t.props.onPressEnter(e),t.onKeyDown(e)},t.handleKeyDownFromClear=function(e){13===e.keyCode&&t.onClear(e)},void 0),n="value"in e?e.value:e.defaultValue;return t.state={value:void 0===n?"":n},t}t.default=i,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var a,r=h(n(3)),o=h(n(4)),i=h(n(7)),s=h(n(8)),l=h(n(0)),u=h(n(5)),c=h(n(19)),d=n(31),f=h(n(6)),p=n(11),n=h(n(44));function h(e){return e&&e.__esModule?e:{default:e}}m=l.default.Component,(0,s.default)(g,m),g.getDerivedStateFromProps=function(e,t){return"value"in e&&e.value!==t.value&&!t.composition?{value:null==(t=e.value)?"":t}:null},g.prototype.ieHack=function(e){return e},g.prototype.onChange=function(e){"stopPropagation"in e?e.stopPropagation():"cancelBubble"in e&&e.cancelBubble();var t=e.target.value;this.props.trim&&(t=t.trim()),t=this.ieHack(t),"value"in this.props&&!this.state.composition||this.setState({value:t}),this.state.composition||(t&&"number"===this.props.htmlType&&(t=Number(t)),this.props.onChange(t,e))},g.prototype.onKeyDown=function(e){var t=e.target.value,n=this.props.maxLength,t=0>6]+c[128|63&l]:l<55296||57344<=l?i+=c[224|l>>12]+c[128|l>>6&63]+c[128|63&l]:(s+=1,l=65536+((1023&l)<<10|1023&o.charCodeAt(s)),i+=c[240|l>>18]+c[128|l>>12&63]+c[128|l>>6&63]+c[128|63&l])}return i},isBuffer:function(e){return!(!e||"object"!=typeof e||!(e.constructor&&e.constructor.isBuffer&&e.constructor.isBuffer(e)))},isRegExp:function(e){return"[object RegExp]"===Object.prototype.toString.call(e)},maybeMap:function(e,t){if(m(e)){for(var n=[],a=0;athis.popupNode.offsetWidth&&p(this.popupNode,"width",s.offsetWidth+"px"),"outside"!==a||"hoz"===r&&1===n||(p(this.popupNode,"height",u.offsetHeight+"px"),this.popupNode.firstElementChild&&p(this.popupNode.firstElementChild,"overflow-y","auto")),this.popupProps);c.onOpen&&c.onOpen()}catch(e){return null}},S.prototype.handlePopupClose=function(){var e=this.props.root.popupNodes,t=e.indexOf(this.popupNode),e=(-1t?r[t+1]:r[0])}),n[a]||(o=r[0]),i.onSort(a,o)},i.keydownHandler=function(e){e.preventDefault(),e.stopPropagation(),e.keyCode===s.KEYCODE.ENTER&&i.handleClick()},i.onSort=function(e,t){var n={};i.props.onSort(e,n[e]=t,n)},(0,o.default)(i,e)}i.displayName="Sort",t.default=i,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var r=d(n(3)),a=d(n(4)),o=d(n(7)),i=d(n(8)),s=d(n(0)),l=d(n(5)),u=d(n(19)),c=d(n(405));function d(e){return e&&e.__esModule?e:{default:e}}f=s.default.Component,(0,i.default)(p,f),p.prototype.render=function(){var e=this.props,t=e.className,n=e.record,e=e.primaryKey,a=this.context.selectedRowKeys,n=(0,u.default)(((a={selected:-1t.highWaterMark&&(t.highWaterMark=(p<=(n=e)?n=p:(n--,n=(n=(n=(n=(n|=n>>>1)|n>>>2)|n>>>4)|n>>>8)|n>>>16,n++),n)),e<=t.length?e:t.ended?t.length:(t.needReadable=!0,0))}function b(e){var t=e._readableState;t.needReadable=!1,t.emittedReadable||(_("emitReadable",t.flowing),t.emittedReadable=!0,t.sync?y.nextTick(w,e):w(e))}function w(e){_("emit readable"),e.emit("readable"),x(e)}function M(e,t){t.readingMore||(t.readingMore=!0,y.nextTick(k,e,t))}function k(e,t){for(var n=t.length;!t.reading&&!t.flowing&&!t.ended&&t.length=t.length?(n=t.decoder?t.buffer.join(""):1===t.buffer.length?t.buffer.head.data:t.buffer.concat(t.length),t.buffer.clear()):n=function(e,t,n){var a;eo.length?o.length:e;if(i===o.length?r+=o:r+=o.slice(0,e),0===(e-=i)){i===o.length?(++a,n.next?t.head=n.next:t.head=t.tail=null):(t.head=n).data=o.slice(i);break}++a}return t.length-=a,r}:function(e,t){var n=c.allocUnsafe(e),a=t.head,r=1;a.data.copy(n),e-=a.data.length;for(;a=a.next;){var o=a.data,i=e>o.length?o.length:e;if(o.copy(n,n.length-e,0,i),0===(e-=i)){i===o.length?(++r,a.next?t.head=a.next:t.head=t.tail=null):(t.head=a).data=o.slice(i);break}++r}return t.length-=r,n})(e,t);return a}(e,t.buffer,t.decoder),n)}function T(e){var t=e._readableState;if(0=n.highWaterMark||n.ended)?(_("read: emitReadable",n.length,n.ended),(0===n.length&&n.ended?T:b)(this),null):0===(e=h(e,n))&&n.ended?(0===n.length&&T(this),null):(t=n.needReadable,_("need readable",t),(0===n.length||n.length-e * @license MIT */ -var S=P(706),o=P(707),s=P(708);function n(){return d.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function l(e,t){if(n()=n())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+n().toString(16)+" bytes");return 0|e}function f(e,t){if(d.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;var n=(e="string"!=typeof e?""+e:e).length;if(0===n)return 0;for(var a=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return L(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return O(e).length;default:if(a)return L(e).length;t=(""+t).toLowerCase(),a=!0}}function t(e,t,n){var a,r=!1;if((t=void 0===t||t<0?0:t)>this.length)return"";if((n=void 0===n||n>this.length?this.length:n)<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e=e||"utf8";;)switch(e){case"hex":var o=this,i=t,s=n,l=o.length;(!s||s<0||l=e.length){if(r)return-1;n=e.length-1}else if(n<0){if(!r)return-1;n=0}if("string"==typeof t&&(t=d.from(t,a)),d.isBuffer(t))return 0===t.length?-1:m(e,t,n,a,r);if("number"==typeof t)return t&=255,d.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?(r?Uint8Array.prototype.indexOf:Uint8Array.prototype.lastIndexOf).call(e,t,n):m(e,[t],n,a,r);throw new TypeError("val must be string, number or Buffer")}function m(e,t,n,a,r){var o=1,i=e.length,s=t.length;if(void 0!==a&&("ucs2"===(a=String(a).toLowerCase())||"ucs-2"===a||"utf16le"===a||"utf-16le"===a)){if(e.length<2||t.length<2)return-1;i/=o=2,s/=2,n/=2}function l(e,t){return 1===o?e[t]:e.readUInt16BE(t*o)}if(r)for(var u=-1,c=n;c>8,r.push(n%256),r.push(a);return r}(t,e.length-n),e,n,a)}function E(e,t,n){n=Math.min(e.length,n);for(var a=[],r=t;r>>10&1023|55296),c=56320|1023&c),a.push(c),r+=d}var f=a,p=f.length;if(p<=v)return String.fromCharCode.apply(String,f);for(var h="",m=0;mt)&&(e+=" ... "),""},d.prototype.compare=function(e,t,n,a,r){if(!d.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===n&&(n=e?e.length:0),void 0===a&&(a=0),void 0===r&&(r=this.length),(t=void 0===t?0:t)<0||n>e.length||a<0||r>this.length)throw new RangeError("out of range index");if(r<=a&&n<=t)return 0;if(r<=a)return-1;if(n<=t)return 1;if(this===e)return 0;for(var o=(r>>>=0)-(a>>>=0),i=(n>>>=0)-(t>>>=0),s=Math.min(o,i),l=this.slice(a,r),u=e.slice(t,n),c=0;cthis.length)throw new RangeError("Attempt to write outside buffer bounds");a=a||"utf8";for(var o,i,s,l=!1;;)switch(a){case"hex":var u=this,c=e,d=t,f=n,p=(d=Number(d)||0,u.length-d);if((!f||p<(f=Number(f)))&&(f=p),(p=c.length)%2!=0)throw new TypeError("Invalid hex string");p/2e.length)throw new RangeError("Index out of range")}function w(e,t,n,a){t<0&&(t=65535+t+1);for(var r=0,o=Math.min(e.length-n,2);r>>8*(a?r:1-r)}function M(e,t,n,a){t<0&&(t=4294967295+t+1);for(var r=0,o=Math.min(e.length-n,4);r>>8*(a?r:3-r)&255}function k(e,t,n,a){if(n+a>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function x(e,t,n,a,r){return r||k(e,0,n,4),o.write(e,t,n,a,23,4),n+4}function C(e,t,n,a,r){return r||k(e,0,n,8),o.write(e,t,n,a,52,8),n+8}d.prototype.slice=function(e,t){var n=this.length;if((e=~~e)<0?(e+=n)<0&&(e=0):n>>8):w(this,e,t,!0),t+2},d.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,2,65535,0),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):w(this,e,t,!1),t+2},d.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,4294967295,0),d.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):M(this,e,t,!0),t+4},d.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,4294967295,0),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},d.prototype.writeIntLE=function(e,t,n,a){e=+e,t|=0,a||b(this,e,t,n,(a=Math.pow(2,8*n-1))-1,-a);var r=0,o=1,i=0;for(this[t]=255&e;++r>0)-i&255;return t+n},d.prototype.writeIntBE=function(e,t,n,a){e=+e,t|=0,a||b(this,e,t,n,(a=Math.pow(2,8*n-1))-1,-a);var r=n-1,o=1,i=0;for(this[t+r]=255&e;0<=--r&&(o*=256);)e<0&&0===i&&0!==this[t+r+1]&&(i=1),this[t+r]=(e/o>>0)-i&255;return t+n},d.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,1,127,-128),d.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&(e=e<0?255+e+1:e),t+1},d.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,2,32767,-32768),d.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):w(this,e,t,!0),t+2},d.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,2,32767,-32768),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):w(this,e,t,!1),t+2},d.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,2147483647,-2147483648),d.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):M(this,e,t,!0),t+4},d.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},d.prototype.writeFloatLE=function(e,t,n){return x(this,e,t,!0,n)},d.prototype.writeFloatBE=function(e,t,n){return x(this,e,t,!1,n)},d.prototype.writeDoubleLE=function(e,t,n){return C(this,e,t,!0,n)},d.prototype.writeDoubleBE=function(e,t,n){return C(this,e,t,!1,n)},d.prototype.copy=function(e,t,n,a){if(n=n||0,a||0===a||(a=this.length),t>=e.length&&(t=e.length),(a=0=this.length)throw new RangeError("sourceStart out of bounds");if(a<0)throw new RangeError("sourceEnd out of bounds");a>this.length&&(a=this.length);var r,o=(a=e.length-t>>=0,n=void 0===n?this.length:n>>>0,"number"==typeof(e=e||0))for(s=t;s>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function O(e){return S.toByteArray(function(e){var t;if((e=((t=e).trim?t.trim():t.replace(/^\s+|\s+$/g,"")).replace(T,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function D(e,t,n,a){for(var r=0;r=t.length||r>=e.length);++r)t[r+n]=e[r];return r}}.call(this,P(65))},function(e,t,n){"use strict";var o=n(138);function i(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,a=this._readableState&&this._readableState.destroyed,r=this._writableState&&this._writableState.destroyed;return a||r?t?t(e):e&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,o.nextTick(i,this,e)):o.nextTick(i,this,e)):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?n._writableState?n._writableState.errorEmitted||(n._writableState.errorEmitted=!0,o.nextTick(i,n,e)):o.nextTick(i,n,e):t&&t(e)})),this},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";var a=n(139).Buffer,r=a.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"==typeof t||a.isEncoding!==r&&r(e))return t||e;throw new Error("Unknown encoding: "+e)}function i(e){var t;switch(this.encoding=o(e),this.encoding){case"utf16le":this.text=u,this.end=c,t=4;break;case"utf8":this.fillLast=l,t=4;break;case"base64":this.text=d,this.end=f,t=3;break;default:return this.write=p,void(this.end=h)}this.lastNeed=0,this.lastTotal=0,this.lastChar=a.allocUnsafe(t)}function s(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function l(e){var t,n=this.lastTotal-this.lastNeed,a=(t=this,128!=(192&(a=e)[0])?(t.lastNeed=0,"�"):1e.slidesToShow&&(n=e.slideWidth*e.slidesToShow*-1,o=e.slideHeight*e.slidesToShow*-1),e.slideCount%e.slidesToScroll!=0&&(t=e.slideIndex+e.slidesToScroll>e.slideCount&&e.slideCount>e.slidesToShow,t=e.rtl?(e.slideIndex>=e.slideCount?e.slideCount-e.slideIndex:e.slideIndex)+e.slidesToScroll>e.slideCount&&e.slideCount>e.slidesToShow:t)&&(o=e.slideIndex>e.slideCount?(n=(e.slidesToShow-(e.slideIndex-e.slideCount))*e.slideWidth*-1,(e.slidesToShow-(e.slideIndex-e.slideCount))*e.slideHeight*-1):(n=e.slideCount%e.slidesToScroll*e.slideWidth*-1,e.slideCount%e.slidesToScroll*e.slideHeight*-1))):e.slideCount%e.slidesToScroll!=0&&e.slideIndex+e.slidesToScroll>e.slideCount&&e.slideCount>e.slidesToShow&&(n=(e.slidesToShow-e.slideCount%e.slidesToScroll)*e.slideWidth),e.centerMode&&(e.infinite?n+=e.slideWidth*Math.floor(e.slidesToShow/2):n=e.slideWidth*Math.floor(e.slidesToShow/2)),a=e.vertical?e.slideIndex*e.slideHeight*-1+o:e.slideIndex*e.slideWidth*-1+n,!0===e.variableWidth&&(t=void 0,a=(r=e.slideCount<=e.slidesToShow||!1===e.infinite?i.default.findDOMNode(e.trackRef).childNodes[e.slideIndex]:(t=e.slideIndex+e.slidesToShow,i.default.findDOMNode(e.trackRef).childNodes[t]))?-1*r.offsetLeft:0,!0===e.centerMode)&&(r=!1===e.infinite?i.default.findDOMNode(e.trackRef).children[e.slideIndex]:i.default.findDOMNode(e.trackRef).children[e.slideIndex+e.slidesToShow+1])?-1*r.offsetLeft+(e.listWidth-r.offsetWidth)/2:a)}},function(e,t,n){"use strict";t.__esModule=!0;var p=u(n(3)),h=u(n(16)),o=u(n(4)),i=u(n(7)),a=u(n(8)),m=u(n(0)),r=u(n(5)),g=u(n(18)),s=u(n(6)),y=u(n(26)),l=n(11);function u(e){return e&&e.__esModule?e:{default:e}}c=m.default.Component,(0,a.default)(d,c),d.prototype.render=function(){var e=this.props,t=e.title,n=e.children,a=e.className,r=e.isExpanded,o=e.disabled,i=e.style,s=e.prefix,l=e.onClick,u=e.id,e=(0,h.default)(e,["title","children","className","isExpanded","disabled","style","prefix","onClick","id"]),a=(0,g.default)(((c={})[s+"collapse-panel"]=!0,c[s+"collapse-panel-hidden"]=!r,c[s+"collapse-panel-expanded"]=r,c[s+"collapse-panel-disabled"]=o,c[a]=a,c)),c=(0,g.default)(((c={})[s+"collapse-panel-icon"]=!0,c[s+"collapse-panel-icon-expanded"]=r,c)),d=u?u+"-heading":void 0,f=u?u+"-region":void 0;return m.default.createElement("div",(0,p.default)({className:a,style:i,id:u},e),m.default.createElement("div",{id:d,className:s+"collapse-panel-title",onClick:l,onKeyDown:this.onKeyDown,tabIndex:"0","aria-disabled":o,"aria-expanded":r,"aria-controls":f,role:"button"},m.default.createElement(y.default,{type:"arrow-right",className:c,"aria-hidden":"true"}),t),m.default.createElement("div",{className:s+"collapse-panel-content",role:"region",id:f},n))},a=n=d,n.propTypes={prefix:r.default.string,style:r.default.object,children:r.default.any,isExpanded:r.default.bool,disabled:r.default.bool,title:r.default.node,className:r.default.string,onClick:r.default.func,id:r.default.string},n.defaultProps={prefix:"next-",isExpanded:!1,onClick:l.func.noop},n.isNextPanel=!0;var c,r=a;function d(){var e,n;(0,o.default)(this,d);for(var t=arguments.length,a=Array(t),r=0;r\n com.alibaba.nacos\n nacos-client\n ${version}\n \n*/\npackage com.alibaba.nacos.example;\n\nimport java.util.Properties;\nimport java.util.concurrent.Executor;\nimport com.alibaba.nacos.api.NacosFactory;\nimport com.alibaba.nacos.api.config.ConfigService;\nimport com.alibaba.nacos.api.config.listener.Listener;\nimport com.alibaba.nacos.api.exception.NacosException;\n\n/**\n * Config service example\n *\n * @author Nacos\n *\n */\npublic class ConfigExample {\n\n\tpublic static void main(String[] args) throws NacosException, InterruptedException {\n\t\tString serverAddr = "localhost";\n\t\tString dataId = "'.concat(e.dataId,'";\n\t\tString group = "').concat(e.group,'";\n\t\tProperties properties = new Properties();\n\t\tproperties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);\n\t\tConfigService configService = NacosFactory.createConfigService(properties);\n\t\tString content = configService.getConfig(dataId, group, 5000);\n\t\tSystem.out.println(content);\n\t\tconfigService.addListener(dataId, group, new Listener() {\n\t\t\t@Override\n\t\t\tpublic void receiveConfigInfo(String configInfo) {\n\t\t\t\tSystem.out.println("receive:" + configInfo);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic Executor getExecutor() {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t});\n\n\t\tboolean isPublishOk = configService.publishConfig(dataId, group, "content");\n\t\tSystem.out.println(isPublishOk);\n\n\t\tThread.sleep(3000);\n\t\tcontent = configService.getConfig(dataId, group, 5000);\n\t\tSystem.out.println(content);\n\n\t\tboolean isRemoveOk = configService.removeConfig(dataId, group);\n\t\tSystem.out.println(isRemoveOk);\n\t\tThread.sleep(3000);\n\n\t\tcontent = configService.getConfig(dataId, group, 5000);\n\t\tSystem.out.println(content);\n\t\tThread.sleep(300000);\n\n\t}\n}\n')}},{key:"getNodejsCode",value:function(e){return"TODO"}},{key:"getCppCode",value:function(e){return"TODO"}},{key:"getShellCode",value:function(e){return"TODO"}},{key:"getPythonCode",value:function(e){return'/*\n* Demo for Nacos\n*/\nimport json\nimport socket\n\nimport nacos\n\n\ndef get_host_ip():\n res = socket.gethostbyname(socket.gethostname())\n return res\n\n\ndef load_config(content):\n _config = json.loads(content)\n return _config\n\n\ndef nacos_config_callback(args):\n content = args[\'raw_content\']\n load_config(content)\n\n\nclass NacosClient:\n service_name = None\n service_port = None\n service_group = None\n\n def __init__(self, server_endpoint, namespace_id, username=None, password=None):\n self.client = nacos.NacosClient(server_endpoint,\n namespace=namespace_id,\n username=username,\n password=password)\n self.endpoint = server_endpoint\n self.service_ip = get_host_ip()\n\n def register(self):\n self.client.add_naming_instance(self.service_name,\n self.service_ip,\n self.service_port,\n group_name=self.service_group)\n\n def modify(self, service_name, service_ip=None, service_port=None):\n self.client.modify_naming_instance(service_name,\n service_ip if service_ip else self.service_ip,\n service_port if service_port else self.service_port)\n\n def unregister(self):\n self.client.remove_naming_instance(self.service_name,\n self.service_ip,\n self.service_port)\n\n def set_service(self, service_name, service_ip, service_port, service_group):\n self.service_name = service_name\n self.service_ip = service_ip\n self.service_port = service_port\n self.service_group = service_group\n\n async def beat_callback(self):\n self.client.send_heartbeat(self.service_name,\n self.service_ip,\n self.service_port)\n\n def load_conf(self, data_id, group):\n return self.client.get_config(data_id=data_id, group=group, no_snapshot=True)\n\n def add_conf_watcher(self, data_id, group, callback):\n self.client.add_config_watcher(data_id=data_id, group=group, cb=callback)\n\n\nif __name__ == \'__main__\':\n nacos_config = {\n "nacos_data_id":"test",\n "nacos_server_ip":"127.0.0.1",\n "nacos_namespace":"public",\n "nacos_groupName":"DEFAULT_GROUP",\n "nacos_user":"nacos",\n "nacos_password":"1234567"\n }\n nacos_data_id = nacos_config["nacos_data_id"]\n SERVER_ADDRESSES = nacos_config["nacos_server_ip"]\n NAMESPACE = nacos_config["nacos_namespace"]\n groupName = nacos_config["nacos_groupName"]\n user = nacos_config["nacos_user"]\n password = nacos_config["nacos_password"]\n # todo 将另一个路由对象(通常定义在其他模块或文件中)合并到主应用(app)中。\n # app.include_router(custom_api.router, tags=[\'test\'])\n service_ip = get_host_ip()\n client = NacosClient(SERVER_ADDRESSES, NAMESPACE, user, password)\n client.add_conf_watcher(nacos_data_id, groupName, nacos_config_callback)\n\n # 启动时,强制同步一次配置\n data_stream = client.load_conf(nacos_data_id, groupName)\n json_config = load_config(data_stream)\n'}},{key:"getCSharpCode",value:function(e){return'/*\nDemo for Basic Nacos Opreation\nApp.csproj\n\n\n \n\n*/\n\nusing Microsoft.Extensions.DependencyInjection;\nusing Nacos.V2;\nusing Nacos.V2.DependencyInjection;\nusing System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\nclass Program\n{\n static async Task Main(string[] args)\n {\n string serverAddr = "http://localhost:8848";\n string dataId = "'.concat(e.dataId,'";\n string group = "').concat(e.group,'";\n\n IServiceCollection services = new ServiceCollection();\n\n services.AddNacosV2Config(x =>\n {\n x.ServerAddresses = new List { serverAddr };\n x.Namespace = "cs-test";\n\n // swich to use http or rpc\n x.ConfigUseRpc = true;\n });\n\n IServiceProvider serviceProvider = services.BuildServiceProvider();\n var configSvc = serviceProvider.GetService();\n\n var content = await configSvc.GetConfig(dataId, group, 3000);\n Console.WriteLine(content);\n\n var listener = new ConfigListener();\n\n await configSvc.AddListener(dataId, group, listener);\n\n var isPublishOk = await configSvc.PublishConfig(dataId, group, "content");\n Console.WriteLine(isPublishOk);\n\n await Task.Delay(3000);\n content = await configSvc.GetConfig(dataId, group, 5000);\n Console.WriteLine(content);\n\n var isRemoveOk = await configSvc.RemoveConfig(dataId, group);\n Console.WriteLine(isRemoveOk);\n await Task.Delay(3000);\n\n content = await configSvc.GetConfig(dataId, group, 5000);\n Console.WriteLine(content);\n await Task.Delay(300000);\n }\n\n internal class ConfigListener : IListener\n {\n public void ReceiveConfigInfo(string configInfo)\n {\n Console.WriteLine("receive:" + configInfo);\n }\n }\n}\n\n/*\nRefer to document: https://github.com/nacos-group/nacos-sdk-csharp/tree/dev/samples/MsConfigApp\nDemo for ASP.NET Core Integration\nMsConfigApp.csproj\n\n\n \n\n*/\n\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Hosting;\nusing Serilog;\nusing Serilog.Events;\n\npublic class Program\n{\n public static void Main(string[] args)\n {\n Log.Logger = new LoggerConfiguration()\n .Enrich.FromLogContext()\n .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)\n .MinimumLevel.Override("System", LogEventLevel.Warning)\n .MinimumLevel.Debug()\n .WriteTo.Console()\n .CreateLogger();\n\n try\n {\n Log.ForContext().Information("Application starting...");\n CreateHostBuilder(args, Log.Logger).Build().Run();\n }\n catch (System.Exception ex)\n {\n Log.ForContext().Fatal(ex, "Application start-up failed!!");\n }\n finally\n {\n Log.CloseAndFlush();\n }\n }\n\n public static IHostBuilder CreateHostBuilder(string[] args, Serilog.ILogger logger) =>\n Host.CreateDefaultBuilder(args)\n .ConfigureAppConfiguration((context, builder) =>\n {\n var c = builder.Build();\n builder.AddNacosV2Configuration(c.GetSection("NacosConfig"), logAction: x => x.AddSerilog(logger));\n })\n .ConfigureWebHostDefaults(webBuilder =>\n {\n webBuilder.UseStartup().UseUrls("http://*:8787");\n })\n .UseSerilog();\n}\n ')}},{key:"openDialog",value:function(e){var t=this;this.setState({dialogvisible:!0}),this.record=e,setTimeout(function(){t.getData()})}},{key:"closeDialog",value:function(){this.setState({dialogvisible:!1})}},{key:"createCodeMirror",value:function(e,t){var n=this.refs.codepreview;n&&(n.innerHTML="",this.cm=window.CodeMirror(n,{value:t,mode:e,height:400,width:500,lineNumbers:!0,theme:"xq-light",lint:!0,tabMode:"indent",autoMatchParens:!0,textWrapping:!0,gutters:["CodeMirror-lint-markers"],extraKeys:{F1:function(e){e.setOption("fullScreen",!e.getOption("fullScreen"))},Esc:function(e){e.getOption("fullScreen")&&e.setOption("fullScreen",!1)}}}))}},{key:"changeTab",value:function(e,t){var n=this;setTimeout(function(){n[e]=!0,n.createCodeMirror("text/javascript",t)})}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e;return x.a.createElement("div",null,x.a.createElement(y.a,{title:e.sampleCode,style:{width:"80%"},visible:this.state.dialogvisible,footer:x.a.createElement("div",null),onClose:this.closeDialog.bind(this)},x.a.createElement("div",{style:{height:500}},x.a.createElement(H.a,{tip:e.loading,style:{width:"100%"},visible:this.state.loading},x.a.createElement(L.a,{shape:"text",style:{height:40,paddingBottom:10}},x.a.createElement(O,{title:"Java",key:1,onClick:this.changeTab.bind(this,"commoneditor1",this.defaultCode)}),x.a.createElement(O,{title:"Spring Boot",key:2,onClick:this.changeTab.bind(this,"commoneditor2",this.sprigboot_code)}),x.a.createElement(O,{title:"Spring Cloud",key:21,onClick:this.changeTab.bind(this,"commoneditor21",this.sprigcloud_code)}),x.a.createElement(O,{title:"Node.js",key:3,onClick:this.changeTab.bind(this,"commoneditor3",this.nodejsCode)}),x.a.createElement(O,{title:"C++",key:4,onClick:this.changeTab.bind(this,"commoneditor4",this.cppCode)}),x.a.createElement(O,{title:"Shell",key:5,onClick:this.changeTab.bind(this,"commoneditor5",this.shellCode)}),x.a.createElement(O,{title:"Python",key:6,onClick:this.changeTab.bind(this,"commoneditor6",this.pythonCode)}),x.a.createElement(O,{title:"C#",key:7,onClick:this.changeTab.bind(this,"commoneditor7",this.csharpCode)})),x.a.createElement("div",{ref:"codepreview"})))))}}]),n}(x.a.Component)).displayName="ShowCodeing",S=S))||S,S=(t(69),t(40)),S=t.n(S),z=(t(756),S.a.Row),D=S.a.Col,W=(0,n.a.config)(((S=function(e){Object(M.a)(n,e);var t=Object(k.a)(n);function n(e){return Object(_.a)(this,n),(e=t.call(this,e)).state={visible:!1,title:"",content:"",isok:!0,dataId:"",group:""},e}return Object(b.a)(n,[{key:"componentDidMount",value:function(){this.initData()}},{key:"initData",value:function(){var e=this.props.locale;this.setState({title:(void 0===e?{}:e).confManagement})}},{key:"openDialog",value:function(e){this.setState({visible:!0,title:e.title,content:e.content,isok:e.isok,dataId:e.dataId,group:e.group,message:e.message})}},{key:"closeDialog",value:function(){this.setState({visible:!1})}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e,t=x.a.createElement("div",{style:{textAlign:"right"}},x.a.createElement(c.a,{type:"primary",onClick:this.closeDialog.bind(this)},e.determine));return x.a.createElement("div",null,x.a.createElement(y.a,{visible:this.state.visible,footer:t,style:{width:555},onCancel:this.closeDialog.bind(this),onClose:this.closeDialog.bind(this),title:e.deletetitle},x.a.createElement("div",null,x.a.createElement(z,null,x.a.createElement(D,{span:"4",style:{paddingTop:16}},x.a.createElement(m.a,{type:"".concat(this.state.isok?"success":"delete","-filling"),style:{color:this.state.isok?"green":"red"},size:"xl"})),x.a.createElement(D,{span:"20"},x.a.createElement("div",null,x.a.createElement("h3",null,this.state.isok?e.deletedSuccessfully:e.deleteFailed),x.a.createElement("p",null,x.a.createElement("span",{style:{color:"#999",marginRight:5}},"Data ID"),x.a.createElement("span",{style:{color:"#c7254e"}},this.state.dataId)),x.a.createElement("p",null,x.a.createElement("span",{style:{color:"#999",marginRight:5}},"Group"),x.a.createElement("span",{style:{color:"#c7254e"}},this.state.group)),this.state.isok?"":x.a.createElement("p",{style:{color:"red"}},this.state.message)))))))}}]),n}(x.a.Component)).displayName="DeleteDialog",S=S))||S,S=(t(757),t(436)),B=t.n(S),U=(0,n.a.config)(((S=function(e){Object(M.a)(n,e);var t=Object(k.a)(n);function n(){return Object(_.a)(this,n),t.apply(this,arguments)}return Object(b.a)(n,[{key:"render",value:function(){var e=this.props,t=e.data,t=void 0===t?{}:t,n=e.height,e=e.locale,a=void 0===e?{}:e;return x.a.createElement("div",null,"notice"===t.modeType?x.a.createElement("div",{"data-spm-click":"gostr=/aliyun;locaid=notice"},x.a.createElement(B.a,{style:{marginBottom:1\n com.alibaba.nacos\n nacos-client\n ${latest.version}\n \n*/\npackage com.alibaba.nacos.example;\n\nimport java.util.Properties;\n\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.alibaba.nacos.api.naming.NamingFactory;\nimport com.alibaba.nacos.api.naming.NamingService;\nimport com.alibaba.nacos.api.naming.listener.Event;\nimport com.alibaba.nacos.api.naming.listener.EventListener;\nimport com.alibaba.nacos.api.naming.listener.NamingEvent;\n\n/**\n * @author nkorange\n */\npublic class NamingExample {\n\n public static void main(String[] args) throws NacosException {\n\n Properties properties = new Properties();\n properties.setProperty("serverAddr", System.getProperty("serverAddr"));\n properties.setProperty("namespace", System.getProperty("namespace"));\n\n NamingService naming = NamingFactory.createNamingService(properties);\n\n naming.registerInstance("'.concat(this.record.name,'", "11.11.11.11", 8888, "TEST1");\n\n naming.registerInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n System.out.println(naming.getAllInstances("').concat(this.record.name,'"));\n\n naming.deregisterInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n System.out.println(naming.getAllInstances("').concat(this.record.name,'"));\n\n naming.subscribe("').concat(this.record.name,'", new EventListener() {\n @Override\n public void onEvent(Event event) {\n System.out.println(((NamingEvent)event).getServiceName());\n System.out.println(((NamingEvent)event).getInstances());\n }\n });\n }\n}')}},{key:"getSpringCode",value:function(e){return'/* Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-example/nacos-spring-discovery-example\n* pom.xml\n \n com.alibaba.nacos\n nacos-spring-context\n ${latest.version}\n \n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-example/nacos-spring-discovery-example/src/main/java/com/alibaba/nacos/example/spring\npackage com.alibaba.nacos.example.spring;\n\nimport com.alibaba.nacos.api.annotation.NacosProperties;\nimport com.alibaba.nacos.spring.context.annotation.discovery.EnableNacosDiscovery;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\n@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = "127.0.0.1:8848"))\npublic class NacosConfiguration {\n\n}\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-example/nacos-spring-discovery-example/src/main/java/com/alibaba/nacos/example/spring/controller\npackage com.alibaba.nacos.example.spring.controller;\n\nimport com.alibaba.nacos.api.annotation.NacosInjected;\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.alibaba.nacos.api.naming.NamingService;\nimport com.alibaba.nacos.api.naming.pojo.Instance;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport java.util.List;\n\nimport static org.springframework.web.bind.annotation.RequestMethod.GET;\n\n@Controller\n@RequestMapping("discovery")\npublic class DiscoveryController {\n\n @NacosInjected\n private NamingService namingService;\n\n @RequestMapping(value = "/get", method = GET)\n @ResponseBody\n public List get(@RequestParam String serviceName) throws NacosException {\n return namingService.getAllInstances(serviceName);\n }\n}'}},{key:"getSpringBootCode",value:function(e){return'/* Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-boot-example/nacos-spring-boot-discovery-example\n* pom.xml\n \n com.alibaba.boot\n nacos-discovery-spring-boot-starter\n ${latest.version}\n \n*/\n/* Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-boot-example/nacos-spring-boot-discovery-example/src/main/resources\n* application.properties\n nacos.discovery.server-addr=127.0.0.1:8848\n*/\n// Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-boot-example/nacos-spring-boot-discovery-example/src/main/java/com/alibaba/nacos/example/spring/boot/controller\n\npackage com.alibaba.nacos.example.spring.boot.controller;\n\nimport com.alibaba.nacos.api.annotation.NacosInjected;\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.alibaba.nacos.api.naming.NamingService;\nimport com.alibaba.nacos.api.naming.pojo.Instance;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport java.util.List;\n\nimport static org.springframework.web.bind.annotation.RequestMethod.GET;\n\n@Controller\n@RequestMapping("discovery")\npublic class DiscoveryController {\n\n @NacosInjected\n private NamingService namingService;\n\n @RequestMapping(value = "/get", method = GET)\n @ResponseBody\n public List get(@RequestParam String serviceName) throws NacosException {\n return namingService.getAllInstances(serviceName);\n }\n}'}},{key:"getSpringCloudCode",value:function(e){return"/* Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/\n* pom.xml\n \n org.springframework.cloud\n spring-cloud-starter-alibaba-nacos-discovery\n ${latest.version}\n \n*/\n\n// nacos-spring-cloud-provider-example\n\n/* Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-provider-example/src/main/resources\n* application.properties\nserver.port=18080\nspring.application.name=".concat(this.record.name,'\nspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848\n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-provider-example/src/main/java/com/alibaba/nacos/example/spring/cloud\npackage com.alibaba.nacos.example.spring.cloud;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @author xiaojing\n */\n@SpringBootApplication\n@EnableDiscoveryClient\npublic class NacosProviderApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(NacosProviderApplication.class, args);\n}\n\n @RestController\n class EchoController {\n @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)\n public String echo(@PathVariable String string) {\n return "Hello Nacos Discovery " + string;\n }\n }\n}\n\n// nacos-spring-cloud-consumer-example\n\n/* Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-consumer-example/src/main/resources\n* application.properties\nspring.application.name=micro-service-oauth2\nspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848\n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-consumer-example/src/main/java/com/alibaba/nacos/example/spring/cloud\npackage com.alibaba.nacos.example.spring.cloud;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * @author xiaojing\n */\n@SpringBootApplication\n@EnableDiscoveryClient\npublic class NacosConsumerApplication {\n\n @LoadBalanced\n @Bean\n public RestTemplate restTemplate() {\n return new RestTemplate();\n }\n\n public static void main(String[] args) {\n SpringApplication.run(NacosConsumerApplication.class, args);\n }\n\n @RestController\n public class TestController {\n\n private final RestTemplate restTemplate;\n\n @Autowired\n public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}\n\n @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)\n public String echo(@PathVariable String str) {\n return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);\n }\n }\n}')}},{key:"getNodejsCode",value:function(e){return"TODO"}},{key:"getCppCode",value:function(e){return"TODO"}},{key:"getShellCode",value:function(e){return"TODO"}},{key:"getPythonCode",value:function(e){return'/*\n* Demo for Nacos\n*/\nimport json\nimport socket\n\nimport nacos\n\n\ndef get_host_ip():\n res = socket.gethostbyname(socket.gethostname())\n return res\n\n\ndef load_config(content):\n _config = json.loads(content)\n return _config\n\n\ndef nacos_config_callback(args):\n content = args[\'raw_content\']\n load_config(content)\n\n\nclass NacosClient:\n service_name = None\n service_port = None\n service_group = None\n\n def __init__(self, server_endpoint, namespace_id, username=None, password=None):\n self.client = nacos.NacosClient(server_endpoint,\n namespace=namespace_id,\n username=username,\n password=password)\n self.endpoint = server_endpoint\n self.service_ip = get_host_ip()\n\n def register(self):\n self.client.add_naming_instance(self.service_name,\n self.service_ip,\n self.service_port,\n group_name=self.service_group)\n\n def modify(self, service_name, service_ip=None, service_port=None):\n self.client.modify_naming_instance(service_name,\n service_ip if service_ip else self.service_ip,\n service_port if service_port else self.service_port)\n\n def unregister(self):\n self.client.remove_naming_instance(self.service_name,\n self.service_ip,\n self.service_port)\n\n def set_service(self, service_name, service_ip, service_port, service_group):\n self.service_name = service_name\n self.service_ip = service_ip\n self.service_port = service_port\n self.service_group = service_group\n\n async def beat_callback(self):\n self.client.send_heartbeat(self.service_name,\n self.service_ip,\n self.service_port)\n\n def load_conf(self, data_id, group):\n return self.client.get_config(data_id=data_id, group=group, no_snapshot=True)\n\n def add_conf_watcher(self, data_id, group, callback):\n self.client.add_config_watcher(data_id=data_id, group=group, cb=callback)\n\n\nif __name__ == \'__main__\':\n nacos_config = {\n "nacos_data_id":"test",\n "nacos_server_ip":"127.0.0.1",\n "nacos_namespace":"public",\n "nacos_groupName":"DEFAULT_GROUP",\n "nacos_user":"nacos",\n "nacos_password":"1234567"\n }\n nacos_data_id = nacos_config["nacos_data_id"]\n SERVER_ADDRESSES = nacos_config["nacos_server_ip"]\n NAMESPACE = nacos_config["nacos_namespace"]\n groupName = nacos_config["nacos_groupName"]\n user = nacos_config["nacos_user"]\n password = nacos_config["nacos_password"]\n # todo 将另一个路由对象(通常定义在其他模块或文件中)合并到主应用(app)中。\n # app.include_router(custom_api.router, tags=[\'test\'])\n service_ip = get_host_ip()\n client = NacosClient(SERVER_ADDRESSES, NAMESPACE, user, password)\n client.add_conf_watcher(nacos_data_id, groupName, nacos_config_callback)\n\n # 启动时,强制同步一次配置\n data_stream = client.load_conf(nacos_data_id, groupName)\n json_config = load_config(data_stream)\n #设定服务\n client.set_service(json_config["service_name"], json_config.get("service_ip", service_ip), service_port, groupName)\n #注册服务\n client.register()\n #下线服务\n client.unregister()\n'}},{key:"getCSharpCode",value:function(e){return'/* Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/\nDemo for Basic Nacos Opreation\nApp.csproj\n\n\n \n\n*/\n\nusing Microsoft.Extensions.DependencyInjection;\nusing Nacos.V2;\nusing Nacos.V2.DependencyInjection;\nusing System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\nclass Program\n{\n static async Task Main(string[] args)\n {\n IServiceCollection services = new ServiceCollection();\n\n services.AddNacosV2Naming(x =>\n {\n x.ServerAddresses = new List { "http://localhost:8848/" };\n x.Namespace = "cs-test";\n\n // swich to use http or rpc\n x.NamingUseRpc = true;\n });\n\n IServiceProvider serviceProvider = services.BuildServiceProvider();\n var namingSvc = serviceProvider.GetService();\n\n await namingSvc.RegisterInstance("'.concat(this.record.name,'", "11.11.11.11", 8888, "TEST1");\n\n await namingSvc.RegisterInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(await namingSvc.GetAllInstances("').concat(this.record.name,'")));\n\n await namingSvc.DeregisterInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n var listener = new EventListener();\n\n await namingSvc.Subscribe("').concat(this.record.name,'", listener);\n }\n\n internal class EventListener : IEventListener\n {\n public Task OnEvent(IEvent @event)\n {\n Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(@event));\n return Task.CompletedTask;\n }\n }\n}\n\n/* Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/\nDemo for ASP.NET Core Integration\nApp.csproj\n\n\n \n\n*/\n\n/* Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/blob/dev/samples/App1/appsettings.json\n* appsettings.json\n{\n "nacos": {\n "ServerAddresses": [ "http://localhost:8848" ],\n "DefaultTimeOut": 15000,\n "Namespace": "cs",\n "ServiceName": "App1",\n "GroupName": "DEFAULT_GROUP",\n "ClusterName": "DEFAULT",\n "Port": 0,\n "Weight": 100,\n "RegisterEnabled": true,\n "InstanceEnabled": true,\n "Ephemeral": true,\n "NamingUseRpc": true,\n "NamingLoadCacheAtStart": ""\n }\n}\n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/blob/dev/samples/App1/Startup.cs\nusing Nacos.AspNetCore.V2;\n\npublic class Startup\n{\n public Startup(IConfiguration configuration)\n {\n Configuration = configuration;\n }\n\n public IConfiguration Configuration { get; }\n\n public void ConfigureServices(IServiceCollection services)\n {\n // ....\n services.AddNacosAspNet(Configuration);\n }\n\n public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n {\n // ....\n }\n}\n ')}},{key:"openDialog",value:function(e){var t=this;this.setState({dialogvisible:!0}),this.record=e,setTimeout(function(){t.getData()})}},{key:"closeDialog",value:function(){this.setState({dialogvisible:!1})}},{key:"createCodeMirror",value:function(e,t){var n=this.refs.codepreview;n&&(n.innerHTML="",this.cm=window.CodeMirror(n,{value:t,mode:e,height:400,width:500,lineNumbers:!0,theme:"xq-light",lint:!0,tabMode:"indent",autoMatchParens:!0,textWrapping:!0,gutters:["CodeMirror-lint-markers"],extraKeys:{F1:function(e){e.setOption("fullScreen",!e.getOption("fullScreen"))},Esc:function(e){e.getOption("fullScreen")&&e.setOption("fullScreen",!1)}}}),this.cm.setSize("auto","490px"))}},{key:"changeTab",value:function(e,t){var n=this;setTimeout(function(){n[e]=!0,n.createCodeMirror("text/javascript",t)})}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e;return O.a.createElement("div",null,O.a.createElement(o.a,{title:e.sampleCode,style:{width:"80%"},visible:this.state.dialogvisible,footer:O.a.createElement("div",null),onClose:this.closeDialog.bind(this)},O.a.createElement("div",{style:{height:500}},O.a.createElement(h.a,{tip:e.loading,style:{width:"100%"},visible:this.state.loading},O.a.createElement(m.a,{shape:"text",style:{height:40,paddingBottom:10}},O.a.createElement(g,{title:"Java",key:0,onClick:this.changeTab.bind(this,"commoneditor1",this.defaultCode)}),O.a.createElement(g,{title:"Spring",key:1,onClick:this.changeTab.bind(this,"commoneditor1",this.springCode)}),O.a.createElement(g,{title:"Spring Boot",key:2,onClick:this.changeTab.bind(this,"commoneditor2",this.sprigbootCode)}),O.a.createElement(g,{title:"Spring Cloud",key:21,onClick:this.changeTab.bind(this,"commoneditor21",this.sprigcloudCode)}),O.a.createElement(g,{title:"Node.js",key:3,onClick:this.changeTab.bind(this,"commoneditor3",this.nodejsCode)}),O.a.createElement(g,{title:"C++",key:4,onClick:this.changeTab.bind(this,"commoneditor4",this.cppCode)}),O.a.createElement(g,{title:"Shell",key:5,onClick:this.changeTab.bind(this,"commoneditor5",this.shellCode)}),O.a.createElement(g,{title:"Python",key:6,onClick:this.changeTab.bind(this,"commoneditor6",this.pythonCode)}),O.a.createElement(g,{title:"C#",key:7,onClick:this.changeTab.bind(this,"commoneditor7",this.csharpCode)})),O.a.createElement("div",{ref:"codepreview"})))))}}]),n}(O.a.Component)).displayName="ShowServiceCodeing",f=f))||f,Y=t(51),I=t(146),A=(t(778),t(22)),R=L.a.Item,H=d.a.Row,F=d.a.Col,z=T.a.Column,d=(0,n.a.config)(((f=function(e){Object(u.a)(n,e);var t=Object(c.a)(n);function n(e){var a;return Object(s.a)(this,n),(a=t.call(this,e)).getQueryLater=function(){setTimeout(function(){return a.queryServiceList()})},a.showcode=function(){setTimeout(function(){return a.queryServiceList()})},a.setNowNameSpace=function(e,t,n){return a.setState({nowNamespaceName:e,nowNamespaceId:t,nowNamespaceDesc:n})},a.rowColor=function(e){return{className:e.healthyInstanceCount?"":"row-bg-red"}},a.editServiceDialog=O.a.createRef(),a.showcode=O.a.createRef(),a.state={loading:!1,total:0,pageSize:10,currentPage:1,dataSource:[],search:{serviceName:Object(p.b)("serviceNameParam")||"",groupName:Object(p.b)("groupNameParam")||""},hasIpCount:!("false"===localStorage.getItem("hasIpCount"))},a.field=new i.a(Object(l.a)(a)),a}return Object(a.a)(n,[{key:"openLoading",value:function(){this.setState({loading:!0})}},{key:"closeLoading",value:function(){this.setState({loading:!1})}},{key:"openEditServiceDialog",value:function(){try{this.editServiceDialog.current.getInstance().show(this.state.service)}catch(e){}}},{key:"queryServiceList",value:function(){var n=this,e=this.state,t=e.currentPage,a=e.pageSize,r=e.search,o=e.withInstances,o=void 0!==o&&o,e=e.hasIpCount,e=["hasIpCount=".concat(e),"withInstances=".concat(o),"pageNo=".concat(t),"pageSize=".concat(a),"serviceNameParam=".concat(r.serviceName),"groupNameParam=".concat(r.groupName)];Object(p.f)({serviceNameParam:r.serviceName,groupNameParam:r.groupName}),this.openLoading(),Object(p.e)({url:"v1/ns/catalog/services?".concat(e.join("&")),success:function(){var e=0o&&v.a.createElement(u.a,{className:"users-pagination",current:i,total:n.totalCount,pageSize:o,onChange:function(e){return t.setState({pageNo:e},function(){return t.getUsers()})}}),v.a.createElement(E,{visible:s,onOk:function(e){return Object(_.c)(e).then(function(e){return t.setState({pageNo:1},function(){return t.getUsers()}),e})},onCancel:function(){return t.colseCreateUser()}}),v.a.createElement(x.a,{visible:l,username:e,onOk:function(e){return Object(_.k)(e).then(function(e){return t.getUsers(),e})},onCancel:function(){return t.setState({passwordResetUser:void 0,passwordResetUserVisible:!1})}}))}}]),n}(v.a.Component)).displayName="UserManagement",n=o))||n)||n;t.a=r},function(e,t,n){"use strict";n(67);var a=n(46),l=n.n(a),a=(n(35),n(17)),u=n.n(a),c=n(32),a=(n(66),n(21)),d=n.n(a),a=(n(34),n(20)),f=n.n(a),a=(n(93),n(55)),p=n.n(a),a=(n(38),n(2)),h=n.n(a),a=(n(37),n(10)),m=n.n(a),i=n(12),s=n(13),g=n(23),y=n(15),v=n(14),a=(n(27),n(6)),a=n.n(a),r=n(0),_=n.n(r),r=n(30),b=n(45),o=n(86),w=n(53),M=(n(49),n(28)),k=n.n(M),M=(n(59),n(29)),S=n.n(M),E=h.a.Item,x=S.a.Option,C={labelCol:{fixedSpan:4},wrapperCol:{span:19}},T=Object(r.b)(function(e){return{namespaces:e.namespace.namespaces}},{getNamespaces:o.b,searchRoles:b.l})(M=(0,a.a.config)(((M=function(e){Object(y.a)(o,e);var r=Object(v.a)(o);function o(){var t;Object(i.a)(this,o);for(var e=arguments.length,n=new Array(e),a=0;ai&&_.a.createElement(l.a,{className:"users-pagination",current:s,total:t.totalCount,pageSize:i,onChange:function(e){return a.setState({pageNo:e},function(){return a.getPermissions()})}}),_.a.createElement(T,{visible:n,onOk:function(e){return Object(b.a)(e).then(function(e){return a.setState({pageNo:1},function(){return a.getPermissions()}),e})},onCancel:function(){return a.colseCreatePermission()}}))}}]),n}(_.a.Component)).displayName="PermissionsManagement",n=M))||n)||n);t.a=r},function(e,t,n){"use strict";n(67);var a=n(46),l=n.n(a),a=(n(35),n(17)),u=n.n(a),a=(n(66),n(21)),c=n.n(a),a=(n(34),n(20)),d=n.n(a),a=(n(93),n(55)),f=n.n(a),a=(n(38),n(2)),p=n.n(a),a=(n(37),n(10)),h=n.n(a),i=n(12),s=n(13),m=n(23),g=n(15),y=n(14),a=(n(27),n(6)),a=n.n(a),r=n(0),v=n.n(r),r=n(30),_=n(45),b=n(53),o=(n(59),n(29)),w=n.n(o),o=(n(49),n(28)),M=n.n(o),k=p.a.Item,S={labelCol:{fixedSpan:4},wrapperCol:{span:19}},E=Object(r.b)(function(e){return{users:e.authority.users}},{searchUsers:_.m})(o=(0,a.a.config)(((o=function(e){Object(g.a)(o,e);var r=Object(y.a)(o);function o(){var t;Object(i.a)(this,o);for(var e=arguments.length,n=new Array(e),a=0;ao&&v.a.createElement(l.a,{className:"users-pagination",current:i,total:t.totalCount,pageSize:o,onChange:function(e){return a.setState({pageNo:e},function(){return a.getRoles()})}}),v.a.createElement(E,{visible:s,onOk:function(e){return Object(_.b)(e).then(function(e){return a.getRoles(),e})},onCancel:function(){return a.colseCreateRole()}}))}}]),n}(v.a.Component)).displayName="RolesManagement",n=o))||n)||n);t.a=r},function(e,t,n){"use strict";n(35);function l(e){var t=void 0===(t=localStorage.token)?"{}":t,t=(Object(_.c)(t)&&JSON.parse(t)||{}).globalAdmin,n=[];return"naming"===e?n.push(b):"config"===e?n.push(w):n.push(w,b),t&&n.push(M),n.push(k),n.push(S),n.push(E),n.filter(function(e){return e})}var a=n(17),u=n.n(a),a=(n(47),n(25)),c=n.n(a),a=(n(43),n(26)),d=n.n(a),r=n(12),o=n(13),i=n(15),s=n(14),a=(n(27),n(6)),a=n.n(a),f=n(19),p=(n(84),n(50)),h=n.n(p),p=n(0),m=n.n(p),p=n(39),g=n(30),y=n(108),v=n(52),_=n(48),b={key:"serviceManagementVirtual",children:[{key:"serviceManagement",url:"/serviceManagement"},{key:"subscriberList",url:"/subscriberList"}]},w={key:"configurationManagementVirtual",children:[{key:"configurationManagement",url:"/configurationManagement"},{key:"historyRollback",url:"/historyRollback"},{key:"listeningToQuery",url:"/listeningToQuery"}]},M={key:"authorityControl",children:[{key:"userList",url:"/userManagement"},{key:"roleManagement",url:"/rolesManagement"},{key:"privilegeManagement",url:"/permissionsManagement"}]},k={key:"namespace",url:"/namespace"},S={key:"clusterManagementVirtual",children:[{key:"clusterManagement",url:"/clusterManagement"}]},E={key:"settingCenter",url:"/settingCenter"},x=(n(386),h.a.SubMenu),C=h.a.Item,p=(n=Object(g.b)(function(e){return Object(f.a)(Object(f.a)({},e.locale),e.base)},{getState:v.e,getNotice:v.d,getGuide:v.c}),g=a.a.config,Object(p.g)(a=n(a=g(((v=function(e){Object(i.a)(n,e);var t=Object(s.a)(n);function n(e){return Object(r.a)(this,n),(e=t.call(this,e)).state={visible:!0},e}return Object(o.a)(n,[{key:"componentDidMount",value:function(){this.props.getState(),this.props.getNotice(),this.props.getGuide()}},{key:"goBack",value:function(){this.props.history.goBack()}},{key:"navTo",value:function(e){var t=this.props.location.search,t=new URLSearchParams(t);t.set("namespace",window.nownamespace),t.set("namespaceShowName",window.namespaceShowName),this.props.history.push([e,"?",t.toString()].join(""))}},{key:"isCurrentPath",value:function(e){return e===this.props.location.pathname?"current-path next-selected":void 0}},{key:"defaultOpenKeys",value:function(){for(var t=this,e=l(this.props.functionMode),n=0,a=e.length;nthis.state.pageSize&&S.a.createElement("div",{style:{marginTop:10,textAlign:"right"}},S.a.createElement(v.a,{current:this.state.pageNo,total:a,pageSize:this.state.pageSize,onChange:function(e){return t.setState({pageNo:e},function(){return t.querySubscriberList()})}}))))}}]),n}(S.a.Component)).displayName="SubscriberList",d=n))||d)||d;t.a=f},function(e,t,n){"use strict";n(54);var a=n(36),c=n.n(a),a=(n(67),n(46)),d=n.n(a),a=(n(179),n(78)),f=n.n(a),a=(n(37),n(10)),p=n.n(a),a=(n(34),n(20)),h=n.n(a),a=(n(35),n(17)),r=n.n(a),a=(n(47),n(25)),o=n.n(a),a=(n(49),n(28)),i=n.n(a),s=n(12),l=n(13),u=n(23),m=n(15),g=n(14),a=(n(27),n(6)),a=n.n(a),y=(n(421),n(123)),v=n.n(y),y=(n(66),n(21)),_=n.n(y),y=(n(69),n(40)),y=n.n(y),b=(n(38),n(2)),w=n.n(b),b=n(0),M=n.n(b),k=n(1),b=n(144),S=n.n(b),E=n(51),x=(n(781),w.a.Item),C=y.a.Row,T=y.a.Col,L=_.a.Column,O=v.a.Panel,y=(0,a.a.config)(((b=function(e){Object(m.a)(a,e);var t=Object(g.a)(a);function a(e){var n;return Object(s.a)(this,a),(n=t.call(this,e)).getQueryLater=function(){setTimeout(function(){return n.queryClusterStateList()})},n.setNowNameSpace=function(e,t){return n.setState({nowNamespaceName:e,nowNamespaceId:t})},n.rowColor=function(e){return{className:(e.voteFor,"")}},n.state={loading:!1,total:0,pageSize:10,currentPage:1,keyword:"",dataSource:[]},n.field=new i.a(Object(u.a)(n)),n}return Object(l.a)(a,[{key:"componentDidMount",value:function(){this.getQueryLater()}},{key:"openLoading",value:function(){this.setState({loading:!0})}},{key:"closeLoading",value:function(){this.setState({loading:!1})}},{key:"queryClusterStateList",value:function(){var n=this,e=this.state,t=e.currentPage,a=e.pageSize,r=e.keyword,e=e.withInstances,e=["withInstances=".concat(void 0!==e&&e),"pageNo=".concat(t),"pageSize=".concat(a),"keyword=".concat(r)];Object(k.e)({url:"v1/core/cluster/nodes?".concat(e.join("&")),beforeSend:function(){return n.openLoading()},success:function(){var e=0this.state.pageSize&&M.a.createElement("div",{style:{marginTop:10,textAlign:"right"}},M.a.createElement(d.a,{current:this.state.currentPage,total:this.state.total,pageSize:this.state.pageSize,onChange:function(e){return t.setState({currentPage:e},function(){return t.queryClusterStateList()})}}))))}}]),a}(M.a.Component)).displayName="ClusterNodeList",n=b))||n;t.a=y},function(e,t,n){"use strict";n(34);var a=n(20),i=n.n(a),s=n(12),l=n(13),u=n(15),c=n(14),a=(n(27),n(6)),a=n.n(a),r=n(19),o=(n(114),n(75)),o=n.n(o),d=n(0),f=n.n(d),p=(n(784),n(51)),d=n(87),h=n(148),m=n(149),g=n(30),y=n(22),v=o.a.Group,g=Object(g.b)(function(e){return Object(r.a)({},e.locale)},{changeLanguage:d.a,changeTheme:h.a,changeNameShow:m.a})(o=(0,a.a.config)(((n=function(e){Object(u.a)(o,e);var r=Object(c.a)(o);function o(e){Object(s.a)(this,o),e=r.call(this,e);var t=localStorage.getItem(y.p),n=localStorage.getItem(y.j),a=localStorage.getItem(y.g);return e.state={theme:"dark"===t?"dark":"light",language:"en-US"===a?"en-US":"zh-CN",nameShow:"select"===n?"select":"label"},e}return Object(l.a)(o,[{key:"newTheme",value:function(e){this.setState({theme:e})}},{key:"newLanguage",value:function(e){this.setState({language:e})}},{key:"newNameShow",value:function(e){this.setState({nameShow:e})}},{key:"submit",value:function(){var e=this.props,t=e.changeLanguage,n=e.changeTheme,e=e.changeNameShow,a=this.state.language,r=this.state.theme,o=this.state.nameShow;t(a),n(r),e(o)}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e,t=[{value:"light",label:e.settingLight},{value:"dark",label:e.settingDark}],n=[{value:"select",label:e.settingShowSelect},{value:"label",label:e.settingShowLabel}];return f.a.createElement(f.a.Fragment,null,f.a.createElement(p.a,{title:e.settingTitle}),f.a.createElement("div",{className:"setting-box"},f.a.createElement("div",{className:"text-box"},f.a.createElement("div",{className:"setting-checkbox"},f.a.createElement("div",{className:"setting-span"},e.settingTheme),f.a.createElement(v,{dataSource:t,value:this.state.theme,onChange:this.newTheme.bind(this)})),f.a.createElement("div",{className:"setting-checkbox"},f.a.createElement("div",{className:"setting-span"},e.settingLocale),f.a.createElement(v,{dataSource:[{value:"en-US",label:"English"},{value:"zh-CN",label:"中文"}],value:this.state.language,onChange:this.newLanguage.bind(this)})),f.a.createElement("div",{className:"setting-checkbox"},f.a.createElement("div",{className:"setting-span"},e.settingShow),f.a.createElement(v,{dataSource:n,value:this.state.nameShow,onChange:this.newNameShow.bind(this)}))),f.a.createElement(i.a,{type:"primary",onClick:this.submit.bind(this)},e.settingSubmit)))}}]),o}(f.a.Component)).displayName="SettingCenter",o=n))||o)||o;t.a=g},function(e,t,V){"use strict";V.r(t),function(e){V(54);var t=V(36),a=V.n(t),t=(V(27),V(6)),r=V.n(t),o=V(12),i=V(13),s=V(15),l=V(14),n=V(19),t=V(0),u=V.n(t),t=V(24),t=V.n(t),c=V(125),d=V(429),f=V(440),p=V(30),h=V(39),m=V(73),g=(V(477),V(449)),y=V(22),v=V(450),_=V(451),b=V(443),w=V(452),M=V(453),k=V(444),S=V(454),E=V(455),x=V(456),C=V(457),T=V(458),L=V(441),O=V(445),D=V(442),N=V(459),P=V(460),j=V(446),I=V(447),A=V(448),R=V(438),H=V(461),Y=V(439),F=V(87),z=V(52),W=V(148),B=V(149),e=(V(785),e.hot,localStorage.getItem(y.g)||localStorage.setItem(y.g,"zh-CN"===navigator.language?"zh-CN":"en-US"),Object(c.b)(Object(n.a)(Object(n.a)({},Y.a),{},{routing:d.routerReducer}))),Y=Object(c.d)(e,Object(c.c)(Object(c.a)(f.a),window[y.l]?window[y.l]():function(e){return e})),U=[{path:"/",exact:!0,render:function(){return u.a.createElement(h.a,{to:"/welcome"})}},{path:"/welcome",component:R.a},{path:"/namespace",component:b.a},{path:"/newconfig",component:w.a},{path:"/configsync",component:M.a},{path:"/configdetail",component:k.a},{path:"/configeditor",component:S.a},{path:"/historyDetail",component:E.a},{path:"/configRollback",component:x.a},{path:"/historyRollback",component:C.a},{path:"/listeningToQuery",component:T.a},{path:"/configurationManagement",component:L.a},{path:"/serviceManagement",component:O.a},{path:"/serviceDetail",component:D.a},{path:"/subscriberList",component:N.a},{path:"/clusterManagement",component:P.a},{path:"/userManagement",component:j.a},{path:"/rolesManagement",component:A.a},{path:"/permissionsManagement",component:I.a},{path:"/settingCenter",component:H.a}],e=Object(p.b)(function(e){return Object(n.a)(Object(n.a)({},e.locale),e.base)},{changeLanguage:F.a,getState:z.e,changeTheme:W.a,changeNameShow:B.a})(d=function(e){Object(s.a)(n,e);var t=Object(l.a)(n);function n(e){return Object(o.a)(this,n),(e=t.call(this,e)).state={shownotice:"none",noticecontent:"",nacosLoading:{}},e}return Object(i.a)(n,[{key:"componentDidMount",value:function(){this.props.getState();var e=localStorage.getItem(y.g),t=localStorage.getItem(y.p),n=localStorage.getItem(y.j);this.props.changeLanguage(e),this.props.changeTheme(t),this.props.changeNameShow(n)}},{key:"router",get:function(){var e=this.props,t=e.loginPageEnabled,e=e.consoleUiEnable;return u.a.createElement(m.a,null,u.a.createElement(h.d,null,t&&"false"===t?null:u.a.createElement(h.b,{path:"/login",component:v.a}),u.a.createElement(h.b,{path:"/register",component:_.a}),u.a.createElement(g.a,null,e&&"true"===e&&U.map(function(e){return u.a.createElement(h.b,Object.assign({key:e.path},e))}))))}},{key:"render",value:function(){var e=this.props,t=e.locale,e=e.loginPageEnabled;return u.a.createElement(a.a,Object.assign({className:"nacos-loading",shape:"flower",tip:"loading...",visible:!e,fullScreen:!0},this.state.nacosLoading),u.a.createElement(r.a,{locale:t},this.router))}}]),n}(u.a.Component))||d;t.a.render(u.a.createElement(p.a,{store:Y},u.a.createElement(e,null)),document.getElementById("root"))}.call(this,V(463)(e))},function(e,t){e.exports=function(e){var t;return e.webpackPolyfill||((t=Object.create(e)).children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),Object.defineProperty(t,"exports",{enumerable:!0}),t.webpackPolyfill=1),t}},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(I,e,t){"use strict"; +var S=P(706),o=P(707),s=P(708);function n(){return d.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function l(e,t){if(n()=n())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+n().toString(16)+" bytes");return 0|e}function f(e,t){if(d.isBuffer(e))return e.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(e)||e instanceof ArrayBuffer))return e.byteLength;var n=(e="string"!=typeof e?""+e:e).length;if(0===n)return 0;for(var a=!1;;)switch(t){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return L(e).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return O(e).length;default:if(a)return L(e).length;t=(""+t).toLowerCase(),a=!0}}function t(e,t,n){var a,r=!1;if((t=void 0===t||t<0?0:t)>this.length)return"";if((n=void 0===n||n>this.length?this.length:n)<=0)return"";if((n>>>=0)<=(t>>>=0))return"";for(e=e||"utf8";;)switch(e){case"hex":var o=this,i=t,s=n,l=o.length;(!s||s<0||l=e.length){if(r)return-1;n=e.length-1}else if(n<0){if(!r)return-1;n=0}if("string"==typeof t&&(t=d.from(t,a)),d.isBuffer(t))return 0===t.length?-1:m(e,t,n,a,r);if("number"==typeof t)return t&=255,d.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?(r?Uint8Array.prototype.indexOf:Uint8Array.prototype.lastIndexOf).call(e,t,n):m(e,[t],n,a,r);throw new TypeError("val must be string, number or Buffer")}function m(e,t,n,a,r){var o=1,i=e.length,s=t.length;if(void 0!==a&&("ucs2"===(a=String(a).toLowerCase())||"ucs-2"===a||"utf16le"===a||"utf-16le"===a)){if(e.length<2||t.length<2)return-1;i/=o=2,s/=2,n/=2}function l(e,t){return 1===o?e[t]:e.readUInt16BE(t*o)}if(r)for(var u=-1,c=n;c>8,r.push(n%256),r.push(a);return r}(t,e.length-n),e,n,a)}function E(e,t,n){n=Math.min(e.length,n);for(var a=[],r=t;r>>10&1023|55296),c=56320|1023&c),a.push(c),r+=d}var f=a,p=f.length;if(p<=v)return String.fromCharCode.apply(String,f);for(var h="",m=0;mt)&&(e+=" ... "),""},d.prototype.compare=function(e,t,n,a,r){if(!d.isBuffer(e))throw new TypeError("Argument must be a Buffer");if(void 0===n&&(n=e?e.length:0),void 0===a&&(a=0),void 0===r&&(r=this.length),(t=void 0===t?0:t)<0||n>e.length||a<0||r>this.length)throw new RangeError("out of range index");if(r<=a&&n<=t)return 0;if(r<=a)return-1;if(n<=t)return 1;if(this===e)return 0;for(var o=(r>>>=0)-(a>>>=0),i=(n>>>=0)-(t>>>=0),s=Math.min(o,i),l=this.slice(a,r),u=e.slice(t,n),c=0;cthis.length)throw new RangeError("Attempt to write outside buffer bounds");a=a||"utf8";for(var o,i,s,l=!1;;)switch(a){case"hex":var u=this,c=e,d=t,f=n,p=(d=Number(d)||0,u.length-d);if((!f||p<(f=Number(f)))&&(f=p),(p=c.length)%2!=0)throw new TypeError("Invalid hex string");p/2e.length)throw new RangeError("Index out of range")}function w(e,t,n,a){t<0&&(t=65535+t+1);for(var r=0,o=Math.min(e.length-n,2);r>>8*(a?r:1-r)}function M(e,t,n,a){t<0&&(t=4294967295+t+1);for(var r=0,o=Math.min(e.length-n,4);r>>8*(a?r:3-r)&255}function k(e,t,n,a){if(n+a>e.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function x(e,t,n,a,r){return r||k(e,0,n,4),o.write(e,t,n,a,23,4),n+4}function C(e,t,n,a,r){return r||k(e,0,n,8),o.write(e,t,n,a,52,8),n+8}d.prototype.slice=function(e,t){var n=this.length;if((e=~~e)<0?(e+=n)<0&&(e=0):n>>8):w(this,e,t,!0),t+2},d.prototype.writeUInt16BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,2,65535,0),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):w(this,e,t,!1),t+2},d.prototype.writeUInt32LE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,4294967295,0),d.TYPED_ARRAY_SUPPORT?(this[t+3]=e>>>24,this[t+2]=e>>>16,this[t+1]=e>>>8,this[t]=255&e):M(this,e,t,!0),t+4},d.prototype.writeUInt32BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,4294967295,0),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},d.prototype.writeIntLE=function(e,t,n,a){e=+e,t|=0,a||b(this,e,t,n,(a=Math.pow(2,8*n-1))-1,-a);var r=0,o=1,i=0;for(this[t]=255&e;++r>0)-i&255;return t+n},d.prototype.writeIntBE=function(e,t,n,a){e=+e,t|=0,a||b(this,e,t,n,(a=Math.pow(2,8*n-1))-1,-a);var r=n-1,o=1,i=0;for(this[t+r]=255&e;0<=--r&&(o*=256);)e<0&&0===i&&0!==this[t+r+1]&&(i=1),this[t+r]=(e/o>>0)-i&255;return t+n},d.prototype.writeInt8=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,1,127,-128),d.TYPED_ARRAY_SUPPORT||(e=Math.floor(e)),this[t]=255&(e=e<0?255+e+1:e),t+1},d.prototype.writeInt16LE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,2,32767,-32768),d.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8):w(this,e,t,!0),t+2},d.prototype.writeInt16BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,2,32767,-32768),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>8,this[t+1]=255&e):w(this,e,t,!1),t+2},d.prototype.writeInt32LE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,2147483647,-2147483648),d.TYPED_ARRAY_SUPPORT?(this[t]=255&e,this[t+1]=e>>>8,this[t+2]=e>>>16,this[t+3]=e>>>24):M(this,e,t,!0),t+4},d.prototype.writeInt32BE=function(e,t,n){return e=+e,t|=0,n||b(this,e,t,4,2147483647,-2147483648),e<0&&(e=4294967295+e+1),d.TYPED_ARRAY_SUPPORT?(this[t]=e>>>24,this[t+1]=e>>>16,this[t+2]=e>>>8,this[t+3]=255&e):M(this,e,t,!1),t+4},d.prototype.writeFloatLE=function(e,t,n){return x(this,e,t,!0,n)},d.prototype.writeFloatBE=function(e,t,n){return x(this,e,t,!1,n)},d.prototype.writeDoubleLE=function(e,t,n){return C(this,e,t,!0,n)},d.prototype.writeDoubleBE=function(e,t,n){return C(this,e,t,!1,n)},d.prototype.copy=function(e,t,n,a){if(n=n||0,a||0===a||(a=this.length),t>=e.length&&(t=e.length),(a=0=this.length)throw new RangeError("sourceStart out of bounds");if(a<0)throw new RangeError("sourceEnd out of bounds");a>this.length&&(a=this.length);var r,o=(a=e.length-t>>=0,n=void 0===n?this.length:n>>>0,"number"==typeof(e=e||0))for(s=t;s>6|192,63&n|128)}else if(n<65536){if((t-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((t-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function O(e){return S.toByteArray(function(e){var t;if((e=((t=e).trim?t.trim():t.replace(/^\s+|\s+$/g,"")).replace(T,"")).length<2)return"";for(;e.length%4!=0;)e+="=";return e}(e))}function D(e,t,n,a){for(var r=0;r=t.length||r>=e.length);++r)t[r+n]=e[r];return r}}.call(this,P(65))},function(e,t,n){"use strict";var o=n(138);function i(e,t){e.emit("error",t)}e.exports={destroy:function(e,t){var n=this,a=this._readableState&&this._readableState.destroyed,r=this._writableState&&this._writableState.destroyed;return a||r?t?t(e):e&&(this._writableState?this._writableState.errorEmitted||(this._writableState.errorEmitted=!0,o.nextTick(i,this,e)):o.nextTick(i,this,e)):(this._readableState&&(this._readableState.destroyed=!0),this._writableState&&(this._writableState.destroyed=!0),this._destroy(e||null,function(e){!t&&e?n._writableState?n._writableState.errorEmitted||(n._writableState.errorEmitted=!0,o.nextTick(i,n,e)):o.nextTick(i,n,e):t&&t(e)})),this},undestroy:function(){this._readableState&&(this._readableState.destroyed=!1,this._readableState.reading=!1,this._readableState.ended=!1,this._readableState.endEmitted=!1),this._writableState&&(this._writableState.destroyed=!1,this._writableState.ended=!1,this._writableState.ending=!1,this._writableState.finalCalled=!1,this._writableState.prefinished=!1,this._writableState.finished=!1,this._writableState.errorEmitted=!1)}}},function(e,t,n){"use strict";var a=n(139).Buffer,r=a.isEncoding||function(e){switch((e=""+e)&&e.toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":case"raw":return!0;default:return!1}};function o(e){var t=function(e){if(!e)return"utf8";for(var t;;)switch(e){case"utf8":case"utf-8":return"utf8";case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return"utf16le";case"latin1":case"binary":return"latin1";case"base64":case"ascii":case"hex":return e;default:if(t)return;e=(""+e).toLowerCase(),t=!0}}(e);if("string"==typeof t||a.isEncoding!==r&&r(e))return t||e;throw new Error("Unknown encoding: "+e)}function i(e){var t;switch(this.encoding=o(e),this.encoding){case"utf16le":this.text=u,this.end=c,t=4;break;case"utf8":this.fillLast=l,t=4;break;case"base64":this.text=d,this.end=f,t=3;break;default:return this.write=p,void(this.end=h)}this.lastNeed=0,this.lastTotal=0,this.lastChar=a.allocUnsafe(t)}function s(e){return e<=127?0:e>>5==6?2:e>>4==14?3:e>>3==30?4:e>>6==2?-1:-2}function l(e){var t,n=this.lastTotal-this.lastNeed,a=(t=this,128!=(192&(a=e)[0])?(t.lastNeed=0,"�"):1e.slidesToShow&&(n=e.slideWidth*e.slidesToShow*-1,o=e.slideHeight*e.slidesToShow*-1),e.slideCount%e.slidesToScroll!=0&&(t=e.slideIndex+e.slidesToScroll>e.slideCount&&e.slideCount>e.slidesToShow,t=e.rtl?(e.slideIndex>=e.slideCount?e.slideCount-e.slideIndex:e.slideIndex)+e.slidesToScroll>e.slideCount&&e.slideCount>e.slidesToShow:t)&&(o=e.slideIndex>e.slideCount?(n=(e.slidesToShow-(e.slideIndex-e.slideCount))*e.slideWidth*-1,(e.slidesToShow-(e.slideIndex-e.slideCount))*e.slideHeight*-1):(n=e.slideCount%e.slidesToScroll*e.slideWidth*-1,e.slideCount%e.slidesToScroll*e.slideHeight*-1))):e.slideCount%e.slidesToScroll!=0&&e.slideIndex+e.slidesToScroll>e.slideCount&&e.slideCount>e.slidesToShow&&(n=(e.slidesToShow-e.slideCount%e.slidesToScroll)*e.slideWidth),e.centerMode&&(e.infinite?n+=e.slideWidth*Math.floor(e.slidesToShow/2):n=e.slideWidth*Math.floor(e.slidesToShow/2)),a=e.vertical?e.slideIndex*e.slideHeight*-1+o:e.slideIndex*e.slideWidth*-1+n,!0===e.variableWidth&&(t=void 0,a=(r=e.slideCount<=e.slidesToShow||!1===e.infinite?i.default.findDOMNode(e.trackRef).childNodes[e.slideIndex]:(t=e.slideIndex+e.slidesToShow,i.default.findDOMNode(e.trackRef).childNodes[t]))?-1*r.offsetLeft:0,!0===e.centerMode)&&(r=!1===e.infinite?i.default.findDOMNode(e.trackRef).children[e.slideIndex]:i.default.findDOMNode(e.trackRef).children[e.slideIndex+e.slidesToShow+1])?-1*r.offsetLeft+(e.listWidth-r.offsetWidth)/2:a)}},function(e,t,n){"use strict";t.__esModule=!0;var p=u(n(3)),h=u(n(17)),o=u(n(4)),i=u(n(7)),a=u(n(8)),m=u(n(0)),r=u(n(5)),g=u(n(19)),s=u(n(6)),y=u(n(26)),l=n(11);function u(e){return e&&e.__esModule?e:{default:e}}c=m.default.Component,(0,a.default)(d,c),d.prototype.render=function(){var e=this.props,t=e.title,n=e.children,a=e.className,r=e.isExpanded,o=e.disabled,i=e.style,s=e.prefix,l=e.onClick,u=e.id,e=(0,h.default)(e,["title","children","className","isExpanded","disabled","style","prefix","onClick","id"]),a=(0,g.default)(((c={})[s+"collapse-panel"]=!0,c[s+"collapse-panel-hidden"]=!r,c[s+"collapse-panel-expanded"]=r,c[s+"collapse-panel-disabled"]=o,c[a]=a,c)),c=(0,g.default)(((c={})[s+"collapse-panel-icon"]=!0,c[s+"collapse-panel-icon-expanded"]=r,c)),d=u?u+"-heading":void 0,f=u?u+"-region":void 0;return m.default.createElement("div",(0,p.default)({className:a,style:i,id:u},e),m.default.createElement("div",{id:d,className:s+"collapse-panel-title",onClick:l,onKeyDown:this.onKeyDown,tabIndex:"0","aria-disabled":o,"aria-expanded":r,"aria-controls":f,role:"button"},m.default.createElement(y.default,{type:"arrow-right",className:c,"aria-hidden":"true"}),t),m.default.createElement("div",{className:s+"collapse-panel-content",role:"region",id:f},n))},a=n=d,n.propTypes={prefix:r.default.string,style:r.default.object,children:r.default.any,isExpanded:r.default.bool,disabled:r.default.bool,title:r.default.node,className:r.default.string,onClick:r.default.func,id:r.default.string},n.defaultProps={prefix:"next-",isExpanded:!1,onClick:l.func.noop},n.isNextPanel=!0;var c,r=a;function d(){var e,n;(0,o.default)(this,d);for(var t=arguments.length,a=Array(t),r=0;r\n com.alibaba.nacos\n nacos-client\n ${version}\n \n*/\npackage com.alibaba.nacos.example;\n\nimport java.util.Properties;\nimport java.util.concurrent.Executor;\nimport com.alibaba.nacos.api.NacosFactory;\nimport com.alibaba.nacos.api.config.ConfigService;\nimport com.alibaba.nacos.api.config.listener.Listener;\nimport com.alibaba.nacos.api.exception.NacosException;\n\n/**\n * Config service example\n *\n * @author Nacos\n *\n */\npublic class ConfigExample {\n\n\tpublic static void main(String[] args) throws NacosException, InterruptedException {\n\t\tString serverAddr = "localhost";\n\t\tString dataId = "'.concat(e.dataId,'";\n\t\tString group = "').concat(e.group,'";\n\t\tProperties properties = new Properties();\n\t\tproperties.put(PropertyKeyConst.SERVER_ADDR, serverAddr);\n\t\tConfigService configService = NacosFactory.createConfigService(properties);\n\t\tString content = configService.getConfig(dataId, group, 5000);\n\t\tSystem.out.println(content);\n\t\tconfigService.addListener(dataId, group, new Listener() {\n\t\t\t@Override\n\t\t\tpublic void receiveConfigInfo(String configInfo) {\n\t\t\t\tSystem.out.println("receive:" + configInfo);\n\t\t\t}\n\n\t\t\t@Override\n\t\t\tpublic Executor getExecutor() {\n\t\t\t\treturn null;\n\t\t\t}\n\t\t});\n\n\t\tboolean isPublishOk = configService.publishConfig(dataId, group, "content");\n\t\tSystem.out.println(isPublishOk);\n\n\t\tThread.sleep(3000);\n\t\tcontent = configService.getConfig(dataId, group, 5000);\n\t\tSystem.out.println(content);\n\n\t\tboolean isRemoveOk = configService.removeConfig(dataId, group);\n\t\tSystem.out.println(isRemoveOk);\n\t\tThread.sleep(3000);\n\n\t\tcontent = configService.getConfig(dataId, group, 5000);\n\t\tSystem.out.println(content);\n\t\tThread.sleep(300000);\n\n\t}\n}\n')}},{key:"getNodejsCode",value:function(e){return"TODO"}},{key:"getCppCode",value:function(e){return"TODO"}},{key:"getShellCode",value:function(e){return"TODO"}},{key:"getPythonCode",value:function(e){return'/*\n* Demo for Nacos\n*/\nimport json\nimport socket\n\nimport nacos\n\n\ndef get_host_ip():\n res = socket.gethostbyname(socket.gethostname())\n return res\n\n\ndef load_config(content):\n _config = json.loads(content)\n return _config\n\n\ndef nacos_config_callback(args):\n content = args[\'raw_content\']\n load_config(content)\n\n\nclass NacosClient:\n service_name = None\n service_port = None\n service_group = None\n\n def __init__(self, server_endpoint, namespace_id, username=None, password=None):\n self.client = nacos.NacosClient(server_endpoint,\n namespace=namespace_id,\n username=username,\n password=password)\n self.endpoint = server_endpoint\n self.service_ip = get_host_ip()\n\n def register(self):\n self.client.add_naming_instance(self.service_name,\n self.service_ip,\n self.service_port,\n group_name=self.service_group)\n\n def modify(self, service_name, service_ip=None, service_port=None):\n self.client.modify_naming_instance(service_name,\n service_ip if service_ip else self.service_ip,\n service_port if service_port else self.service_port)\n\n def unregister(self):\n self.client.remove_naming_instance(self.service_name,\n self.service_ip,\n self.service_port)\n\n def set_service(self, service_name, service_ip, service_port, service_group):\n self.service_name = service_name\n self.service_ip = service_ip\n self.service_port = service_port\n self.service_group = service_group\n\n async def beat_callback(self):\n self.client.send_heartbeat(self.service_name,\n self.service_ip,\n self.service_port)\n\n def load_conf(self, data_id, group):\n return self.client.get_config(data_id=data_id, group=group, no_snapshot=True)\n\n def add_conf_watcher(self, data_id, group, callback):\n self.client.add_config_watcher(data_id=data_id, group=group, cb=callback)\n\n\nif __name__ == \'__main__\':\n nacos_config = {\n "nacos_data_id":"test",\n "nacos_server_ip":"127.0.0.1",\n "nacos_namespace":"public",\n "nacos_groupName":"DEFAULT_GROUP",\n "nacos_user":"nacos",\n "nacos_password":"1234567"\n }\n nacos_data_id = nacos_config["nacos_data_id"]\n SERVER_ADDRESSES = nacos_config["nacos_server_ip"]\n NAMESPACE = nacos_config["nacos_namespace"]\n groupName = nacos_config["nacos_groupName"]\n user = nacos_config["nacos_user"]\n password = nacos_config["nacos_password"]\n # todo 将另一个路由对象(通常定义在其他模块或文件中)合并到主应用(app)中。\n # app.include_router(custom_api.router, tags=[\'test\'])\n service_ip = get_host_ip()\n client = NacosClient(SERVER_ADDRESSES, NAMESPACE, user, password)\n client.add_conf_watcher(nacos_data_id, groupName, nacos_config_callback)\n\n # 启动时,强制同步一次配置\n data_stream = client.load_conf(nacos_data_id, groupName)\n json_config = load_config(data_stream)\n'}},{key:"getCSharpCode",value:function(e){return'/*\nDemo for Basic Nacos Opreation\nApp.csproj\n\n\n \n\n*/\n\nusing Microsoft.Extensions.DependencyInjection;\nusing Nacos.V2;\nusing Nacos.V2.DependencyInjection;\nusing System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\nclass Program\n{\n static async Task Main(string[] args)\n {\n string serverAddr = "http://localhost:8848";\n string dataId = "'.concat(e.dataId,'";\n string group = "').concat(e.group,'";\n\n IServiceCollection services = new ServiceCollection();\n\n services.AddNacosV2Config(x =>\n {\n x.ServerAddresses = new List { serverAddr };\n x.Namespace = "cs-test";\n\n // swich to use http or rpc\n x.ConfigUseRpc = true;\n });\n\n IServiceProvider serviceProvider = services.BuildServiceProvider();\n var configSvc = serviceProvider.GetService();\n\n var content = await configSvc.GetConfig(dataId, group, 3000);\n Console.WriteLine(content);\n\n var listener = new ConfigListener();\n\n await configSvc.AddListener(dataId, group, listener);\n\n var isPublishOk = await configSvc.PublishConfig(dataId, group, "content");\n Console.WriteLine(isPublishOk);\n\n await Task.Delay(3000);\n content = await configSvc.GetConfig(dataId, group, 5000);\n Console.WriteLine(content);\n\n var isRemoveOk = await configSvc.RemoveConfig(dataId, group);\n Console.WriteLine(isRemoveOk);\n await Task.Delay(3000);\n\n content = await configSvc.GetConfig(dataId, group, 5000);\n Console.WriteLine(content);\n await Task.Delay(300000);\n }\n\n internal class ConfigListener : IListener\n {\n public void ReceiveConfigInfo(string configInfo)\n {\n Console.WriteLine("receive:" + configInfo);\n }\n }\n}\n\n/*\nRefer to document: https://github.com/nacos-group/nacos-sdk-csharp/tree/dev/samples/MsConfigApp\nDemo for ASP.NET Core Integration\nMsConfigApp.csproj\n\n\n \n\n*/\n\nusing Microsoft.AspNetCore.Hosting;\nusing Microsoft.Extensions.Configuration;\nusing Microsoft.Extensions.Hosting;\nusing Serilog;\nusing Serilog.Events;\n\npublic class Program\n{\n public static void Main(string[] args)\n {\n Log.Logger = new LoggerConfiguration()\n .Enrich.FromLogContext()\n .MinimumLevel.Override("Microsoft", LogEventLevel.Warning)\n .MinimumLevel.Override("System", LogEventLevel.Warning)\n .MinimumLevel.Debug()\n .WriteTo.Console()\n .CreateLogger();\n\n try\n {\n Log.ForContext().Information("Application starting...");\n CreateHostBuilder(args, Log.Logger).Build().Run();\n }\n catch (System.Exception ex)\n {\n Log.ForContext().Fatal(ex, "Application start-up failed!!");\n }\n finally\n {\n Log.CloseAndFlush();\n }\n }\n\n public static IHostBuilder CreateHostBuilder(string[] args, Serilog.ILogger logger) =>\n Host.CreateDefaultBuilder(args)\n .ConfigureAppConfiguration((context, builder) =>\n {\n var c = builder.Build();\n builder.AddNacosV2Configuration(c.GetSection("NacosConfig"), logAction: x => x.AddSerilog(logger));\n })\n .ConfigureWebHostDefaults(webBuilder =>\n {\n webBuilder.UseStartup().UseUrls("http://*:8787");\n })\n .UseSerilog();\n}\n ')}},{key:"openDialog",value:function(e){var t=this;this.setState({dialogvisible:!0}),this.record=e,setTimeout(function(){t.getData()})}},{key:"closeDialog",value:function(){this.setState({dialogvisible:!1})}},{key:"createCodeMirror",value:function(e,t){var n=this.refs.codepreview;n&&(n.innerHTML="",this.cm=window.CodeMirror(n,{value:t,mode:e,height:400,width:500,lineNumbers:!0,theme:"xq-light",lint:!0,tabMode:"indent",autoMatchParens:!0,textWrapping:!0,gutters:["CodeMirror-lint-markers"],extraKeys:{F1:function(e){e.setOption("fullScreen",!e.getOption("fullScreen"))},Esc:function(e){e.getOption("fullScreen")&&e.setOption("fullScreen",!1)}}}))}},{key:"changeTab",value:function(e,t){var n=this;setTimeout(function(){n[e]=!0,n.createCodeMirror("text/javascript",t)})}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e;return x.a.createElement("div",null,x.a.createElement(y.a,{title:e.sampleCode,style:{width:"80%"},visible:this.state.dialogvisible,footer:x.a.createElement("div",null),onClose:this.closeDialog.bind(this)},x.a.createElement("div",{style:{height:500}},x.a.createElement(H.a,{tip:e.loading,style:{width:"100%"},visible:this.state.loading},x.a.createElement(L.a,{shape:"text",style:{height:40,paddingBottom:10}},x.a.createElement(O,{title:"Java",key:1,onClick:this.changeTab.bind(this,"commoneditor1",this.defaultCode)}),x.a.createElement(O,{title:"Spring Boot",key:2,onClick:this.changeTab.bind(this,"commoneditor2",this.sprigboot_code)}),x.a.createElement(O,{title:"Spring Cloud",key:21,onClick:this.changeTab.bind(this,"commoneditor21",this.sprigcloud_code)}),x.a.createElement(O,{title:"Node.js",key:3,onClick:this.changeTab.bind(this,"commoneditor3",this.nodejsCode)}),x.a.createElement(O,{title:"C++",key:4,onClick:this.changeTab.bind(this,"commoneditor4",this.cppCode)}),x.a.createElement(O,{title:"Shell",key:5,onClick:this.changeTab.bind(this,"commoneditor5",this.shellCode)}),x.a.createElement(O,{title:"Python",key:6,onClick:this.changeTab.bind(this,"commoneditor6",this.pythonCode)}),x.a.createElement(O,{title:"C#",key:7,onClick:this.changeTab.bind(this,"commoneditor7",this.csharpCode)})),x.a.createElement("div",{ref:"codepreview"})))))}}]),n}(x.a.Component)).displayName="ShowCodeing",S=S))||S,S=(t(69),t(40)),S=t.n(S),z=(t(756),S.a.Row),D=S.a.Col,W=(0,n.a.config)(((S=function(e){Object(M.a)(n,e);var t=Object(k.a)(n);function n(e){return Object(_.a)(this,n),(e=t.call(this,e)).state={visible:!1,title:"",content:"",isok:!0,dataId:"",group:""},e}return Object(b.a)(n,[{key:"componentDidMount",value:function(){this.initData()}},{key:"initData",value:function(){var e=this.props.locale;this.setState({title:(void 0===e?{}:e).confManagement})}},{key:"openDialog",value:function(e){this.setState({visible:!0,title:e.title,content:e.content,isok:e.isok,dataId:e.dataId,group:e.group,message:e.message})}},{key:"closeDialog",value:function(){this.setState({visible:!1})}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e,t=x.a.createElement("div",{style:{textAlign:"right"}},x.a.createElement(c.a,{type:"primary",onClick:this.closeDialog.bind(this)},e.determine));return x.a.createElement("div",null,x.a.createElement(y.a,{visible:this.state.visible,footer:t,style:{width:555},onCancel:this.closeDialog.bind(this),onClose:this.closeDialog.bind(this),title:e.deletetitle},x.a.createElement("div",null,x.a.createElement(z,null,x.a.createElement(D,{span:"4",style:{paddingTop:16}},x.a.createElement(m.a,{type:"".concat(this.state.isok?"success":"delete","-filling"),style:{color:this.state.isok?"green":"red"},size:"xl"})),x.a.createElement(D,{span:"20"},x.a.createElement("div",null,x.a.createElement("h3",null,this.state.isok?e.deletedSuccessfully:e.deleteFailed),x.a.createElement("p",null,x.a.createElement("span",{style:{color:"#999",marginRight:5}},"Data ID"),x.a.createElement("span",{style:{color:"#c7254e"}},this.state.dataId)),x.a.createElement("p",null,x.a.createElement("span",{style:{color:"#999",marginRight:5}},"Group"),x.a.createElement("span",{style:{color:"#c7254e"}},this.state.group)),this.state.isok?"":x.a.createElement("p",{style:{color:"red"}},this.state.message)))))))}}]),n}(x.a.Component)).displayName="DeleteDialog",S=S))||S,S=(t(757),t(436)),B=t.n(S),U=(0,n.a.config)(((S=function(e){Object(M.a)(n,e);var t=Object(k.a)(n);function n(){return Object(_.a)(this,n),t.apply(this,arguments)}return Object(b.a)(n,[{key:"render",value:function(){var e=this.props,t=e.data,t=void 0===t?{}:t,n=e.height,e=e.locale,a=void 0===e?{}:e;return x.a.createElement("div",null,"notice"===t.modeType?x.a.createElement("div",{"data-spm-click":"gostr=/aliyun;locaid=notice"},x.a.createElement(B.a,{style:{marginBottom:1\n com.alibaba.nacos\n nacos-client\n ${latest.version}\n \n*/\npackage com.alibaba.nacos.example;\n\nimport java.util.Properties;\n\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.alibaba.nacos.api.naming.NamingFactory;\nimport com.alibaba.nacos.api.naming.NamingService;\nimport com.alibaba.nacos.api.naming.listener.Event;\nimport com.alibaba.nacos.api.naming.listener.EventListener;\nimport com.alibaba.nacos.api.naming.listener.NamingEvent;\n\n/**\n * @author nkorange\n */\npublic class NamingExample {\n\n public static void main(String[] args) throws NacosException {\n\n Properties properties = new Properties();\n properties.setProperty("serverAddr", System.getProperty("serverAddr"));\n properties.setProperty("namespace", System.getProperty("namespace"));\n\n NamingService naming = NamingFactory.createNamingService(properties);\n\n naming.registerInstance("'.concat(this.record.name,'", "11.11.11.11", 8888, "TEST1");\n\n naming.registerInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n System.out.println(naming.getAllInstances("').concat(this.record.name,'"));\n\n naming.deregisterInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n System.out.println(naming.getAllInstances("').concat(this.record.name,'"));\n\n naming.subscribe("').concat(this.record.name,'", new EventListener() {\n @Override\n public void onEvent(Event event) {\n System.out.println(((NamingEvent)event).getServiceName());\n System.out.println(((NamingEvent)event).getInstances());\n }\n });\n }\n}')}},{key:"getSpringCode",value:function(e){return'/* Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-example/nacos-spring-discovery-example\n* pom.xml\n \n com.alibaba.nacos\n nacos-spring-context\n ${latest.version}\n \n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-example/nacos-spring-discovery-example/src/main/java/com/alibaba/nacos/example/spring\npackage com.alibaba.nacos.example.spring;\n\nimport com.alibaba.nacos.api.annotation.NacosProperties;\nimport com.alibaba.nacos.spring.context.annotation.discovery.EnableNacosDiscovery;\nimport org.springframework.context.annotation.Configuration;\n\n@Configuration\n@EnableNacosDiscovery(globalProperties = @NacosProperties(serverAddr = "127.0.0.1:8848"))\npublic class NacosConfiguration {\n\n}\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-example/nacos-spring-discovery-example/src/main/java/com/alibaba/nacos/example/spring/controller\npackage com.alibaba.nacos.example.spring.controller;\n\nimport com.alibaba.nacos.api.annotation.NacosInjected;\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.alibaba.nacos.api.naming.NamingService;\nimport com.alibaba.nacos.api.naming.pojo.Instance;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport java.util.List;\n\nimport static org.springframework.web.bind.annotation.RequestMethod.GET;\n\n@Controller\n@RequestMapping("discovery")\npublic class DiscoveryController {\n\n @NacosInjected\n private NamingService namingService;\n\n @RequestMapping(value = "/get", method = GET)\n @ResponseBody\n public List get(@RequestParam String serviceName) throws NacosException {\n return namingService.getAllInstances(serviceName);\n }\n}'}},{key:"getSpringBootCode",value:function(e){return'/* Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-boot-example/nacos-spring-boot-discovery-example\n* pom.xml\n \n com.alibaba.boot\n nacos-discovery-spring-boot-starter\n ${latest.version}\n \n*/\n/* Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-boot-example/nacos-spring-boot-discovery-example/src/main/resources\n* application.properties\n nacos.discovery.server-addr=127.0.0.1:8848\n*/\n// Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-boot-example/nacos-spring-boot-discovery-example/src/main/java/com/alibaba/nacos/example/spring/boot/controller\n\npackage com.alibaba.nacos.example.spring.boot.controller;\n\nimport com.alibaba.nacos.api.annotation.NacosInjected;\nimport com.alibaba.nacos.api.exception.NacosException;\nimport com.alibaba.nacos.api.naming.NamingService;\nimport com.alibaba.nacos.api.naming.pojo.Instance;\nimport org.springframework.stereotype.Controller;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestParam;\nimport org.springframework.web.bind.annotation.ResponseBody;\n\nimport java.util.List;\n\nimport static org.springframework.web.bind.annotation.RequestMethod.GET;\n\n@Controller\n@RequestMapping("discovery")\npublic class DiscoveryController {\n\n @NacosInjected\n private NamingService namingService;\n\n @RequestMapping(value = "/get", method = GET)\n @ResponseBody\n public List get(@RequestParam String serviceName) throws NacosException {\n return namingService.getAllInstances(serviceName);\n }\n}'}},{key:"getSpringCloudCode",value:function(e){return"/* Refer to document: https://github.com/nacos-group/nacos-examples/blob/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/\n* pom.xml\n \n org.springframework.cloud\n spring-cloud-starter-alibaba-nacos-discovery\n ${latest.version}\n \n*/\n\n// nacos-spring-cloud-provider-example\n\n/* Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-provider-example/src/main/resources\n* application.properties\nserver.port=18080\nspring.application.name=".concat(this.record.name,'\nspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848\n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-provider-example/src/main/java/com/alibaba/nacos/example/spring/cloud\npackage com.alibaba.nacos.example.spring.cloud;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\n\n/**\n * @author xiaojing\n */\n@SpringBootApplication\n@EnableDiscoveryClient\npublic class NacosProviderApplication {\n\n public static void main(String[] args) {\n SpringApplication.run(NacosProviderApplication.class, args);\n}\n\n @RestController\n class EchoController {\n @RequestMapping(value = "/echo/{string}", method = RequestMethod.GET)\n public String echo(@PathVariable String string) {\n return "Hello Nacos Discovery " + string;\n }\n }\n}\n\n// nacos-spring-cloud-consumer-example\n\n/* Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-consumer-example/src/main/resources\n* application.properties\nspring.application.name=micro-service-oauth2\nspring.cloud.nacos.discovery.server-addr=127.0.0.1:8848\n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-examples/tree/master/nacos-spring-cloud-example/nacos-spring-cloud-discovery-example/nacos-spring-cloud-consumer-example/src/main/java/com/alibaba/nacos/example/spring/cloud\npackage com.alibaba.nacos.example.spring.cloud;\n\nimport org.springframework.beans.factory.annotation.Autowired;\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.autoconfigure.SpringBootApplication;\nimport org.springframework.cloud.client.discovery.EnableDiscoveryClient;\nimport org.springframework.cloud.client.loadbalancer.LoadBalanced;\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.web.bind.annotation.PathVariable;\nimport org.springframework.web.bind.annotation.RequestMapping;\nimport org.springframework.web.bind.annotation.RequestMethod;\nimport org.springframework.web.bind.annotation.RestController;\nimport org.springframework.web.client.RestTemplate;\n\n/**\n * @author xiaojing\n */\n@SpringBootApplication\n@EnableDiscoveryClient\npublic class NacosConsumerApplication {\n\n @LoadBalanced\n @Bean\n public RestTemplate restTemplate() {\n return new RestTemplate();\n }\n\n public static void main(String[] args) {\n SpringApplication.run(NacosConsumerApplication.class, args);\n }\n\n @RestController\n public class TestController {\n\n private final RestTemplate restTemplate;\n\n @Autowired\n public TestController(RestTemplate restTemplate) {this.restTemplate = restTemplate;}\n\n @RequestMapping(value = "/echo/{str}", method = RequestMethod.GET)\n public String echo(@PathVariable String str) {\n return restTemplate.getForObject("http://service-provider/echo/" + str, String.class);\n }\n }\n}')}},{key:"getNodejsCode",value:function(e){return"TODO"}},{key:"getCppCode",value:function(e){return"TODO"}},{key:"getShellCode",value:function(e){return"TODO"}},{key:"getPythonCode",value:function(e){return'/*\n* Demo for Nacos\n*/\nimport json\nimport socket\n\nimport nacos\n\n\ndef get_host_ip():\n res = socket.gethostbyname(socket.gethostname())\n return res\n\n\ndef load_config(content):\n _config = json.loads(content)\n return _config\n\n\ndef nacos_config_callback(args):\n content = args[\'raw_content\']\n load_config(content)\n\n\nclass NacosClient:\n service_name = None\n service_port = None\n service_group = None\n\n def __init__(self, server_endpoint, namespace_id, username=None, password=None):\n self.client = nacos.NacosClient(server_endpoint,\n namespace=namespace_id,\n username=username,\n password=password)\n self.endpoint = server_endpoint\n self.service_ip = get_host_ip()\n\n def register(self):\n self.client.add_naming_instance(self.service_name,\n self.service_ip,\n self.service_port,\n group_name=self.service_group)\n\n def modify(self, service_name, service_ip=None, service_port=None):\n self.client.modify_naming_instance(service_name,\n service_ip if service_ip else self.service_ip,\n service_port if service_port else self.service_port)\n\n def unregister(self):\n self.client.remove_naming_instance(self.service_name,\n self.service_ip,\n self.service_port)\n\n def set_service(self, service_name, service_ip, service_port, service_group):\n self.service_name = service_name\n self.service_ip = service_ip\n self.service_port = service_port\n self.service_group = service_group\n\n async def beat_callback(self):\n self.client.send_heartbeat(self.service_name,\n self.service_ip,\n self.service_port)\n\n def load_conf(self, data_id, group):\n return self.client.get_config(data_id=data_id, group=group, no_snapshot=True)\n\n def add_conf_watcher(self, data_id, group, callback):\n self.client.add_config_watcher(data_id=data_id, group=group, cb=callback)\n\n\nif __name__ == \'__main__\':\n nacos_config = {\n "nacos_data_id":"test",\n "nacos_server_ip":"127.0.0.1",\n "nacos_namespace":"public",\n "nacos_groupName":"DEFAULT_GROUP",\n "nacos_user":"nacos",\n "nacos_password":"1234567"\n }\n nacos_data_id = nacos_config["nacos_data_id"]\n SERVER_ADDRESSES = nacos_config["nacos_server_ip"]\n NAMESPACE = nacos_config["nacos_namespace"]\n groupName = nacos_config["nacos_groupName"]\n user = nacos_config["nacos_user"]\n password = nacos_config["nacos_password"]\n # todo 将另一个路由对象(通常定义在其他模块或文件中)合并到主应用(app)中。\n # app.include_router(custom_api.router, tags=[\'test\'])\n service_ip = get_host_ip()\n client = NacosClient(SERVER_ADDRESSES, NAMESPACE, user, password)\n client.add_conf_watcher(nacos_data_id, groupName, nacos_config_callback)\n\n # 启动时,强制同步一次配置\n data_stream = client.load_conf(nacos_data_id, groupName)\n json_config = load_config(data_stream)\n #设定服务\n client.set_service(json_config["service_name"], json_config.get("service_ip", service_ip), service_port, groupName)\n #注册服务\n client.register()\n #下线服务\n client.unregister()\n'}},{key:"getCSharpCode",value:function(e){return'/* Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/\nDemo for Basic Nacos Opreation\nApp.csproj\n\n\n \n\n*/\n\nusing Microsoft.Extensions.DependencyInjection;\nusing Nacos.V2;\nusing Nacos.V2.DependencyInjection;\nusing System;\nusing System.Collections.Generic;\nusing System.Threading.Tasks;\n\nclass Program\n{\n static async Task Main(string[] args)\n {\n IServiceCollection services = new ServiceCollection();\n\n services.AddNacosV2Naming(x =>\n {\n x.ServerAddresses = new List { "http://localhost:8848/" };\n x.Namespace = "cs-test";\n\n // swich to use http or rpc\n x.NamingUseRpc = true;\n });\n\n IServiceProvider serviceProvider = services.BuildServiceProvider();\n var namingSvc = serviceProvider.GetService();\n\n await namingSvc.RegisterInstance("'.concat(this.record.name,'", "11.11.11.11", 8888, "TEST1");\n\n await namingSvc.RegisterInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(await namingSvc.GetAllInstances("').concat(this.record.name,'")));\n\n await namingSvc.DeregisterInstance("').concat(this.record.name,'", "2.2.2.2", 9999, "DEFAULT");\n\n var listener = new EventListener();\n\n await namingSvc.Subscribe("').concat(this.record.name,'", listener);\n }\n\n internal class EventListener : IEventListener\n {\n public Task OnEvent(IEvent @event)\n {\n Console.WriteLine(Newtonsoft.Json.JsonConvert.SerializeObject(@event));\n return Task.CompletedTask;\n }\n }\n}\n\n/* Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/\nDemo for ASP.NET Core Integration\nApp.csproj\n\n\n \n\n*/\n\n/* Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/blob/dev/samples/App1/appsettings.json\n* appsettings.json\n{\n "nacos": {\n "ServerAddresses": [ "http://localhost:8848" ],\n "DefaultTimeOut": 15000,\n "Namespace": "cs",\n "ServiceName": "App1",\n "GroupName": "DEFAULT_GROUP",\n "ClusterName": "DEFAULT",\n "Port": 0,\n "Weight": 100,\n "RegisterEnabled": true,\n "InstanceEnabled": true,\n "Ephemeral": true,\n "NamingUseRpc": true,\n "NamingLoadCacheAtStart": ""\n }\n}\n*/\n\n// Refer to document: https://github.com/nacos-group/nacos-sdk-csharp/blob/dev/samples/App1/Startup.cs\nusing Nacos.AspNetCore.V2;\n\npublic class Startup\n{\n public Startup(IConfiguration configuration)\n {\n Configuration = configuration;\n }\n\n public IConfiguration Configuration { get; }\n\n public void ConfigureServices(IServiceCollection services)\n {\n // ....\n services.AddNacosAspNet(Configuration);\n }\n\n public void Configure(IApplicationBuilder app, IWebHostEnvironment env)\n {\n // ....\n }\n}\n ')}},{key:"openDialog",value:function(e){var t=this;this.setState({dialogvisible:!0}),this.record=e,setTimeout(function(){t.getData()})}},{key:"closeDialog",value:function(){this.setState({dialogvisible:!1})}},{key:"createCodeMirror",value:function(e,t){var n=this.refs.codepreview;n&&(n.innerHTML="",this.cm=window.CodeMirror(n,{value:t,mode:e,height:400,width:500,lineNumbers:!0,theme:"xq-light",lint:!0,tabMode:"indent",autoMatchParens:!0,textWrapping:!0,gutters:["CodeMirror-lint-markers"],extraKeys:{F1:function(e){e.setOption("fullScreen",!e.getOption("fullScreen"))},Esc:function(e){e.getOption("fullScreen")&&e.setOption("fullScreen",!1)}}}),this.cm.setSize("auto","490px"))}},{key:"changeTab",value:function(e,t){var n=this;setTimeout(function(){n[e]=!0,n.createCodeMirror("text/javascript",t)})}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e;return O.a.createElement("div",null,O.a.createElement(o.a,{title:e.sampleCode,style:{width:"80%"},visible:this.state.dialogvisible,footer:O.a.createElement("div",null),onClose:this.closeDialog.bind(this)},O.a.createElement("div",{style:{height:500}},O.a.createElement(h.a,{tip:e.loading,style:{width:"100%"},visible:this.state.loading},O.a.createElement(m.a,{shape:"text",style:{height:40,paddingBottom:10}},O.a.createElement(g,{title:"Java",key:0,onClick:this.changeTab.bind(this,"commoneditor1",this.defaultCode)}),O.a.createElement(g,{title:"Spring",key:1,onClick:this.changeTab.bind(this,"commoneditor1",this.springCode)}),O.a.createElement(g,{title:"Spring Boot",key:2,onClick:this.changeTab.bind(this,"commoneditor2",this.sprigbootCode)}),O.a.createElement(g,{title:"Spring Cloud",key:21,onClick:this.changeTab.bind(this,"commoneditor21",this.sprigcloudCode)}),O.a.createElement(g,{title:"Node.js",key:3,onClick:this.changeTab.bind(this,"commoneditor3",this.nodejsCode)}),O.a.createElement(g,{title:"C++",key:4,onClick:this.changeTab.bind(this,"commoneditor4",this.cppCode)}),O.a.createElement(g,{title:"Shell",key:5,onClick:this.changeTab.bind(this,"commoneditor5",this.shellCode)}),O.a.createElement(g,{title:"Python",key:6,onClick:this.changeTab.bind(this,"commoneditor6",this.pythonCode)}),O.a.createElement(g,{title:"C#",key:7,onClick:this.changeTab.bind(this,"commoneditor7",this.csharpCode)})),O.a.createElement("div",{ref:"codepreview"})))))}}]),n}(O.a.Component)).displayName="ShowServiceCodeing",f=f))||f,Y=t(51),I=t(146),A=(t(778),t(22)),R=L.a.Item,H=d.a.Row,F=d.a.Col,z=T.a.Column,d=(0,n.a.config)(((f=function(e){Object(u.a)(n,e);var t=Object(c.a)(n);function n(e){var a;return Object(s.a)(this,n),(a=t.call(this,e)).getQueryLater=function(){setTimeout(function(){return a.queryServiceList()})},a.showcode=function(){setTimeout(function(){return a.queryServiceList()})},a.setNowNameSpace=function(e,t,n){return a.setState({nowNamespaceName:e,nowNamespaceId:t,nowNamespaceDesc:n})},a.rowColor=function(e){return{className:e.healthyInstanceCount?"":"row-bg-red"}},a.editServiceDialog=O.a.createRef(),a.showcode=O.a.createRef(),a.state={loading:!1,total:0,pageSize:10,currentPage:1,dataSource:[],search:{serviceName:Object(p.b)("serviceNameParam")||"",groupName:Object(p.b)("groupNameParam")||""},hasIpCount:!("false"===localStorage.getItem("hasIpCount"))},a.field=new i.a(Object(l.a)(a)),a}return Object(a.a)(n,[{key:"openLoading",value:function(){this.setState({loading:!0})}},{key:"closeLoading",value:function(){this.setState({loading:!1})}},{key:"openEditServiceDialog",value:function(){try{this.editServiceDialog.current.getInstance().show(this.state.service)}catch(e){}}},{key:"queryServiceList",value:function(){var n=this,e=this.state,t=e.currentPage,a=e.pageSize,r=e.search,o=e.withInstances,o=void 0!==o&&o,e=e.hasIpCount,e=["hasIpCount=".concat(e),"withInstances=".concat(o),"pageNo=".concat(t),"pageSize=".concat(a),"serviceNameParam=".concat(r.serviceName),"groupNameParam=".concat(r.groupName)];Object(p.f)({serviceNameParam:r.serviceName,groupNameParam:r.groupName}),this.openLoading(),Object(p.e)({url:"v3/console/ns/service/list?".concat(e.join("&")),success:function(e){var e=e.data,e=void 0===e?{}:e,t=e.count,e=e.serviceList;n.setState({dataSource:void 0===e?[]:e,total:void 0===t?0:t,loading:!1})},error:function(){return n.setState({dataSource:[],total:0,currentPage:0,loading:!1})}})}},{key:"showSampleCode",value:function(e){this.showcode.current.getInstance().openDialog(e)}},{key:"querySubscriber",value:function(e){var t=e.name,e=e.groupName,n=this.state.nowNamespaceId;this.props.history.push(Object(D.a)("/subscriberList",{namespace:n,name:t,groupName:e}))}},{key:"handlePageSizeChange",value:function(e){var t=this;this.setState({pageSize:e},function(){return t.queryServiceList()})}},{key:"deleteService",value:function(e){var t=this,n=this.props.locale,n=void 0===n?{}:n,a=n.prompt,n=n.promptDelete;o.a.confirm({title:a,content:n,onOk:function(){Object(p.e)({method:"DELETE",url:"v3/console/ns/service?serviceName=".concat(e.name,"&groupName=").concat(e.groupName),dataType:"json",beforeSend:function(){return t.openLoading()},success:function(e){0!==e.code?r.a.error(e.message||"删除服务失败"):(r.a.success("服务删除成功"),t.queryServiceList())},error:function(e){var t;r.a.error((null==(t=e.data)?void 0:t.responseText)||e.statusText||"请求失败")},complete:function(){return t.closeLoading()}})}})}},{key:"render",value:function(){var a=this,e=this.props.locale,t=void 0===e?{}:e,e=t.pubNoData,n=t.serviceList,r=t.serviceName,o=t.serviceNamePlaceholder,i=t.groupName,s=t.groupNamePlaceholder,l=t.hiddenEmptyService,u=t.query,c=t.create,d=t.operation,f=t.detail,p=t.sampleCode,h=t.deleteAction,m=t.subscriber,g=this.state,y=g.search,v=g.nowNamespaceName,_=g.nowNamespaceId,b=g.nowNamespaceDesc,g=g.hasIpCount,w=this.field,M=w.init,w=w.getValue;return this.init=M,this.getValue=w,O.a.createElement("div",{className:"main-container service-management"},O.a.createElement(Y.a,{title:n,desc:b,namespaceId:_,namespaceName:v,nameSpace:!0}),O.a.createElement(N.a,{setNowNameSpace:this.setNowNameSpace,namespaceCallBack:this.getQueryLater}),O.a.createElement(H,{className:"demo-row",style:{marginBottom:10,padding:0}},O.a.createElement(F,{span:"24"},O.a.createElement(L.a,{inline:!0,field:this.field},O.a.createElement(R,{label:""},O.a.createElement(C.a,{type:"primary",onClick:function(){return a.openEditServiceDialog()}},c)),O.a.createElement(R,{label:r},O.a.createElement(E.a,{placeholder:o,style:{width:200},value:y.serviceName,onChange:function(e){return a.setState({search:Object(x.a)(Object(x.a)({},y),{},{serviceName:e})})},onPressEnter:function(){return a.setState({currentPage:1},function(){return a.queryServiceList()})}})),O.a.createElement(R,{label:i},O.a.createElement(E.a,{placeholder:s,style:{width:200},value:y.groupName,onChange:function(e){return a.setState({search:Object(x.a)(Object(x.a)({},y),{},{groupName:e})})},onPressEnter:function(){return a.setState({currentPage:1},function(){return a.queryServiceList()})}})),O.a.createElement(L.a.Item,{label:"".concat(l)},O.a.createElement(S.a,{checked:g,onChange:function(e){return a.setState({hasIpCount:e,currentPage:1},function(){localStorage.setItem("hasIpCount",e),a.queryServiceList()})}})),O.a.createElement(R,{label:""},O.a.createElement(C.a,{type:"primary",onClick:function(){return a.setState({currentPage:1},function(){return a.queryServiceList()})},style:{marginRight:10}},u))))),O.a.createElement(H,{style:{padding:0}},O.a.createElement(F,{span:"24",style:{padding:0}},O.a.createElement(T.a,{dataSource:this.state.dataSource,locale:{empty:e},rowProps:function(e){return a.rowColor(e)},loading:this.state.loading},O.a.createElement(z,{title:t.columnServiceName,dataIndex:"name"}),O.a.createElement(z,{title:t.groupName,dataIndex:"groupName"}),O.a.createElement(z,{title:t.columnClusterCount,dataIndex:"clusterCount"}),O.a.createElement(z,{title:t.columnIpCount,dataIndex:"ipCount"}),O.a.createElement(z,{title:t.columnHealthyInstanceCount,dataIndex:"healthyInstanceCount"}),O.a.createElement(z,{title:t.columnTriggerFlag,dataIndex:"triggerFlag"}),O.a.createElement(z,{title:d,align:"center",cell:function(e,t,n){return O.a.createElement("div",null,O.a.createElement("a",{onClick:function(){var e=n.name,t=n.groupName;a.props.history.push(Object(D.a)("/serviceDetail",{name:e,groupName:t}))},style:{marginRight:5}},f),O.a.createElement("span",{style:{marginRight:5}},"|"),O.a.createElement("a",{style:{marginRight:5},onClick:function(){return a.showSampleCode(n)}},p),O.a.createElement("span",{style:{marginRight:5}},"|"),O.a.createElement("a",{style:{marginRight:5},onClick:function(){return a.querySubscriber(n)}},m),O.a.createElement("span",{style:{marginRight:5}},"|"),O.a.createElement("a",{onClick:function(){return a.deleteService(n)},style:{marginRight:5}},h))}})))),O.a.createElement("div",{style:{marginTop:10,textAlign:"right"}},O.a.createElement(k.a,{current:this.state.currentPage,pageSizeList:A.f,pageSizePosition:"start",pageSizeSelector:"dropdown",popupProps:{align:"bl tl"},total:this.state.total,pageSize:this.state.pageSize,totalRender:function(e){return O.a.createElement(I.a,{locale:t,total:e})},onPageSizeChange:function(e){return a.handlePageSizeChange(e)},onChange:function(e){return a.setState({currentPage:e},function(){return a.queryServiceList()})}})),O.a.createElement(j,{ref:this.showcode}),O.a.createElement(P.a,{ref:this.editServiceDialog,openLoading:function(){return a.openLoading()},closeLoading:function(){return a.closeLoading()},queryServiceList:function(){return a.setState({currentPage:1},function(){return a.queryServiceList()})}}))}}]),n}(O.a.Component)).displayName="ServiceList",t=f))||t;e.a=d},function(e,t,n){"use strict";n(67);var a=n(47),u=n.n(a),a=(n(35),n(18)),c=n.n(a),a=(n(66),n(21)),d=n.n(a),a=(n(34),n(20)),f=n.n(a),a=(n(93),n(55)),p=n.n(a),a=(n(38),n(2)),h=n.n(a),a=(n(37),n(10)),m=n.n(a),i=n(13),s=n(14),l=n(23),g=n(16),y=n(15),a=(n(27),n(6)),a=n.n(a),r=n(0),v=n.n(r),r=n(30),_=n(45),b=n(53),w=n(32),o=(n(49),n(28)),M=n.n(o),k=(n(168),h.a.Item),S={labelCol:{fixedSpan:4},wrapperCol:{span:19}},E=(0,a.a.config)(((o=function(e){Object(g.a)(o,e);var r=Object(y.a)(o);function o(){var e;Object(i.a)(this,o);for(var t=arguments.length,n=new Array(t),a=0;ao&&v.a.createElement(u.a,{className:"users-pagination",current:i,total:n.totalCount,pageSize:o,onChange:function(e){return t.setState({pageNo:e},function(){return t.getUsers()})}}),v.a.createElement(E,{visible:s,onOk:function(e){return Object(_.d)(e).then(function(e){return t.setState({pageNo:1},function(){return t.getUsers()}),e})},onCancel:function(){return t.colseCreateUser()}}),v.a.createElement(x.a,{visible:l,username:e,onOk:function(e){return Object(_.l)(e).then(function(e){return t.getUsers(),e})},onCancel:function(){return t.setState({passwordResetUser:void 0,passwordResetUserVisible:!1})}}))}}]),n}(v.a.Component)).displayName="UserManagement",n=o))||n)||n;t.a=r},function(e,t,n){"use strict";n(46);var a=n(24),l=n.n(a),a=(n(67),n(47)),u=n.n(a),a=(n(35),n(18)),c=n.n(a),d=n(32),a=(n(66),n(21)),f=n.n(a),a=(n(34),n(20)),p=n.n(a),a=(n(93),n(55)),h=n.n(a),a=(n(38),n(2)),m=n.n(a),a=(n(37),n(10)),g=n.n(a),i=n(13),s=n(14),y=n(23),v=n(16),_=n(15),a=(n(27),n(6)),a=n.n(a),r=n(0),b=n.n(r),r=n(30),w=n(45),o=n(86),M=n(53),k=(n(49),n(28)),S=n.n(k),k=(n(59),n(29)),E=n.n(k),x=m.a.Item,C=E.a.Option,T={labelCol:{fixedSpan:4},wrapperCol:{span:19}},L=Object(r.b)(function(e){return{namespaces:e.namespace.namespaces}},{getNamespaces:o.b,searchRoles:w.m})(k=(0,a.a.config)(((k=function(e){Object(v.a)(o,e);var r=Object(_.a)(o);function o(){var t;Object(i.a)(this,o);for(var e=arguments.length,n=new Array(e),a=0;ai&&b.a.createElement(u.a,{className:"users-pagination",current:s,total:t.totalCount,pageSize:i,onChange:function(e){return a.setState({pageNo:e},function(){return a.getPermissions()})}}),b.a.createElement(L,{visible:n,onOk:function(t){return Object(w.a)(t).then(function(e){e?l.a.error({content:o.checkPermission}):Object(w.b)(t).then(function(e){return a.setState({pageNo:1},function(){return a.getPermissions()}),e})})},onCancel:function(){return a.colseCreatePermission()}}))}}]),n}(b.a.Component)).displayName="PermissionsManagement",n=k))||n)||n);t.a=r},function(e,t,n){"use strict";n(67);var a=n(47),l=n.n(a),a=(n(35),n(18)),u=n.n(a),a=(n(66),n(21)),c=n.n(a),a=(n(34),n(20)),d=n.n(a),a=(n(93),n(55)),f=n.n(a),a=(n(38),n(2)),p=n.n(a),a=(n(37),n(10)),h=n.n(a),i=n(13),s=n(14),m=n(23),g=n(16),y=n(15),a=(n(27),n(6)),a=n.n(a),r=n(0),v=n.n(r),r=n(30),_=n(45),b=n(53),o=(n(59),n(29)),w=n.n(o),o=(n(49),n(28)),M=n.n(o),k=p.a.Item,S={labelCol:{fixedSpan:4},wrapperCol:{span:19}},E=Object(r.b)(function(e){return{users:e.authority.users}},{searchUsers:_.n})(o=(0,a.a.config)(((o=function(e){Object(g.a)(o,e);var r=Object(y.a)(o);function o(){var t;Object(i.a)(this,o);for(var e=arguments.length,n=new Array(e),a=0;ao&&v.a.createElement(l.a,{className:"users-pagination",current:i,total:t.totalCount,pageSize:o,onChange:function(e){return a.setState({pageNo:e},function(){return a.getRoles()})}}),v.a.createElement(E,{visible:s,onOk:function(e){return Object(_.c)(e).then(function(e){return a.getRoles(),e})},onCancel:function(){return a.colseCreateRole()}}))}}]),n}(v.a.Component)).displayName="RolesManagement",n=o))||n)||n);t.a=r},function(e,t,n){"use strict";n(35);function l(e){var t=void 0===(t=localStorage.token)?"{}":t,t=(Object(_.c)(t)&&JSON.parse(t)||{}).globalAdmin,n=[];return"naming"===e?n.push(b):"config"===e?n.push(w):n.push(w,b),t&&n.push(M),n.push(k),n.push(S),n.push(E),n.filter(function(e){return e})}var a=n(18),u=n.n(a),a=(n(46),n(24)),c=n.n(a),a=(n(43),n(26)),d=n.n(a),r=n(13),o=n(14),i=n(16),s=n(15),a=(n(27),n(6)),a=n.n(a),f=n(12),p=(n(84),n(50)),h=n.n(p),p=n(0),m=n.n(p),p=n(39),g=n(30),y=n(108),v=n(52),_=n(48),b={key:"serviceManagementVirtual",children:[{key:"serviceManagement",url:"/serviceManagement"},{key:"subscriberList",url:"/subscriberList"}]},w={key:"configurationManagementVirtual",children:[{key:"configurationManagement",url:"/configurationManagement"},{key:"historyRollback",url:"/historyRollback"},{key:"listeningToQuery",url:"/listeningToQuery"}]},M={key:"authorityControl",children:[{key:"userList",url:"/userManagement"},{key:"roleManagement",url:"/rolesManagement"},{key:"privilegeManagement",url:"/permissionsManagement"}]},k={key:"namespace",url:"/namespace"},S={key:"clusterManagementVirtual",children:[{key:"clusterManagement",url:"/clusterManagement"}]},E={key:"settingCenter",url:"/settingCenter"},x=(n(386),h.a.SubMenu),C=h.a.Item,p=(n=Object(g.b)(function(e){return Object(f.a)(Object(f.a)({},e.locale),e.base)},{getState:v.e,getNotice:v.d,getGuide:v.c}),g=a.a.config,Object(p.g)(a=n(a=g(((v=function(e){Object(i.a)(n,e);var t=Object(s.a)(n);function n(e){return Object(r.a)(this,n),(e=t.call(this,e)).state={visible:!0},e}return Object(o.a)(n,[{key:"componentDidMount",value:function(){this.props.getState(),this.props.getNotice(),this.props.getGuide()}},{key:"goBack",value:function(){this.props.history.goBack()}},{key:"navTo",value:function(e){var t=this.props.location.search,t=new URLSearchParams(t);t.set("namespace",window.nownamespace),t.set("namespaceShowName",window.namespaceShowName),this.props.history.push([e,"?",t.toString()].join(""))}},{key:"isCurrentPath",value:function(e){return e===this.props.location.pathname?"current-path next-selected":void 0}},{key:"defaultOpenKeys",value:function(){for(var t=this,e=l(this.props.functionMode),n=0,a=e.length;nthis.state.pageSize&&S.a.createElement("div",{style:{marginTop:10,textAlign:"right"}},S.a.createElement(v.a,{current:this.state.pageNo,total:a,pageSize:this.state.pageSize,onChange:function(e){return t.setState({pageNo:e},function(){return t.querySubscriberList()})}}))))}}]),n}(S.a.Component)).displayName="SubscriberList",d=n))||d)||d;t.a=f},function(e,t,n){"use strict";n(54);var a=n(36),c=n.n(a),a=(n(67),n(47)),d=n.n(a),a=(n(179),n(78)),f=n.n(a),a=(n(37),n(10)),p=n.n(a),a=(n(34),n(20)),h=n.n(a),a=(n(35),n(18)),r=n.n(a),a=(n(46),n(24)),o=n.n(a),a=(n(49),n(28)),i=n.n(a),s=n(13),l=n(14),u=n(23),m=n(16),g=n(15),a=(n(27),n(6)),a=n.n(a),y=(n(421),n(123)),v=n.n(y),y=(n(66),n(21)),_=n.n(y),y=(n(69),n(40)),y=n.n(y),b=(n(38),n(2)),w=n.n(b),b=n(0),M=n.n(b),k=n(1),b=n(144),S=n.n(b),E=n(51),x=(n(781),w.a.Item),C=y.a.Row,T=y.a.Col,L=_.a.Column,O=v.a.Panel,y=(0,a.a.config)(((b=function(e){Object(m.a)(a,e);var t=Object(g.a)(a);function a(e){var n;return Object(s.a)(this,a),(n=t.call(this,e)).getQueryLater=function(){setTimeout(function(){return n.queryClusterStateList()})},n.setNowNameSpace=function(e,t){return n.setState({nowNamespaceName:e,nowNamespaceId:t})},n.rowColor=function(e){return{className:(e.voteFor,"")}},n.state={loading:!1,total:0,pageSize:10,currentPage:1,keyword:"",dataSource:[]},n.field=new i.a(Object(u.a)(n)),n}return Object(l.a)(a,[{key:"componentDidMount",value:function(){this.getQueryLater()}},{key:"openLoading",value:function(){this.setState({loading:!0})}},{key:"closeLoading",value:function(){this.setState({loading:!1})}},{key:"queryClusterStateList",value:function(){var n=this,e=this.state,t=e.currentPage,a=e.pageSize,r=e.keyword,e=e.withInstances,e=["withInstances=".concat(void 0!==e&&e),"pageNo=".concat(t),"pageSize=".concat(a),"keyword=".concat(r)];Object(k.e)({url:"v3/console/core/cluster/nodes?".concat(e.join("&")),beforeSend:function(){return n.openLoading()},success:function(){var e=0this.state.pageSize&&M.a.createElement("div",{style:{marginTop:10,textAlign:"right"}},M.a.createElement(d.a,{current:this.state.currentPage,total:this.state.total,pageSize:this.state.pageSize,onChange:function(e){return t.setState({currentPage:e},function(){return t.queryClusterStateList()})}}))))}}]),a}(M.a.Component)).displayName="ClusterNodeList",n=b))||n;t.a=y},function(e,t,n){"use strict";n(34);var a=n(20),i=n.n(a),s=n(13),l=n(14),u=n(16),c=n(15),a=(n(27),n(6)),a=n.n(a),r=n(12),o=(n(114),n(75)),o=n.n(o),d=n(0),f=n.n(d),p=(n(784),n(51)),d=n(87),h=n(148),m=n(149),g=n(30),y=n(22),v=o.a.Group,g=Object(g.b)(function(e){return Object(r.a)({},e.locale)},{changeLanguage:d.a,changeTheme:h.a,changeNameShow:m.a})(o=(0,a.a.config)(((n=function(e){Object(u.a)(o,e);var r=Object(c.a)(o);function o(e){Object(s.a)(this,o),e=r.call(this,e);var t=localStorage.getItem(y.p),n=localStorage.getItem(y.j),a=localStorage.getItem(y.g);return e.state={theme:"dark"===t?"dark":"light",language:"en-US"===a?"en-US":"zh-CN",nameShow:"select"===n?"select":"label"},e}return Object(l.a)(o,[{key:"newTheme",value:function(e){this.setState({theme:e})}},{key:"newLanguage",value:function(e){this.setState({language:e})}},{key:"newNameShow",value:function(e){this.setState({nameShow:e})}},{key:"submit",value:function(){var e=this.props,t=e.changeLanguage,n=e.changeTheme,e=e.changeNameShow,a=this.state.language,r=this.state.theme,o=this.state.nameShow;t(a),n(r),e(o)}},{key:"render",value:function(){var e=this.props.locale,e=void 0===e?{}:e,t=[{value:"light",label:e.settingLight},{value:"dark",label:e.settingDark}],n=[{value:"select",label:e.settingShowSelect},{value:"label",label:e.settingShowLabel}];return f.a.createElement(f.a.Fragment,null,f.a.createElement(p.a,{title:e.settingTitle}),f.a.createElement("div",{className:"setting-box"},f.a.createElement("div",{className:"text-box"},f.a.createElement("div",{className:"setting-checkbox"},f.a.createElement("div",{className:"setting-span"},e.settingTheme),f.a.createElement(v,{dataSource:t,value:this.state.theme,onChange:this.newTheme.bind(this)})),f.a.createElement("div",{className:"setting-checkbox"},f.a.createElement("div",{className:"setting-span"},e.settingLocale),f.a.createElement(v,{dataSource:[{value:"en-US",label:"English"},{value:"zh-CN",label:"中文"}],value:this.state.language,onChange:this.newLanguage.bind(this)})),f.a.createElement("div",{className:"setting-checkbox"},f.a.createElement("div",{className:"setting-span"},e.settingShow),f.a.createElement(v,{dataSource:n,value:this.state.nameShow,onChange:this.newNameShow.bind(this)}))),f.a.createElement(i.a,{type:"primary",onClick:this.submit.bind(this)},e.settingSubmit)))}}]),o}(f.a.Component)).displayName="SettingCenter",o=n))||o)||o;t.a=g},function(e,t,V){"use strict";V.r(t),function(e){V(54);var t=V(36),a=V.n(t),t=(V(27),V(6)),r=V.n(t),o=V(13),i=V(14),s=V(16),l=V(15),n=V(12),t=V(0),u=V.n(t),t=V(25),t=V.n(t),c=V(125),d=V(429),f=V(440),p=V(30),h=V(39),m=V(73),g=(V(477),V(449)),y=V(22),v=V(450),_=V(451),b=V(443),w=V(452),M=V(453),k=V(444),S=V(454),E=V(455),x=V(456),C=V(457),T=V(458),L=V(441),O=V(445),D=V(442),N=V(459),P=V(460),j=V(446),I=V(447),A=V(448),R=V(438),H=V(461),Y=V(439),F=V(87),z=V(52),W=V(148),B=V(149),e=(V(785),e.hot,localStorage.getItem(y.g)||localStorage.setItem(y.g,"zh-CN"===navigator.language?"zh-CN":"en-US"),Object(c.b)(Object(n.a)(Object(n.a)({},Y.a),{},{routing:d.routerReducer}))),Y=Object(c.d)(e,Object(c.c)(Object(c.a)(f.a),window[y.l]?window[y.l]():function(e){return e})),U=[{path:"/",exact:!0,render:function(){return u.a.createElement(h.a,{to:"/welcome"})}},{path:"/welcome",component:R.a},{path:"/namespace",component:b.a},{path:"/newconfig",component:w.a},{path:"/configsync",component:M.a},{path:"/configdetail",component:k.a},{path:"/configeditor",component:S.a},{path:"/historyDetail",component:E.a},{path:"/configRollback",component:x.a},{path:"/historyRollback",component:C.a},{path:"/listeningToQuery",component:T.a},{path:"/configurationManagement",component:L.a},{path:"/serviceManagement",component:O.a},{path:"/serviceDetail",component:D.a},{path:"/subscriberList",component:N.a},{path:"/clusterManagement",component:P.a},{path:"/userManagement",component:j.a},{path:"/rolesManagement",component:A.a},{path:"/permissionsManagement",component:I.a},{path:"/settingCenter",component:H.a}],e=Object(p.b)(function(e){return Object(n.a)(Object(n.a)({},e.locale),e.base)},{changeLanguage:F.a,getState:z.e,changeTheme:W.a,changeNameShow:B.a})(d=function(e){Object(s.a)(n,e);var t=Object(l.a)(n);function n(e){return Object(o.a)(this,n),(e=t.call(this,e)).state={shownotice:"none",noticecontent:"",nacosLoading:{}},e}return Object(i.a)(n,[{key:"componentDidMount",value:function(){this.props.getState();var e=localStorage.getItem(y.g),t=localStorage.getItem(y.p),n=localStorage.getItem(y.j);this.props.changeLanguage(e),this.props.changeTheme(t),this.props.changeNameShow(n)}},{key:"router",get:function(){var e=this.props,t=e.loginPageEnabled,e=e.consoleUiEnable;return u.a.createElement(m.a,null,u.a.createElement(h.d,null,t&&"false"===t?null:u.a.createElement(h.b,{path:"/login",component:v.a}),u.a.createElement(h.b,{path:"/register",component:_.a}),u.a.createElement(g.a,null,e&&"true"===e&&U.map(function(e){return u.a.createElement(h.b,Object.assign({key:e.path},e))}))))}},{key:"render",value:function(){var e=this.props,t=e.locale,e=e.loginPageEnabled;return u.a.createElement(a.a,Object.assign({className:"nacos-loading",shape:"flower",tip:"loading...",visible:!e,fullScreen:!0},this.state.nacosLoading),u.a.createElement(r.a,{locale:t},this.router))}}]),n}(u.a.Component))||d;t.a.render(u.a.createElement(p.a,{store:Y},u.a.createElement(e,null)),document.getElementById("root"))}.call(this,V(463)(e))},function(e,t){e.exports=function(e){var t;return e.webpackPolyfill||((t=Object.create(e)).children||(t.children=[]),Object.defineProperty(t,"loaded",{enumerable:!0,get:function(){return t.l}}),Object.defineProperty(t,"id",{enumerable:!0,get:function(){return t.i}}),Object.defineProperty(t,"exports",{enumerable:!0}),t.webpackPolyfill=1),t}},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(I,e,t){"use strict"; /** @license React v16.14.0 * react.production.min.js * @@ -335,6 +335,6 @@ var S=P(706),o=P(707),s=P(708);function n(){return d.TYPED_ARRAY_SUPPORT?2147483 * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. - */var a=60103,r=60106,o=60107,i=60108,s=60114,l=60109,u=60110,c=60112,d=60113,f=60120,p=60115,h=60116,m=60121,g=60122,y=60117,v=60129,_=60131;function b(e){if("object"==typeof e&&null!==e){var t=e.$$typeof;switch(t){case a:switch(e=e.type){case o:case s:case i:case d:case f:return e;default:switch(e=e&&e.$$typeof){case u:case c:case h:case p:case l:return e;default:return t}}case r:return t}}}"function"==typeof Symbol&&Symbol.for&&(a=(w=Symbol.for)("react.element"),r=w("react.portal"),o=w("react.fragment"),i=w("react.strict_mode"),s=w("react.profiler"),l=w("react.provider"),u=w("react.context"),c=w("react.forward_ref"),d=w("react.suspense"),f=w("react.suspense_list"),p=w("react.memo"),h=w("react.lazy"),m=w("react.block"),g=w("react.server.block"),y=w("react.fundamental"),v=w("react.debug_trace_mode"),_=w("react.legacy_hidden"));var w=l,M=a,k=c,S=o,E=h,x=p,C=r,T=s,L=i,O=d;t.ContextConsumer=u,t.ContextProvider=w,t.Element=M,t.ForwardRef=k,t.Fragment=S,t.Lazy=E,t.Memo=x,t.Portal=C,t.Profiler=T,t.StrictMode=L,t.Suspense=O,t.isAsyncMode=function(){return!1},t.isConcurrentMode=function(){return!1},t.isContextConsumer=function(e){return b(e)===u},t.isContextProvider=function(e){return b(e)===l},t.isElement=function(e){return"object"==typeof e&&null!==e&&e.$$typeof===a},t.isForwardRef=function(e){return b(e)===c},t.isFragment=function(e){return b(e)===o},t.isLazy=function(e){return b(e)===h},t.isMemo=function(e){return b(e)===p},t.isPortal=function(e){return b(e)===r},t.isProfiler=function(e){return b(e)===s},t.isStrictMode=function(e){return b(e)===i},t.isSuspense=function(e){return b(e)===d},t.isValidElementType=function(e){return"string"==typeof e||"function"==typeof e||e===o||e===s||e===v||e===i||e===d||e===f||e===_||"object"==typeof e&&null!==e&&(e.$$typeof===h||e.$$typeof===p||e.$$typeof===l||e.$$typeof===u||e.$$typeof===c||e.$$typeof===y||e.$$typeof===m||e[0]===g)},t.typeOf=b},function(e,t,n){"use strict";var a,r=n(1);window.edasprefix="acm",window.globalConfig={isParentEdas:function(){return window.parent&&-1!==window.parent.location.host.indexOf("edas")}},r.e.middleWare(function(){var e=0=e.length?{value:void 0,done:!0}:(e=a(e,t),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){var o=n(153),i=n(152);e.exports=function(r){return function(e,t){var n,e=String(i(e)),t=o(t),a=e.length;return t<0||a<=t?r?"":void 0:(n=e.charCodeAt(t))<55296||56319=e.length?(this._t=void 0,r(1)):r(0,"keys"==t?n:"values"==t?e[n]:[n,e[n]])},"values"),o.Arguments=o.Array,a("keys"),a("values"),a("entries")},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,n){e.exports={default:n(500),__esModule:!0}},function(e,t,n){n(501),n(506),n(507),n(508),e.exports=n(81).Symbol},function(I,A,e){"use strict";function a(e){var t=T[e]=_(M[E]);return t._k=e,t}function n(e,t){m(e);for(var n,a=B(t=g(t)),r=0,o=a.length;rr;)l(T,t=n[r++])||t==x||t==H||a.push(t);return a}function i(e){for(var t,n=e===O,a=X(n?L:g(e)),r=[],o=0;a.length>o;)!l(T,t=a[o++])||n&&!l(O,t)||r.push(T[t]);return r}var s=e(80),l=e(90),u=e(82),c=e(96),R=e(211),H=e(502).KEY,d=e(113),f=e(155),p=e(161),F=e(129),h=e(100),z=e(162),W=e(163),B=e(503),U=e(504),m=e(112),V=e(98),K=e(158),g=e(99),y=e(151),v=e(126),_=e(160),q=e(505),G=e(213),b=e(157),$=e(89),J=e(127),Q=G.f,w=$.f,X=q.f,M=s.Symbol,k=s.JSON,S=k&&k.stringify,E="prototype",x=h("_hidden"),Z=h("toPrimitive"),ee={}.propertyIsEnumerable,C=f("symbol-registry"),T=f("symbols"),L=f("op-symbols"),O=Object[E],f="function"==typeof M&&!!b.f,D=s.QObject,N=!D||!D[E]||!D[E].findChild,P=u&&d(function(){return 7!=_(w({},"a",{get:function(){return w(this,"a",{value:7}).a}})).a})?function(e,t,n){var a=Q(O,t);a&&delete O[t],w(e,t,n),a&&e!==O&&w(O,t,a)}:w,j=f&&"symbol"==typeof M.iterator?function(e){return"symbol"==typeof e}:function(e){return e instanceof M},Y=function(e,t,n){return e===O&&Y(L,t,n),m(e),t=y(t,!0),m(n),(l(T,t)?(n.enumerable?(l(e,x)&&e[x][t]&&(e[x][t]=!1),n=_(n,{enumerable:v(0,!1)})):(l(e,x)||w(e,x,v(1,{})),e[x][t]=!0),P):w)(e,t,n)};f||(R((M=function(){if(this instanceof M)throw TypeError("Symbol is not a constructor!");var t=F(0ne;)h(te[ne++]);for(var ae=J(h.store),re=0;ae.length>re;)W(ae[re++]);c(c.S+c.F*!f,"Symbol",{for:function(e){return l(C,e+="")?C[e]:C[e]=M(e)},keyFor:function(e){if(!j(e))throw TypeError(e+" is not a symbol!");for(var t in C)if(C[t]===e)return t},useSetter:function(){N=!0},useSimple:function(){N=!1}}),c(c.S+c.F*!f,"Object",{create:function(e,t){return void 0===t?_(e):n(_(e),t)},defineProperty:Y,defineProperties:n,getOwnPropertyDescriptor:r,getOwnPropertyNames:o,getOwnPropertySymbols:i});D=d(function(){b.f(1)});c(c.S+c.F*D,"Object",{getOwnPropertySymbols:function(e){return b.f(K(e))}}),k&&c(c.S+c.F*(!f||d(function(){var e=M();return"[null]"!=S([e])||"{}"!=S({a:e})||"{}"!=S(Object(e))})),"JSON",{stringify:function(e){for(var t,n,a=[e],r=1;ri;)o.call(e,a=r[i++])&&t.push(a);return t}},function(e,t,n){var a=n(209);e.exports=Array.isArray||function(e){return"Array"==a(e)}},function(e,t,n){var a=n(99),r=n(212).f,o={}.toString,i="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){if(!i||"[object Window]"!=o.call(e))return r(a(e));try{return r(e)}catch(e){return i.slice()}}},function(e,t){},function(e,t,n){n(163)("asyncIterator")},function(e,t,n){n(163)("observable")},function(e,t,n){e.exports={default:n(510),__esModule:!0}},function(e,t,n){n(511),e.exports=n(81).Object.setPrototypeOf},function(e,t,n){var a=n(96);a(a.S,"Object",{setPrototypeOf:n(512).set})},function(e,t,r){function o(e,t){if(a(e),!n(t)&&null!==t)throw TypeError(t+": can't set as prototype!")}var n=r(98),a=r(112);e.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(e,n,a){try{(a=r(204)(Function.call,r(213).f(Object.prototype,"__proto__").set,2))(e,[]),n=!(e instanceof Array)}catch(e){n=!0}return function(e,t){return o(e,t),n?e.__proto__=t:a(e,t),e}}({},!1):void 0),check:o}},function(e,t,n){e.exports={default:n(514),__esModule:!0}},function(e,t,n){n(515);var a=n(81).Object;e.exports=function(e,t){return a.create(e,t)}},function(e,t,n){var a=n(96);a(a.S,"Object",{create:n(160)})},function(e,t,n){"use strict";var i=n(517);function a(){}function r(){}r.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,r,o){if(o!==i)throw(o=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types")).name="Invariant Violation",o}function t(){return e}var n={array:e.isRequired=e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:r,resetWarningCache:a};return n.PropTypes=n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t,n){"use strict";function l(e,t,n,a){e.removeEventListener&&e.removeEventListener(t,n,a||!1)}function a(e,t,n,a){return e.addEventListener&&e.addEventListener(t,n,a||!1),{off:function(){return l(e,t,n,a)}}}t.__esModule=!0,t.on=a,t.once=function(r,o,i,s){return a(r,o,function e(){for(var t=arguments.length,n=Array(t),a=0;a68?1900:2e3)},r=function(t){return function(e){this[t]=+e}},o=[/[+-]\d\d:?(\d\d)?|Z/,function(e){(this.zone||(this.zone={})).offset=function(e){if(!e)return 0;if("Z"===e)return 0;var t=e.match(/([+-]|\d\d)/g),n=60*t[1]+(+t[2]||0);return 0===n?0:"+"===t[0]?-n:n}(e)}],i=function(e){var t=h[e];return t&&(t.indexOf?t:t.s.concat(t.f))},s=function(e,t){var n,a=h.meridiem;if(a){for(var r=1;r<=24;r+=1)if(e.indexOf(a(r,0,t))>-1){n=r>12;break}}else n=e===(t?"pm":"PM");return n},f={A:[n,function(e){this.afternoon=s(e,!1)}],a:[n,function(e){this.afternoon=s(e,!0)}],S:[/\d/,function(e){this.milliseconds=100*+e}],SS:[e,function(e){this.milliseconds=10*+e}],SSS:[/\d{3}/,function(e){this.milliseconds=+e}],s:[t,r("seconds")],ss:[t,r("seconds")],m:[t,r("minutes")],mm:[t,r("minutes")],H:[t,r("hours")],h:[t,r("hours")],HH:[t,r("hours")],hh:[t,r("hours")],D:[t,r("day")],DD:[e,r("day")],Do:[n,function(e){var t=h.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var a=1;a<=31;a+=1)t(a).replace(/\[|\]/g,"")===e&&(this.day=a)}],M:[t,r("month")],MM:[e,r("month")],MMM:[n,function(e){var t=i("months"),n=(i("monthsShort")||t.map(function(e){return e.slice(0,3)})).indexOf(e)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[n,function(e){var t=i("months").indexOf(e)+1;if(t<1)throw new Error;this.month=t%12||t}],Y:[/[+-]?\d+/,r("year")],YY:[e,function(e){this.year=a(e)}],YYYY:[/\d{4}/,r("year")],Z:o,ZZ:o};function b(e){var t,r;t=e,r=h&&h.formats;for(var u=(e=t.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(e,t,n){var a=n&&n.toUpperCase();return t||r[n]||l[n]||r[a].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(e,t,n){return t||n.slice(1)})})).match(d),c=u.length,n=0;n-1)return new Date(("X"===t?1e3:1)*e);var a=b(t)(e),r=a.year,o=a.month,i=a.day,s=a.hours,l=a.minutes,u=a.seconds,c=a.milliseconds,d=a.zone,f=new Date,p=i||(r||o?1:f.getDate()),h=r||f.getFullYear(),m=0;r&&!o||(m=o>0?o-1:f.getMonth());var g=s||0,y=l||0,v=u||0,_=c||0;return d?new Date(Date.UTC(h,m,p,g,y,v,_+60*d.offset*1e3)):n?new Date(Date.UTC(h,m,p,g,y,v,_)):new Date(h,m,p,g,y,v,_)}catch(e){return new Date("")}}(t,r,n),this.init(),l&&!0!==l&&(this.$L=this.locale(l).$L),s&&t!=this.format(r)&&(this.$d=new Date("")),h={}}else if(r instanceof Array)for(var u=r.length,c=1;c<=u;c+=1){a[1]=r[c-1];var d=f.apply(this,a);if(d.isValid()){this.$d=d.$d,this.$L=d.$L,this.init();break}c===u&&(this.$d=new Date(""))}else p.call(this,e)}}}()},function(e,t,n){e.exports=function(){"use strict";return function(e,t,a){a.updateLocale=function(e,t){var n=a.Ls[e];if(n)return(t?Object.keys(t):[]).forEach(function(e){n[e]=t[e]}),n}}}()},function(e,t,n){e.exports=function(e,t,n){function a(e,t,n,a,r){var o,e=e.name?e:e.$locale(),t=s(e[t]),n=s(e[n]),i=t||n.map(function(e){return e.slice(0,a)});return r?(o=e.weekStart,i.map(function(e,t){return i[(t+(o||0))%7]})):i}function r(){return n.Ls[n.locale()]}function o(e,t){return e.formats[t]||e.formats[t.toUpperCase()].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(e,t,n){return t||n.slice(1)})}var t=t.prototype,s=function(e){return e&&(e.indexOf?e:e.s)};t.localeData=function(){return function(){var t=this;return{months:function(e){return e?e.format("MMMM"):a(t,"months")},monthsShort:function(e){return e?e.format("MMM"):a(t,"monthsShort","months",3)},firstDayOfWeek:function(){return t.$locale().weekStart||0},weekdays:function(e){return e?e.format("dddd"):a(t,"weekdays")},weekdaysMin:function(e){return e?e.format("dd"):a(t,"weekdaysMin","weekdays",2)},weekdaysShort:function(e){return e?e.format("ddd"):a(t,"weekdaysShort","weekdays",3)},longDateFormat:function(e){return o(t.$locale(),e)},meridiem:this.$locale().meridiem,ordinal:this.$locale().ordinal}}.bind(this)()},n.localeData=function(){var t=r();return{firstDayOfWeek:function(){return t.weekStart||0},weekdays:function(){return n.weekdays()},weekdaysShort:function(){return n.weekdaysShort()},weekdaysMin:function(){return n.weekdaysMin()},months:function(){return n.months()},monthsShort:function(){return n.monthsShort()},longDateFormat:function(e){return o(t,e)},meridiem:t.meridiem,ordinal:t.ordinal}},n.months=function(){return a(r(),"months")},n.monthsShort=function(){return a(r(),"monthsShort","months",3)},n.weekdays=function(e){return a(r(),"weekdays",null,null,e)},n.weekdaysShort=function(e){return a(r(),"weekdaysShort","weekdays",3,e)},n.weekdaysMin=function(e){return a(r(),"weekdaysMin","weekdays",2,e)}}},function(e,t,n){e.exports=function(){"use strict";var i="month",s="quarter";return function(e,t){var n=t.prototype;n.quarter=function(e){return this.$utils().u(e)?Math.ceil((this.month()+1)/3):this.month(this.month()%3+3*(e-1))};var a=n.add;n.add=function(e,t){return e=Number(e),this.$utils().p(t)===s?this.add(3*e,i):a.bind(this)(e,t)};var o=n.startOf;n.startOf=function(e,t){var n=this.$utils(),a=!!n.u(t)||t;if(n.p(e)===s){var r=this.quarter()-1;return a?this.month(3*r).startOf(i).startOf("day"):this.month(3*r+2).endOf(i).endOf("day")}return o.bind(this)(e,t)}}}()},function(e,t,n){e.exports=function(){"use strict";return function(e,t){var n=t.prototype,o=n.format;n.format=function(e){var t=this,n=this.$locale();if(!this.isValid())return o.bind(this)(e);var a=this.$utils(),r=(e||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(e){switch(e){case"Q":return Math.ceil((t.$M+1)/3);case"Do":return n.ordinal(t.$D);case"gggg":return t.weekYear();case"GGGG":return t.isoWeekYear();case"wo":return n.ordinal(t.week(),"W");case"w":case"ww":return a.s(t.week(),"w"===e?1:2,"0");case"W":case"WW":return a.s(t.isoWeek(),"W"===e?1:2,"0");case"k":case"kk":return a.s(String(0===t.$H?24:t.$H),"k"===e?1:2,"0");case"X":return Math.floor(t.$d.getTime()/1e3);case"x":return t.$d.getTime();case"z":return"["+t.offsetName()+"]";case"zzz":return"["+t.offsetName("long")+"]";default:return e}});return o.bind(this)(r)}}}()},function(e,t,n){e.exports=function(){"use strict";var s="week",l="year";return function(e,t,i){var n=t.prototype;n.week=function(e){if(void 0===e&&(e=null),null!==e)return this.add(7*(e-this.week()),"day");var t=this.$locale().yearStart||1;if(11===this.month()&&this.date()>25){var n=i(this).startOf(l).add(1,l).date(t),a=i(this).endOf(s);if(n.isBefore(a))return 1}var r=i(this).startOf(l).date(t).startOf(s).subtract(1,"millisecond"),o=this.diff(r,s,!0);return o<0?i(this).startOf("week").week():Math.ceil(o)},n.weeks=function(e){return void 0===e&&(e=null),this.week(e)}}}()},function(e,t,n){e.exports=function(e){"use strict";function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=t(e),a={name:"zh-cn",weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),ordinal:function(e,t){return"W"===t?e+"周":e+"日"},weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日Ah点mm分",LLLL:"YYYY年M月D日ddddAh点mm分",l:"YYYY/M/D",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1 分钟",mm:"%d 分钟",h:"1 小时",hh:"%d 小时",d:"1 天",dd:"%d 天",M:"1 个月",MM:"%d 个月",y:"1 年",yy:"%d 年"},meridiem:function(e,t){var n=100*e+t;return n<600?"凌晨":n<900?"早上":n<1100?"上午":n<1300?"中午":n<1800?"下午":"晚上"}};return n.default.locale(a,null,!0),a}(n(219))},function(e,t,n){"use strict";t.__esModule=!0,t.flex=t.transition=t.animation=void 0;var r=n(215),o=n(101);function a(e){var n,a;return!!r.hasDOM&&(n=document.createElement("div"),(a=!1,o.each)(e,function(e,t){if(void 0!==n.style[t])return!(a={end:e})}),a)}var i,s;t.animation=a({WebkitAnimation:"webkitAnimationEnd",OAnimation:"oAnimationEnd",animation:"animationend"}),t.transition=a({WebkitTransition:"webkitTransitionEnd",OTransition:"oTransitionEnd",transition:"transitionend"}),t.flex=(n={display:["flex","-webkit-flex","-moz-flex","-ms-flexbox"]},!!r.hasDOM&&(i=document.createElement("div"),(s=!1,o.each)(n,function(e,t){return(0,o.each)(e,function(e){try{i.style[t]=e,s=s||i.style[t]===e}catch(e){}return!s}),!s}),s))},function(e,t,n){"use strict";t.__esModule=!0,t.getFocusNodeList=i,t.saveLastFocusNode=function(){s=document.activeElement},t.clearLastFocusNode=function(){s=null},t.backLastFocusNode=function(){if(s)try{s.focus()}catch(e){}},t.limitTabRange=function(e,t){{var n,a;t.keyCode===r.default.TAB&&(e=i(e),n=e.length-1,-1<(a=e.indexOf(document.activeElement)))&&(a=a+(t.shiftKey?-1:1),e[a=n<(a=a<0?n:a)?0:a].focus(),t.preventDefault())}};var t=n(220),r=(t=t)&&t.__esModule?t:{default:t},a=n(101);function o(e){var t=e.nodeName.toLowerCase(),n=parseInt(e.getAttribute("tabindex"),10),n=!isNaN(n)&&-1a.height)&&(r[1]=-t.top-("t"===e?t.height:0)),r},this._getParentScrollOffset=function(e){var t=0,n=0;return e&&e.offsetParent&&e.offsetParent!==document.body&&(isNaN(e.offsetParent.scrollTop)||(t+=e.offsetParent.scrollTop),isNaN(e.offsetParent.scrollLeft)||(n+=e.offsetParent.scrollLeft)),{top:t,left:n}}};var p=a;function h(e){(0,o.default)(this,h),r.call(this),this.pinElement=e.pinElement,this.baseElement=e.baseElement,this.pinFollowBaseElementWhenFixed=e.pinFollowBaseElementWhenFixed,this.container=function(e){var t=e.container,e=e.baseElement;if("undefined"==typeof document)return t;for(var n=(n=(0,i.default)(t,e))||document.body;"static"===y.dom.getStyle(n,"position");){if(!n||n===document.body)return document.body;n=n.parentNode}return n}(e),this.autoFit=e.autoFit||!1,this.align=e.align||"tl tl",this.offset=e.offset||[0,0],this.needAdjust=e.needAdjust||!1,this.isRtl=e.isRtl||!1}t.default=p,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var w=a(n(3)),M=a(n(16)),k=n(0),S=a(k),E=a(n(18)),x=a(n(196)),C=a(n(83)),T=n(11);function a(e){return e&&e.__esModule?e:{default:e}}t.default=function(e){var t,n,a,r,o,i,s,l,u,c,d,f,p,h,m,g,y,v,_,b;return k.useState&&k.useRef&&k.useEffect?(t=void 0===(t=e.prefix)?"next-":t,r=e.animation,n=void 0===r?{in:"expandInDown",out:"expandOutUp"}:r,a=e.visible,r=e.hasMask,o=e.align,o=void 0===(s=e.points)?o?o.split(" "):void 0:s,i=e.onPosition,s=e.children,b=e.className,l=e.style,u=e.wrapperClassName,c=e.beforeOpen,d=e.onOpen,f=e.afterOpen,p=e.beforeClose,h=e.onClose,m=e.afterClose,e=(0,M.default)(e,["prefix","animation","visible","hasMask","align","points","onPosition","children","className","style","wrapperClassName","beforeOpen","onOpen","afterOpen","beforeClose","onClose","afterClose"]),g=(_=(0,k.useState)(!0))[0],y=_[1],v=(0,k.useRef)(null),_=S.default.createElement(C.default.OverlayAnimate,{visible:a,animation:n,onEnter:function(){y(!1),"function"==typeof c&&c(v.current)},onEntering:function(){"function"==typeof d&&d(v.current)},onEntered:function(){"function"==typeof f&&f(v.current)},onExit:function(){"function"==typeof p&&p(v.current)},onExiting:function(){"function"==typeof h&&h(v.current)},onExited:function(){y(!0),"function"==typeof m&&m(v.current)},timeout:300,style:l},s?(0,k.cloneElement)(s,{className:(0,E.default)([t+"overlay-inner",b,s&&s.props&&s.props.className])}):S.default.createElement("span",null)),b=(0,E.default)(((l={})[t+"overlay-wrapper v2"]=!0,l[u]=u,l.opened=a,l)),S.default.createElement(x.default,(0,w.default)({},e,{visible:a,isAnimationEnd:g,hasMask:r,wrapperClassName:b,maskClassName:t+"overlay-backdrop",maskRender:function(e){return S.default.createElement(C.default.OverlayAnimate,{visible:a,animation:!!n&&{in:"fadeIn",out:"fadeOut"},timeout:300,unmountOnExit:!0},e)},points:o,onPosition:function(e){(0,w.default)(e,{align:e.config.points}),"function"==typeof i&&i(e)},ref:v}),_)):(T.log.warning("need react version > 16.8.0"),null)},e.exports=t.default},function(n,e){function a(e,t){return n.exports=a=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},n.exports.__esModule=!0,n.exports.default=n.exports,a(e,t)}n.exports=a,n.exports.__esModule=!0,n.exports.default=n.exports},function(e,t,n){"use strict";t.__esModule=!0;var a,c=g(n(16)),d=g(n(3)),r=g(n(4)),o=g(n(7)),i=g(n(8)),l=n(0),f=g(l),p=n(24),s=n(31),u=g(n(5)),h=n(11),m=g(n(362));function g(e){return e&&e.__esModule?e:{default:e}}var y,n=h.func.noop,v=h.func.makeChain,_=h.func.bindCtx,u=(y=l.Component,(0,i.default)(b,y),b.getDerivedStateFromProps=function(e,t){return"visible"in e?(0,d.default)({},t,{visible:e.visible}):null},b.prototype.componentWillUnmount=function(){var t=this;["_timer","_hideTimer","_showTimer"].forEach(function(e){t[e]&&clearTimeout(t[e])})},b.prototype.handleVisibleChange=function(e,t,n){"visible"in this.props||this.setState({visible:e}),this.props.onVisibleChange(e,t,n)},b.prototype.handleTriggerClick=function(e){this.state.visible&&!this.props.canCloseByTrigger||this.handleVisibleChange(!this.state.visible,"fromTrigger",e)},b.prototype.handleTriggerKeyDown=function(e){var t=this.props.triggerClickKeycode;(Array.isArray(t)?t:[t]).includes(e.keyCode)&&(e.preventDefault(),this.handleTriggerClick(e))},b.prototype.handleTriggerMouseEnter=function(e){var t=this;this._mouseNotFirstOnMask=!1,this._hideTimer&&(clearTimeout(this._hideTimer),this._hideTimer=null),this._showTimer&&(clearTimeout(this._showTimer),this._showTimer=null),this.state.visible||(this._showTimer=setTimeout(function(){t.handleVisibleChange(!0,"fromTrigger",e)},this.props.delay))},b.prototype.handleTriggerMouseLeave=function(e,t){var n=this;this._showTimer&&(clearTimeout(this._showTimer),this._showTimer=null),this.state.visible&&(this._hideTimer=setTimeout(function(){n.handleVisibleChange(!1,t||"fromTrigger",e)},this.props.delay))},b.prototype.handleTriggerFocus=function(e){this.handleVisibleChange(!0,"fromTrigger",e)},b.prototype.handleTriggerBlur=function(e){this._isForwardContent||this.handleVisibleChange(!1,"fromTrigger",e),this._isForwardContent=!1},b.prototype.handleContentMouseDown=function(){this._isForwardContent=!0},b.prototype.handleContentMouseEnter=function(){clearTimeout(this._hideTimer)},b.prototype.handleContentMouseLeave=function(e){this.handleTriggerMouseLeave(e,"fromContent")},b.prototype.handleMaskMouseEnter=function(){this._mouseNotFirstOnMask||(clearTimeout(this._hideTimer),this._hideTimer=null,this._mouseNotFirstOnMask=!1)},b.prototype.handleMaskMouseLeave=function(){this._mouseNotFirstOnMask=!0},b.prototype.handleRequestClose=function(e,t){this.handleVisibleChange(!1,e,t)},b.prototype.renderTrigger=function(){var e,t,n,a,r,o,i,s=this,l=this.props,u=l.trigger,l=l.disabled,c={key:"trigger","aria-haspopup":!0,"aria-expanded":this.state.visible};return this.state.visible||(c["aria-describedby"]=void 0),l||(l=this.props.triggerType,l=Array.isArray(l)?l:[l],e=u&&u.props||{},t=e.onClick,n=e.onKeyDown,a=e.onMouseEnter,r=e.onMouseLeave,o=e.onFocus,i=e.onBlur,l.forEach(function(e){switch(e){case"click":c.onClick=v(s.handleTriggerClick,t),c.onKeyDown=v(s.handleTriggerKeyDown,n);break;case"hover":c.onMouseEnter=v(s.handleTriggerMouseEnter,a),c.onMouseLeave=v(s.handleTriggerMouseLeave,r);break;case"focus":c.onFocus=v(s.handleTriggerFocus,o),c.onBlur=v(s.handleTriggerBlur,i)}})),u&&f.default.cloneElement(u,c)},b.prototype.renderContent=function(){var t=this,e=this.props,n=e.children,e=e.triggerType,e=Array.isArray(e)?e:[e],n=l.Children.only(n),a=n.props,r=a.onMouseDown,o=a.onMouseEnter,i=a.onMouseLeave,s={key:"portal"};return e.forEach(function(e){switch(e){case"focus":s.onMouseDown=v(t.handleContentMouseDown,r);break;case"hover":s.onMouseEnter=v(t.handleContentMouseEnter,o),s.onMouseLeave=v(t.handleContentMouseLeave,i)}}),f.default.cloneElement(n,s)},b.prototype.renderPortal=function(){function e(){return(0,p.findDOMNode)(t)}var t=this,n=this.props,a=n.target,r=n.safeNode,o=n.followTrigger,i=n.triggerType,s=n.hasMask,l=n.wrapperStyle,n=(0,c.default)(n,["target","safeNode","followTrigger","triggerType","hasMask","wrapperStyle"]),u=this.props.container,r=Array.isArray(r)?[].concat(r):[r],l=(r.unshift(e),l||{});return o&&(u=function(e){return e&&e.parentNode||e},l.position="relative"),"hover"===i&&s&&(n.onMaskMouseEnter=this.handleMaskMouseEnter,n.onMaskMouseLeave=this.handleMaskMouseLeave),f.default.createElement(m.default,(0,d.default)({},n,{key:"overlay",ref:function(e){return t.overlay=e},visible:this.state.visible,target:a||e,container:u,safeNode:r,wrapperStyle:l,triggerType:i,hasMask:s,onRequestClose:this.handleRequestClose}),this.props.children&&this.renderContent())},b.prototype.render=function(){return[this.renderTrigger(),this.renderPortal()]},a=i=b,i.propTypes={children:u.default.node,trigger:u.default.element,triggerType:u.default.oneOfType([u.default.string,u.default.array]),triggerClickKeycode:u.default.oneOfType([u.default.number,u.default.array]),visible:u.default.bool,defaultVisible:u.default.bool,onVisibleChange:u.default.func,disabled:u.default.bool,autoFit:u.default.bool,delay:u.default.number,canCloseByTrigger:u.default.bool,target:u.default.any,safeNode:u.default.any,followTrigger:u.default.bool,container:u.default.any,hasMask:u.default.bool,wrapperStyle:u.default.object,rtl:u.default.bool,v2:u.default.bool,placement:u.default.string,placementOffset:u.default.number,autoAdjust:u.default.bool},i.defaultProps={triggerType:"hover",triggerClickKeycode:[h.KEYCODE.SPACE,h.KEYCODE.ENTER],defaultVisible:!1,onVisibleChange:n,disabled:!1,autoFit:!1,delay:200,canCloseByTrigger:!0,followTrigger:!1,container:function(){return document.body},rtl:!1},a);function b(e){(0,r.default)(this,b);var t=(0,o.default)(this,y.call(this,e));return t.state={visible:void 0===e.visible?e.defaultVisible:e.visible},_(t,["handleTriggerClick","handleTriggerKeyDown","handleTriggerMouseEnter","handleTriggerMouseLeave","handleTriggerFocus","handleTriggerBlur","handleContentMouseEnter","handleContentMouseLeave","handleContentMouseDown","handleRequestClose","handleMaskMouseEnter","handleMaskMouseLeave"]),t}u.displayName="Popup",t.default=(0,s.polyfill)(u),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var L=a(n(3)),O=a(n(16)),D=n(0),N=a(D),P=a(n(18)),j=a(n(196)),Y=a(n(83)),I=n(11);function a(e){return e&&e.__esModule?e:{default:e}}t.default=function(r){var e,t,o,n,a,i,s,l,u,c,d,f,p,h,m,g,y,v,_,b,w,M,k,S,E,x,C,T;return D.useState&&D.useRef&&D.useEffect?(e=void 0===(e=r.prefix)?"next-":e,E=r.animation,t=void 0===E?{in:"expandInDown",out:"expandOutUp"}:E,E=r.defaultVisible,x=r.onVisibleChange,o=void 0===x?function(){}:x,x=r.trigger,n=void 0===(n=r.triggerType)?"hover":n,C=r.overlay,a=r.onPosition,T=r.children,i=r.className,s=r.style,l=r.wrapperClassName,u=r.triggerClickKeycode,c=r.align,d=r.beforeOpen,f=r.onOpen,p=r.afterOpen,h=r.beforeClose,m=r.onClose,g=r.afterClose,y=(0,O.default)(r,["prefix","animation","defaultVisible","onVisibleChange","trigger","triggerType","overlay","onPosition","children","className","style","wrapperClassName","triggerClickKeycode","align","beforeOpen","onOpen","afterOpen","beforeClose","onClose","afterClose"]),E=(0,D.useState)(E),v=E[0],_=E[1],E=(0,D.useState)(t),b=E[0],w=E[1],M=(E=(0,D.useState)(!0))[0],k=E[1],S=(0,D.useRef)(null),(0,D.useEffect)(function(){"visible"in r&&_(r.visible)},[r.visible]),(0,D.useEffect)(function(){"animation"in r&&b!==t&&w(t)},[t]),E=C?T:x,x=N.default.createElement(Y.default.OverlayAnimate,{visible:v,animation:b,timeout:200,onEnter:function(){k(!1),"function"==typeof d&&d(S.current)},onEntering:function(){"function"==typeof f&&f(S.current)},onEntered:function(){"function"==typeof p&&p(S.current)},onExit:function(){"function"==typeof h&&h(S.current)},onExiting:function(){"function"==typeof m&&m(S.current)},onExited:function(){k(!0),"function"==typeof g&&g(S.current)},style:s},(x=C||T)?(0,D.cloneElement)(x,{className:(0,P.default)([e+"overlay-inner",i,x&&x.props&&x.props.className])}):N.default.createElement("span",null)),C=(0,P.default)(((s={})[e+"overlay-wrapper v2"]=!0,s[l]=l,s.opened=v,s)),T={},c&&(T.points=c.split(" ")),N.default.createElement(j.default.Popup,(0,L.default)({},y,T,{wrapperClassName:C,overlay:x,visible:v,isAnimationEnd:M,triggerType:n,onVisibleChange:function(e){for(var t=arguments.length,n=Array(1 16.8.0"),null)},e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var l=a(n(3)),u=a(n(16)),o=n(0),c=a(o),i=a(n(24)),s=a(n(6)),d=a(n(83)),f=a(n(165)),p=n(11);function a(e){return e&&e.__esModule?e:{default:e}}var h={top:8,maxCount:0,duration:3e3},m=s.default.config(function(e){var t=e.prefix,s=void 0===t?"next-":t,t=e.dataSource,a=void 0===t?[]:t,r=(0,o.useState)()[1];return a.forEach(function(n){n.timer||(n.timer=setTimeout(function(){var e,t=a.indexOf(n);-1a&&y.shift(),i.default.render(c.default.createElement(s.default,s.default.getContext(),c.default.createElement(m,{dataSource:y})),g),{key:n,close:function(){r.timer&&clearTimeout(r.timer);var e=y.indexOf(r);-1 16.8.0")}},e.exports=t.default},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n(565)},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var p=l(n(3)),r=l(n(4)),o=l(n(7)),a=l(n(8)),h=l(n(42)),m=l(n(0)),i=l(n(5)),g=l(n(18)),y=n(11),s=l(n(28)),v=l(n(368));function l(e){return e&&e.__esModule?e:{default:e}}function _(e,r){var o=r.size,i=r.device,s=r.labelAlign,l=r.labelTextAlign,u=r.labelCol,c=r.wrapperCol,d=r.responsive,f=r.colon;return m.default.Children.map(e,function(e){var t,n,a;return y.obj.isReactFragment(e)?_(e.props.children,r):e&&-1<["function","object"].indexOf((0,h.default)(e.type))&&"form_item"===e.type._typeMark?(t={labelCol:e.props.labelCol||u,wrapperCol:e.props.wrapperCol||c,labelAlign:e.props.labelAlign||("phone"===i?"top":s),labelTextAlign:e.props.labelTextAlign||l,colon:"colon"in e.props?e.props.colon:f,size:e.props.size||o,responsive:d},m.default.cloneElement(e,(n=t,a={},Object.keys(n).forEach(function(e){void 0!==n[e]&&(a[e]=n[e])}),a))):e})}u=m.default.Component,(0,a.default)(b,u),b.prototype.getChildContext=function(){return{_formField:this.props.field||this._formField,_formSize:this.props.size,_formDisabled:this.props.disabled,_formPreview:this.props.isPreview,_formFullWidth:this.props.fullWidth,_formLabelForErrorMessage:this.props.useLabelForErrorMessage}},b.prototype.componentDidUpdate=function(e){var t=this.props;this._formField&&("value"in t&&t.value!==e.value&&this._formField.setValues(t.value),"error"in t)&&t.error!==e.error&&this._formField.setValues(t.error)},b.prototype.render=function(){var e=this.props,t=e.className,n=e.inline,a=e.size,r=(e.device,e.labelAlign,e.labelTextAlign,e.onSubmit),o=e.children,i=(e.labelCol,e.wrapperCol,e.style),s=e.prefix,l=e.rtl,u=e.isPreview,c=e.component,d=e.responsive,f=e.gap,n=(e.colon,(0,g.default)(((e={})[s+"form"]=!0,e[s+"inline"]=n,e[""+s+a]=a,e[s+"form-responsive-grid"]=d,e[s+"form-preview"]=u,e[t]=!!t,e))),a=_(o,this.props);return m.default.createElement(c,(0,p.default)({role:"grid"},y.obj.pickOthers(b.propTypes,this.props),{className:n,style:i,dir:l?"rtl":void 0,onSubmit:r}),d?m.default.createElement(v.default,{gap:f},a):a)},a=n=b,n.propTypes={prefix:i.default.string,inline:i.default.bool,size:i.default.oneOf(["large","medium","small"]),fullWidth:i.default.bool,labelAlign:i.default.oneOf(["top","left","inset"]),labelTextAlign:i.default.oneOf(["left","right"]),field:i.default.any,saveField:i.default.func,labelCol:i.default.object,wrapperCol:i.default.object,onSubmit:i.default.func,children:i.default.any,className:i.default.string,style:i.default.object,value:i.default.object,onChange:i.default.func,component:i.default.oneOfType([i.default.string,i.default.func]),fieldOptions:i.default.object,rtl:i.default.bool,device:i.default.oneOf(["phone","tablet","desktop"]),responsive:i.default.bool,isPreview:i.default.bool,useLabelForErrorMessage:i.default.bool,colon:i.default.bool,disabled:i.default.bool,gap:i.default.oneOfType([i.default.arrayOf(i.default.number),i.default.number])},n.defaultProps={prefix:"next-",onSubmit:function(e){e.preventDefault()},size:"medium",labelAlign:"left",onChange:y.func.noop,component:"form",saveField:y.func.noop,device:"desktop",colon:!1,disabled:!1},n.childContextTypes={_formField:i.default.object,_formSize:i.default.string,_formDisabled:i.default.bool,_formPreview:i.default.bool,_formFullWidth:i.default.bool,_formLabelForErrorMessage:i.default.bool};var u,n=a;function b(e){(0,r.default)(this,b);var t,n,a=(0,o.default)(this,u.call(this,e));return a.onChange=function(e,t){a.props.onChange(a._formField.getValues(),{name:e,value:t,field:a._formField})},a._formField=null,!1!==e.field&&(t=(0,p.default)({},e.fieldOptions,{onChange:a.onChange}),e.field?(a._formField=e.field,n=a._formField.options.onChange,t.onChange=y.func.makeChain(n,a.onChange),a._formField.setOptions&&a._formField.setOptions(t)):("value"in e&&(t.values=e.value),a._formField=new s.default(a,t)),e.locale&&e.locale.Validate&&a._formField.setOptions({messages:e.locale.Validate}),e.saveField(a._formField)),a}n.displayName="Form",t.default=n,e.exports=t.default},function(e,t,n){"use strict";var a=n(91),m=(Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,a(n(169))),i=a(n(570)),r=a(n(170)),o=a(n(116)),b=a(n(171)),w=a(n(77)),s=a(n(366)),l=a(n(367)),g=a(n(577)),M=n(586),u={state:"",valueName:"value",trigger:"onChange",inputValues:[]},a=function(){function a(e){var t=this,n=1e.length)&&(t=e.length);for(var n=0,a=new Array(t);n=a.length?n:(o=a[r],n=e(t&&t[o],n,a,r+1),t?Array.isArray(t)?((a=[].concat(t))[o]=n,a):(0,l.default)({},t,(0,i.default)({},o,n)):((r=isNaN(o)?{}:[])[o]=n,r))};t=function(){};void 0!==e&&e.env,n.warning=t}.call(this,r(103))},function(e,t,n){"use strict";t.__esModule=!0,t.cloneAndAddKey=function(e){{var t;if(e&&(0,a.isValidElement)(e))return t=e.key||"error",(0,a.cloneElement)(e,{key:t})}return e},t.scrollToFirstError=function(e){var t=e.errorsGroup,n=e.options,a=e.instance;if(t&&n.scrollToFirstError){var r,o=void 0,i=void 0;for(r in t)if(t.hasOwnProperty(r)){var s=u.default.findDOMNode(a[r]);if(!s)return;var l=s.offsetTop;(void 0===i||l), use instead of.'),S.default.cloneElement(e,{className:t,size:d||C(r)})):(0,k.isValidElement)(e)?e:S.default.createElement("span",{className:a+"btn-helper"},e)}),t=c,_=(0,b.default)({},x.obj.pickOthers(Object.keys(T.propTypes),e),{type:o,disabled:p,onClick:h,className:(0,E.default)(n)});return"button"!==t&&(delete _.type,_.disabled)&&(delete _.onClick,_.href)&&delete _.href,S.default.createElement(t,(0,b.default)({},_,{dir:g?"rtl":void 0,onMouseUp:this.onMouseUp,ref:this.buttonRefHandler}),s,u)},a=n=T,n.propTypes=(0,b.default)({},s.default.propTypes,{prefix:r.default.string,rtl:r.default.bool,type:r.default.oneOf(["primary","secondary","normal"]),size:r.default.oneOf(["small","medium","large"]),icons:r.default.shape({loading:r.default.node}),iconSize:r.default.oneOfType([r.default.oneOf(["xxs","xs","small","medium","large","xl","xxl","xxxl","inherit"]),r.default.number]),htmlType:r.default.oneOf(["submit","reset","button"]),component:r.default.oneOf(["button","a","div","span"]),loading:r.default.bool,ghost:r.default.oneOf([!0,!1,"light","dark"]),text:r.default.bool,warning:r.default.bool,disabled:r.default.bool,onClick:r.default.func,className:r.default.string,onMouseUp:r.default.func,children:r.default.node}),n.defaultProps={prefix:"next-",type:"normal",size:"medium",icons:{},htmlType:"button",component:"button",loading:!1,ghost:!1,text:!1,warning:!1,disabled:!1,onClick:function(){}};var u,s=a;function T(){var e,t;(0,o.default)(this,T);for(var n=arguments.length,a=Array(n),r=0;ra[r])return!0;if(n[r] 0, or `null`');if(U(i,"numericSeparator")&&"boolean"!=typeof i.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var t=i.numericSeparator;if(void 0===n)return"undefined";if(null===n)return"null";if("boolean"==typeof n)return n?"true":"false";if("string"==typeof n)return function e(t,n){if(t.length>n.maxStringLength)return a=t.length-n.maxStringLength,a="... "+a+" more character"+(1"}if(z(n))return 0===n.length?"[]":(l=$(n,m),h&&!function(e){for(var t=0;t "+m(e,n))}),ne("Map",_.call(n),u,h)):function(e){if(w&&e&&"object"==typeof e)try{w.call(e);try{_.call(e)}catch(e){return 1}return e instanceof Set}catch(e){}return}(n)?(c=[],M&&M.call(n,function(e){c.push(m(e,n))}),ne("Set",w.call(n),c,h)):function(e){if(k&&e&&"object"==typeof e)try{k.call(e,k);try{S.call(e,S)}catch(e){return 1}return e instanceof WeakMap}catch(e){}return}(n)?q("WeakMap"):function(e){if(S&&e&&"object"==typeof e)try{S.call(e,S);try{k.call(e,k)}catch(e){return 1}return e instanceof WeakSet}catch(e){}return}(n)?q("WeakSet"):function(e){if(E&&e&&"object"==typeof e)try{return E.call(e),1}catch(e){}return}(n)?q("WeakRef"):"[object Number]"!==V(d=n)||j&&"object"==typeof d&&j in d?function(e){if(e&&"object"==typeof e&&D)try{return D.call(e),1}catch(e){}return}(n)?K(m(D.call(n))):"[object Boolean]"!==V(t=n)||j&&"object"==typeof t&&j in t?"[object String]"!==V(e=n)||j&&"object"==typeof e&&j in e?("[object Date]"!==V(t=n)||j&&"object"==typeof t&&j in t)&&!W(n)?(e=$(n,m),t=I?I(n)===Object.prototype:n instanceof Object||n.constructor===Object,f=n instanceof Object?"":"null prototype",p=!t&&j&&Object(n)===n&&j in n?x.call(V(n),8,-1):f?"Object":"",t=(!t&&"function"==typeof n.constructor&&n.constructor.name?n.constructor.name+" ":"")+(p||f?"["+O.call(L.call([],p||[],f||[]),": ")+"] ":""),0===e.length?t+"{}":h?t+"{"+G(e,h)+"}":t+"{ "+O.call(e,", ")+" }"):String(n):K(m(String(n))):K(J.call(n)):K(m(Number(n)))};var l=Object.prototype.hasOwnProperty||function(e){return e in this};function U(e,t){return l.call(e,t)}function V(e){return i.call(e)}function ee(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,a=e.length;nu&&!d&&(o=o.slice(0,u),e=C.default.createElement(m.default,{key:"_count",type:"primary",size:p,animation:!1},c(a,t))),0, as child."),a.push(t),e.props.children)&&(t.children=n(e.props.children))}),a}(e.children):t},N.prototype.fetchInfoFromBinaryChildren=function(e){function r(e,t){return t=t||0,e.forEach(function(e){e.children?t=r(e.children,t):t+=1}),t}var a=!1,o=[],i=[],e=(function t(){var e=0r.tRight&&(e=r.tRight,r.changedPageX=r.tRight-r.startLeft),e-r.cellLefto.clientHeight,o.scrollWidth,o.clientWidth,o={},e||(o[r]=0,o[a]=0),+i&&(o.marginBottom=-i,o.paddingBottom=i,e)&&(o[a]=i),h.dom.setStyle(this.headerNode,o)),n&&!this.props.lockType&&this.headerNode&&(r=this.headerNode.querySelector("."+t+"table-header-fixer"),e=h.dom.getStyle(this.headerNode,"height"),a=h.dom.getStyle(this.headerNode,"paddingBottom"),h.dom.setStyle(r,{width:i,height:e-a}))},o.prototype.render=function(){var e=this.props,t=e.components,n=e.className,a=e.prefix,r=e.fixedHeader,o=e.lockType,i=e.dataSource,e=(e.maxBodyHeight,(0,u.default)(e,["components","className","prefix","fixedHeader","lockType","dataSource","maxBodyHeight"]));return r&&((t=(0,l.default)({},t)).Header||(t.Header=m.default),t.Body||(t.Body=g.default),t.Wrapper||(t.Wrapper=y.default),n=(0,p.default)(((r={})[a+"table-fixed"]=!0,r[a+"table-wrap-empty"]=!i.length,r[n]=n,r))),d.default.createElement(s,(0,l.default)({},e,{dataSource:i,lockType:o,components:t,className:n,prefix:a}))},o}(d.default.Component),n.FixedHeader=m.default,n.FixedBody=g.default,n.FixedWrapper=y.default,n.propTypes=(0,l.default)({hasHeader:r.default.bool,fixedHeader:r.default.bool,maxBodyHeight:r.default.oneOfType([r.default.number,r.default.string])},s.propTypes),n.defaultProps=(0,l.default)({},s.defaultProps,{hasHeader:!0,fixedHeader:!1,maxBodyHeight:200,components:{},refs:{},prefix:"next-"}),n.childContextTypes={fixedHeader:r.default.bool,getNode:r.default.func,onFixedScrollSync:r.default.func,getTableInstanceForFixed:r.default.func,maxBodyHeight:r.default.oneOfType([r.default.number,r.default.string])};var t,n=t;return n.displayName="FixedTable",(0,o.statics)(n,s),n};var d=s(n(0)),r=s(n(5)),f=n(24),p=s(n(18)),h=n(11),m=s(n(136)),g=s(n(406)),y=s(n(137)),o=n(70);function s(e){return e&&e.__esModule?e:{default:e}}e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var i=o(n(16)),f=o(n(3)),r=o(n(4)),s=o(n(7)),l=o(n(8)),u=(t.default=function(o){e=t=function(n){function a(e,t){(0,r.default)(this,a);var d=(0,s.default)(this,n.call(this,e,t));return d.addSelection=function(e){var t=d.props,n=t.prefix,a=t.rowSelection,t=t.size,a=a.columnProps&&a.columnProps()||{};e.find(function(e){return"selection"===e.key})||e.unshift((0,f.default)({key:"selection",title:d.renderSelectionHeader.bind(d),cell:d.renderSelectionBody.bind(d),width:"small"===t?34:50,className:n+"table-selection "+n+"table-prerow",__normalized:!0},a))},d.renderSelectionHeader=function(){var e=d.selectAllRow,t={},n=d.props,a=n.rowSelection,r=n.primaryKey,o=n.dataSource,i=n.entireDataSource,n=n.locale,s=d.state.selectedRowKeys,l=a.mode||"multiple",u=!!s.length,c=!1,i=(d.flatDataSource(i||o).filter(function(e,t){return!a.getProps||!(a.getProps(e,t)||{}).disabled}).map(function(e){return e[r]}).forEach(function(e){-1===s.indexOf(e)?u=!1:c=!0}),t.onClick=b(function(e){e.stopPropagation()},t.onClick),a.titleProps&&a.titleProps()||{});return u&&(c=!1),["multiple"===l?p.default.createElement(h.default,(0,f.default)({key:"_total",indeterminate:c,"aria-label":n.selectAll,checked:u,onChange:e},t,i)):null,a.titleAddons&&a.titleAddons()]},d.renderSelectionBody=function(e,t,n){var a=d.props,r=a.rowSelection,a=a.primaryKey,o=d.state.selectedRowKeys,i=r.mode||"multiple",o=-1l.length&&(u=o),w(u.filter(function(e){return-1=Math.max(a-y,0)&&od.clientHeight;this.isLock()?(e=this.bodyLeftNode,t=this.bodyRightNode,n=this.getWrapperNode("right"),a=f?c:0,d=d.offsetHeight-c,f||(r[l]=0,r[u]=0),+c?(r.marginBottom=-c,r.paddingBottom=c):(r.marginBottom=-20,r.paddingBottom=20),d={"max-height":d},o||+c||(d[u]=0),+c&&(d[u]=-c),e&&g.dom.setStyle(e,d),t&&g.dom.setStyle(t,d),n&&+c&&g.dom.setStyle(n,i?"left":"right",a+"px")):(r.marginBottom=-c,r.paddingBottom=c,r[u]=0,f||(r[l]=0)),s&&g.dom.setStyle(s,r)},a.prototype.adjustHeaderSize=function(){var o=this;this.isLock()&&this.tableInc.groupChildren.forEach(function(e,t){var n=o.tableInc.groupChildren[t].length-1,n=o.getHeaderCellNode(t,n),a=o.getHeaderCellNode(t,0),r=o.getHeaderCellNode(t,0,"right"),t=o.getHeaderCellNode(t,0,"left");n&&r&&(n=n.offsetHeight,g.dom.setStyle(r,"height",n),setTimeout(function(){var e=o.tableRightInc.affixRef;return e&&e.getInstance()&&e.getInstance().updatePosition()})),a&&t&&(r=a.offsetHeight,g.dom.setStyle(t,"height",r),setTimeout(function(){var e=o.tableLeftInc.affixRef;return e&&e.getInstance()&&e.getInstance().updatePosition()}))})},a.prototype.adjustRowHeight=function(){var n=this;this.isLock()&&this.tableInc.props.dataSource.forEach(function(e,t){t=""+("object"===(void 0===e?"undefined":(0,r.default)(e))&&"__rowIndex"in e?e.__rowIndex:t)+(e.__expanded?"_expanded":"");n.setRowHeight(t,"left"),n.setRowHeight(t,"right")})},a.prototype.setRowHeight=function(e,t){var t=this.getRowNode(e,t),e=this.getRowNode(e),e=(M?e&&e.offsetHeight:e&&parseFloat(getComputedStyle(e).height))||"auto",n=(M?t&&t.offsetHeight:t&&parseFloat(getComputedStyle(t).height))||"auto";t&&e!==n&&g.dom.setStyle(t,"height",e)},a.prototype.getWrapperNode=function(e){e=e?e.charAt(0).toUpperCase()+e.substr(1):"";try{return(0,u.findDOMNode)(this["lock"+e+"El"])}catch(e){return null}},a.prototype.getRowNode=function(e,t){t=this["table"+(t=t?t.charAt(0).toUpperCase()+t.substr(1):"")+"Inc"];try{return(0,u.findDOMNode)(t.getRowRef(e))}catch(e){return null}},a.prototype.getHeaderCellNode=function(e,t,n){n=this["table"+(n=n?n.charAt(0).toUpperCase()+n.substr(1):"")+"Inc"];try{return(0,u.findDOMNode)(n.getHeaderCellRef(e,t))}catch(e){return null}},a.prototype.getCellNode=function(e,t,n){n=this["table"+(n=n?n.charAt(0).toUpperCase()+n.substr(1):"")+"Inc"];try{return(0,u.findDOMNode)(n.getCellRef(e,t))}catch(e){return null}},a.prototype.render=function(){var e,t=this.props,n=(t.children,t.columns,t.prefix),a=t.components,r=t.className,o=t.dataSource,i=t.tableWidth,t=(0,f.default)(t,["children","columns","prefix","components","className","dataSource","tableWidth"]),s=this.normalizeChildrenState(this.props),l=s.lockLeftChildren,u=s.lockRightChildren,s=s.children,c={left:this.getFlatenChildrenLength(l),right:this.getFlatenChildrenLength(u),origin:this.getFlatenChildrenLength(s)};return this._notNeedAdjustLockLeft&&(l=[]),this._notNeedAdjustLockRight&&(u=[]),this.lockLeftChildren=l,this.lockRightChildren=u,this.isOriginLock()?((a=(0,p.default)({},a)).Body=a.Body||v.default,a.Header=a.Header||_.default,a.Wrapper=a.Wrapper||b.default,a.Row=a.Row||y.default,r=(0,m.default)(((e={})[n+"table-lock"]=!0,e[n+"table-wrap-empty"]=!o.length,e[r]=r,e)),e=[h.default.createElement(d,(0,p.default)({},t,{dataSource:o,key:"lock-left",columns:l,className:n+"table-lock-left",lengths:c,prefix:n,lockType:"left",components:a,ref:this.saveLockLeftRef,loading:!1,"aria-hidden":!0})),h.default.createElement(d,(0,p.default)({},t,{dataSource:o,key:"lock-right",columns:u,className:n+"table-lock-right",lengths:c,prefix:n,lockType:"right",components:a,ref:this.saveLockRightRef,loading:!1,"aria-hidden":!0}))],h.default.createElement(d,(0,p.default)({},t,{tableWidth:i,dataSource:o,columns:s,prefix:n,lengths:c,wrapperContent:e,components:a,className:r}))):h.default.createElement(d,this.props)},a}(h.default.Component),t.LockRow=y.default,t.LockBody=v.default,t.LockHeader=_.default,t.propTypes=(0,p.default)({scrollToCol:a.default.number,scrollToRow:a.default.number},d.propTypes),t.defaultProps=(0,p.default)({},d.defaultProps),t.childContextTypes={getTableInstance:a.default.func,getLockNode:a.default.func,onLockBodyScroll:a.default.func,onRowMouseEnter:a.default.func,onRowMouseLeave:a.default.func};var e,t=e;return t.displayName="LockTable",(0,w.statics)(t,d),t},n(0)),h=d(l),u=n(24),a=d(n(5)),m=d(n(18)),c=d(n(182)),g=n(11),y=d(n(184)),v=d(n(407)),_=d(n(408)),b=d(n(137)),w=n(70);function d(e){return e&&e.__esModule?e:{default:e}}var M=g.env.ieVersion;function k(e){return function n(e){return e.map(function(e){var t=(0,p.default)({},e);return e.children&&(e.children=n(e.children)),t})}(e)}e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var l=s(n(16)),u=s(n(3)),r=s(n(4)),o=s(n(7)),i=s(n(8)),c=(t.default=function(s){e=t=function(n){function a(e,t){(0,r.default)(this,a);var c=(0,o.default)(this,n.call(this,e));return c.state={},c.updateOffsetArr=function(){var e=c.splitChildren||{},t=e.lockLeftChildren,n=e.lockRightChildren,e=e.originChildren,a=c.getFlatenChildren(t).length,r=c.getFlatenChildren(n).length,e=a+r+c.getFlatenChildren(e).length,a=0r.top-e.offset?(t?(l.position="absolute",l.top=a-(r.top-e.offset),u="relative"):(l.position="fixed",l.top=e.offset+n.top),c._setAffixStyle(l,!0),c._setContainerStyle(s)):e.bottom&&a{e=new Date(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}},function(e,t,n){"use strict";const a=n(186);class r extends Date{constructor(e){super(e+"Z"),this.isFloating=!0}toISOString(){return`${this.getUTCFullYear()}-${a(2,this.getUTCMonth()+1)}-`+a(2,this.getUTCDate())+"T"+(`${a(2,this.getUTCHours())}:${a(2,this.getUTCMinutes())}:${a(2,this.getUTCSeconds())}.`+a(3,this.getUTCMilliseconds()))}}e.exports=e=>{e=new r(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}},function(a,e,r){"use strict";!function(e){const t=r(186);class n extends e.Date{constructor(e){super(e),this.isDate=!0}toISOString(){return`${this.getUTCFullYear()}-${t(2,this.getUTCMonth()+1)}-`+t(2,this.getUTCDate())}}a.exports=e=>{e=new n(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}}.call(this,r(65))},function(e,t,n){"use strict";const a=n(186);class r extends Date{constructor(e){super(`0000-01-01T${e}Z`),this.isTime=!0}toISOString(){return`${a(2,this.getUTCHours())}:${a(2,this.getUTCMinutes())}:${a(2,this.getUTCSeconds())}.`+a(3,this.getUTCMilliseconds())}}e.exports=e=>{e=new r(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}},function(e,t,n){"use strict";!function(s){e.exports=function(r,e){e=e||{};const n=e.blocksize||40960,o=new t;return new Promise((e,t)=>{s(i,0,n,e,t)});function i(e,t,n,a){if(e>=r.length)try{return n(o.finish())}catch(e){return a(l(e,r))}try{o.parse(r.slice(e,e+t)),s(i,e+t,t,n,a)}catch(e){a(l(e,r))}}};const t=n(185),l=n(187)}.call(this,n(412).setImmediate)},function(e,t,n){!function(e,p){!function(n,o){"use strict";var a,i,s,r,l,u,t,e;function c(e){delete i[e]}function d(e){if(s)setTimeout(d,0,e);else{var t=i[e];if(t){s=!0;try{var n=t,a=n.callback,r=n.args;switch(r.length){case 0:a();break;case 1:a(r[0]);break;case 2:a(r[0],r[1]);break;case 3:a(r[0],r[1],r[2]);break;default:a.apply(o,r)}}finally{c(e),s=!1}}}}function f(){function e(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(t)&&d(+e.data.slice(t.length))}var t="setImmediate$"+Math.random()+"$";n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),l=function(e){n.postMessage(t+e,"*")}}n.setImmediate||(a=1,s=!(i={}),r=n.document,e=(e=Object.getPrototypeOf&&Object.getPrototypeOf(n))&&e.setTimeout?e:n,"[object process]"==={}.toString.call(n.process)?l=function(e){p.nextTick(function(){d(e)})}:!function(){var e,t;if(n.postMessage&&!n.importScripts)return e=!0,t=n.onmessage,n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}()?l=n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){d(e.data)},function(e){t.port2.postMessage(e)}):r&&"onreadystatechange"in r.createElement("script")?(u=r.documentElement,function(e){var t=r.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,u.removeChild(t),t=null},u.appendChild(t)}):function(e){setTimeout(d,0,e)}:f(),e.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n{let n,a=!1,r=!1;function o(){if(a=!0,!n)try{e(l.finish())}catch(e){t(e)}}function i(e){r=!0,t(e)}s.once("end",o),s.once("error",i),function e(){n=!0;let t;for(;null!==(t=s.read());)try{l.parse(t)}catch(e){return i(e)}n=!1;if(a)return o();if(r)return;s.once("readable",e)}()})}(e):function(){const a=new o;return new r.Transform({objectMode:!0,transform(e,t,n){try{a.parse(e.toString(t))}catch(e){this.emit("error",e)}n()},flush(e){try{this.push(a.finish())}catch(e){this.emit("error",e)}e()}})}()};const r=n(704),o=n(185)},function(e,t,n){e.exports=a;var c=n(188).EventEmitter;function a(){c.call(this)}n(105)(a,c),a.Readable=n(189),a.Writable=n(714),a.Duplex=n(715),a.Transform=n(716),a.PassThrough=n(717),(a.Stream=a).prototype.pipe=function(t,e){var n=this;function a(e){t.writable&&!1===t.write(e)&&n.pause&&n.pause()}function r(){n.readable&&n.resume&&n.resume()}n.on("data",a),t.on("drain",r),t._isStdio||e&&!1===e.end||(n.on("end",i),n.on("close",s));var o=!1;function i(){o||(o=!0,t.end())}function s(){o||(o=!0,"function"==typeof t.destroy&&t.destroy())}function l(e){if(u(),0===c.listenerCount(this,"error"))throw e}function u(){n.removeListener("data",a),t.removeListener("drain",r),n.removeListener("end",i),n.removeListener("close",s),n.removeListener("error",l),t.removeListener("error",l),n.removeListener("end",u),n.removeListener("close",u),t.removeListener("close",u)}return n.on("error",l),t.on("error",l),n.on("end",u),n.on("close",u),t.on("close",u),t.emit("pipe",n),t}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";t.byteLength=function(e){var e=c(e),t=e[0],e=e[1];return 3*(t+e)/4-e},t.toByteArray=function(e){var t,n,a=c(e),r=a[0],a=a[1],o=new u(function(e,t){return 3*(e+t)/4-t}(r,a)),i=0,s=0>16&255,o[i++]=t>>8&255,o[i++]=255&t;2===a&&(t=l[e.charCodeAt(n)]<<2|l[e.charCodeAt(n+1)]>>4,o[i++]=255&t);1===a&&(t=l[e.charCodeAt(n)]<<10|l[e.charCodeAt(n+1)]<<4|l[e.charCodeAt(n+2)]>>2,o[i++]=t>>8&255,o[i++]=255&t);return o},t.fromByteArray=function(e){for(var t,n=e.length,a=n%3,r=[],o=0,i=n-a;o>18&63]+s[e>>12&63]+s[e>>6&63]+s[63&e]}(a));return r.join("")}(e,o,i>2]+s[t<<4&63]+"==")):2==a&&(t=(e[n-2]<<8)+e[n-1],r.push(s[t>>10]+s[t>>4&63]+s[t<<2&63]+"="));return r.join("")};for(var s=[],l=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r=0,o=a.length;r=e.length?{value:void 0,done:!0}:(e=a(e,t),this._i+=e.length,{value:e,done:!1})})},function(e,t,n){var o=n(153),i=n(152);e.exports=function(r){return function(e,t){var n,e=String(i(e)),t=o(t),a=e.length;return t<0||a<=t?r?"":void 0:(n=e.charCodeAt(t))<55296||56319=e.length?(this._t=void 0,r(1)):r(0,"keys"==t?n:"values"==t?e[n]:[n,e[n]])},"values"),o.Arguments=o.Array,a("keys"),a("values"),a("entries")},function(e,t){e.exports=function(){}},function(e,t){e.exports=function(e,t){return{value:t,done:!!e}}},function(e,t,n){e.exports={default:n(500),__esModule:!0}},function(e,t,n){n(501),n(506),n(507),n(508),e.exports=n(81).Symbol},function(I,A,e){"use strict";function a(e){var t=T[e]=_(M[E]);return t._k=e,t}function n(e,t){m(e);for(var n,a=B(t=g(t)),r=0,o=a.length;rr;)l(T,t=n[r++])||t==x||t==H||a.push(t);return a}function i(e){for(var t,n=e===O,a=X(n?L:g(e)),r=[],o=0;a.length>o;)!l(T,t=a[o++])||n&&!l(O,t)||r.push(T[t]);return r}var s=e(80),l=e(90),u=e(82),c=e(96),R=e(211),H=e(502).KEY,d=e(113),f=e(155),p=e(161),F=e(129),h=e(100),z=e(162),W=e(163),B=e(503),U=e(504),m=e(112),V=e(98),K=e(158),g=e(99),y=e(151),v=e(126),_=e(160),q=e(505),G=e(213),b=e(157),$=e(89),J=e(127),Q=G.f,w=$.f,X=q.f,M=s.Symbol,k=s.JSON,S=k&&k.stringify,E="prototype",x=h("_hidden"),Z=h("toPrimitive"),ee={}.propertyIsEnumerable,C=f("symbol-registry"),T=f("symbols"),L=f("op-symbols"),O=Object[E],f="function"==typeof M&&!!b.f,D=s.QObject,N=!D||!D[E]||!D[E].findChild,P=u&&d(function(){return 7!=_(w({},"a",{get:function(){return w(this,"a",{value:7}).a}})).a})?function(e,t,n){var a=Q(O,t);a&&delete O[t],w(e,t,n),a&&e!==O&&w(O,t,a)}:w,j=f&&"symbol"==typeof M.iterator?function(e){return"symbol"==typeof e}:function(e){return e instanceof M},Y=function(e,t,n){return e===O&&Y(L,t,n),m(e),t=y(t,!0),m(n),(l(T,t)?(n.enumerable?(l(e,x)&&e[x][t]&&(e[x][t]=!1),n=_(n,{enumerable:v(0,!1)})):(l(e,x)||w(e,x,v(1,{})),e[x][t]=!0),P):w)(e,t,n)};f||(R((M=function(){if(this instanceof M)throw TypeError("Symbol is not a constructor!");var t=F(0ne;)h(te[ne++]);for(var ae=J(h.store),re=0;ae.length>re;)W(ae[re++]);c(c.S+c.F*!f,"Symbol",{for:function(e){return l(C,e+="")?C[e]:C[e]=M(e)},keyFor:function(e){if(!j(e))throw TypeError(e+" is not a symbol!");for(var t in C)if(C[t]===e)return t},useSetter:function(){N=!0},useSimple:function(){N=!1}}),c(c.S+c.F*!f,"Object",{create:function(e,t){return void 0===t?_(e):n(_(e),t)},defineProperty:Y,defineProperties:n,getOwnPropertyDescriptor:r,getOwnPropertyNames:o,getOwnPropertySymbols:i});D=d(function(){b.f(1)});c(c.S+c.F*D,"Object",{getOwnPropertySymbols:function(e){return b.f(K(e))}}),k&&c(c.S+c.F*(!f||d(function(){var e=M();return"[null]"!=S([e])||"{}"!=S({a:e})||"{}"!=S(Object(e))})),"JSON",{stringify:function(e){for(var t,n,a=[e],r=1;ri;)o.call(e,a=r[i++])&&t.push(a);return t}},function(e,t,n){var a=n(209);e.exports=Array.isArray||function(e){return"Array"==a(e)}},function(e,t,n){var a=n(99),r=n(212).f,o={}.toString,i="object"==typeof window&&window&&Object.getOwnPropertyNames?Object.getOwnPropertyNames(window):[];e.exports.f=function(e){if(!i||"[object Window]"!=o.call(e))return r(a(e));try{return r(e)}catch(e){return i.slice()}}},function(e,t){},function(e,t,n){n(163)("asyncIterator")},function(e,t,n){n(163)("observable")},function(e,t,n){e.exports={default:n(510),__esModule:!0}},function(e,t,n){n(511),e.exports=n(81).Object.setPrototypeOf},function(e,t,n){var a=n(96);a(a.S,"Object",{setPrototypeOf:n(512).set})},function(e,t,r){function o(e,t){if(a(e),!n(t)&&null!==t)throw TypeError(t+": can't set as prototype!")}var n=r(98),a=r(112);e.exports={set:Object.setPrototypeOf||("__proto__"in{}?function(e,n,a){try{(a=r(204)(Function.call,r(213).f(Object.prototype,"__proto__").set,2))(e,[]),n=!(e instanceof Array)}catch(e){n=!0}return function(e,t){return o(e,t),n?e.__proto__=t:a(e,t),e}}({},!1):void 0),check:o}},function(e,t,n){e.exports={default:n(514),__esModule:!0}},function(e,t,n){n(515);var a=n(81).Object;e.exports=function(e,t){return a.create(e,t)}},function(e,t,n){var a=n(96);a(a.S,"Object",{create:n(160)})},function(e,t,n){"use strict";var i=n(517);function a(){}function r(){}r.resetWarningCache=a,e.exports=function(){function e(e,t,n,a,r,o){if(o!==i)throw(o=new Error("Calling PropTypes validators directly is not supported by the `prop-types` package. Use PropTypes.checkPropTypes() to call them. Read more at http://fb.me/use-check-prop-types")).name="Invariant Violation",o}function t(){return e}var n={array:e.isRequired=e,bigint:e,bool:e,func:e,number:e,object:e,string:e,symbol:e,any:e,arrayOf:t,element:e,elementType:e,instanceOf:t,node:e,objectOf:t,oneOf:t,oneOfType:t,shape:t,exact:t,checkPropTypes:r,resetWarningCache:a};return n.PropTypes=n}},function(e,t,n){"use strict";e.exports="SECRET_DO_NOT_PASS_THIS_OR_YOU_WILL_BE_FIRED"},function(e,t,n){"use strict";function l(e,t,n,a){e.removeEventListener&&e.removeEventListener(t,n,a||!1)}function a(e,t,n,a){return e.addEventListener&&e.addEventListener(t,n,a||!1),{off:function(){return l(e,t,n,a)}}}t.__esModule=!0,t.on=a,t.once=function(r,o,i,s){return a(r,o,function e(){for(var t=arguments.length,n=Array(t),a=0;a68?1900:2e3)},r=function(t){return function(e){this[t]=+e}},o=[/[+-]\d\d:?(\d\d)?|Z/,function(e){(this.zone||(this.zone={})).offset=function(e){if(!e)return 0;if("Z"===e)return 0;var t=e.match(/([+-]|\d\d)/g),n=60*t[1]+(+t[2]||0);return 0===n?0:"+"===t[0]?-n:n}(e)}],i=function(e){var t=h[e];return t&&(t.indexOf?t:t.s.concat(t.f))},s=function(e,t){var n,a=h.meridiem;if(a){for(var r=1;r<=24;r+=1)if(e.indexOf(a(r,0,t))>-1){n=r>12;break}}else n=e===(t?"pm":"PM");return n},f={A:[n,function(e){this.afternoon=s(e,!1)}],a:[n,function(e){this.afternoon=s(e,!0)}],S:[/\d/,function(e){this.milliseconds=100*+e}],SS:[e,function(e){this.milliseconds=10*+e}],SSS:[/\d{3}/,function(e){this.milliseconds=+e}],s:[t,r("seconds")],ss:[t,r("seconds")],m:[t,r("minutes")],mm:[t,r("minutes")],H:[t,r("hours")],h:[t,r("hours")],HH:[t,r("hours")],hh:[t,r("hours")],D:[t,r("day")],DD:[e,r("day")],Do:[n,function(e){var t=h.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var a=1;a<=31;a+=1)t(a).replace(/\[|\]/g,"")===e&&(this.day=a)}],M:[t,r("month")],MM:[e,r("month")],MMM:[n,function(e){var t=i("months"),n=(i("monthsShort")||t.map(function(e){return e.slice(0,3)})).indexOf(e)+1;if(n<1)throw new Error;this.month=n%12||n}],MMMM:[n,function(e){var t=i("months").indexOf(e)+1;if(t<1)throw new Error;this.month=t%12||t}],Y:[/[+-]?\d+/,r("year")],YY:[e,function(e){this.year=a(e)}],YYYY:[/\d{4}/,r("year")],Z:o,ZZ:o};function b(e){var t,r;t=e,r=h&&h.formats;for(var u=(e=t.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(e,t,n){var a=n&&n.toUpperCase();return t||r[n]||l[n]||r[a].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(e,t,n){return t||n.slice(1)})})).match(d),c=u.length,n=0;n-1)return new Date(("X"===t?1e3:1)*e);var a=b(t)(e),r=a.year,o=a.month,i=a.day,s=a.hours,l=a.minutes,u=a.seconds,c=a.milliseconds,d=a.zone,f=new Date,p=i||(r||o?1:f.getDate()),h=r||f.getFullYear(),m=0;r&&!o||(m=o>0?o-1:f.getMonth());var g=s||0,y=l||0,v=u||0,_=c||0;return d?new Date(Date.UTC(h,m,p,g,y,v,_+60*d.offset*1e3)):n?new Date(Date.UTC(h,m,p,g,y,v,_)):new Date(h,m,p,g,y,v,_)}catch(e){return new Date("")}}(t,r,n),this.init(),l&&!0!==l&&(this.$L=this.locale(l).$L),s&&t!=this.format(r)&&(this.$d=new Date("")),h={}}else if(r instanceof Array)for(var u=r.length,c=1;c<=u;c+=1){a[1]=r[c-1];var d=f.apply(this,a);if(d.isValid()){this.$d=d.$d,this.$L=d.$L,this.init();break}c===u&&(this.$d=new Date(""))}else p.call(this,e)}}}()},function(e,t,n){e.exports=function(){"use strict";return function(e,t,a){a.updateLocale=function(e,t){var n=a.Ls[e];if(n)return(t?Object.keys(t):[]).forEach(function(e){n[e]=t[e]}),n}}}()},function(e,t,n){e.exports=function(e,t,n){function a(e,t,n,a,r){var o,e=e.name?e:e.$locale(),t=s(e[t]),n=s(e[n]),i=t||n.map(function(e){return e.slice(0,a)});return r?(o=e.weekStart,i.map(function(e,t){return i[(t+(o||0))%7]})):i}function r(){return n.Ls[n.locale()]}function o(e,t){return e.formats[t]||e.formats[t.toUpperCase()].replace(/(\[[^\]]+])|(MMMM|MM|DD|dddd)/g,function(e,t,n){return t||n.slice(1)})}var t=t.prototype,s=function(e){return e&&(e.indexOf?e:e.s)};t.localeData=function(){return function(){var t=this;return{months:function(e){return e?e.format("MMMM"):a(t,"months")},monthsShort:function(e){return e?e.format("MMM"):a(t,"monthsShort","months",3)},firstDayOfWeek:function(){return t.$locale().weekStart||0},weekdays:function(e){return e?e.format("dddd"):a(t,"weekdays")},weekdaysMin:function(e){return e?e.format("dd"):a(t,"weekdaysMin","weekdays",2)},weekdaysShort:function(e){return e?e.format("ddd"):a(t,"weekdaysShort","weekdays",3)},longDateFormat:function(e){return o(t.$locale(),e)},meridiem:this.$locale().meridiem,ordinal:this.$locale().ordinal}}.bind(this)()},n.localeData=function(){var t=r();return{firstDayOfWeek:function(){return t.weekStart||0},weekdays:function(){return n.weekdays()},weekdaysShort:function(){return n.weekdaysShort()},weekdaysMin:function(){return n.weekdaysMin()},months:function(){return n.months()},monthsShort:function(){return n.monthsShort()},longDateFormat:function(e){return o(t,e)},meridiem:t.meridiem,ordinal:t.ordinal}},n.months=function(){return a(r(),"months")},n.monthsShort=function(){return a(r(),"monthsShort","months",3)},n.weekdays=function(e){return a(r(),"weekdays",null,null,e)},n.weekdaysShort=function(e){return a(r(),"weekdaysShort","weekdays",3,e)},n.weekdaysMin=function(e){return a(r(),"weekdaysMin","weekdays",2,e)}}},function(e,t,n){e.exports=function(){"use strict";var i="month",s="quarter";return function(e,t){var n=t.prototype;n.quarter=function(e){return this.$utils().u(e)?Math.ceil((this.month()+1)/3):this.month(this.month()%3+3*(e-1))};var a=n.add;n.add=function(e,t){return e=Number(e),this.$utils().p(t)===s?this.add(3*e,i):a.bind(this)(e,t)};var o=n.startOf;n.startOf=function(e,t){var n=this.$utils(),a=!!n.u(t)||t;if(n.p(e)===s){var r=this.quarter()-1;return a?this.month(3*r).startOf(i).startOf("day"):this.month(3*r+2).endOf(i).endOf("day")}return o.bind(this)(e,t)}}}()},function(e,t,n){e.exports=function(){"use strict";return function(e,t){var n=t.prototype,o=n.format;n.format=function(e){var t=this,n=this.$locale();if(!this.isValid())return o.bind(this)(e);var a=this.$utils(),r=(e||"YYYY-MM-DDTHH:mm:ssZ").replace(/\[([^\]]+)]|Q|wo|ww|w|WW|W|zzz|z|gggg|GGGG|Do|X|x|k{1,2}|S/g,function(e){switch(e){case"Q":return Math.ceil((t.$M+1)/3);case"Do":return n.ordinal(t.$D);case"gggg":return t.weekYear();case"GGGG":return t.isoWeekYear();case"wo":return n.ordinal(t.week(),"W");case"w":case"ww":return a.s(t.week(),"w"===e?1:2,"0");case"W":case"WW":return a.s(t.isoWeek(),"W"===e?1:2,"0");case"k":case"kk":return a.s(String(0===t.$H?24:t.$H),"k"===e?1:2,"0");case"X":return Math.floor(t.$d.getTime()/1e3);case"x":return t.$d.getTime();case"z":return"["+t.offsetName()+"]";case"zzz":return"["+t.offsetName("long")+"]";default:return e}});return o.bind(this)(r)}}}()},function(e,t,n){e.exports=function(){"use strict";var s="week",l="year";return function(e,t,i){var n=t.prototype;n.week=function(e){if(void 0===e&&(e=null),null!==e)return this.add(7*(e-this.week()),"day");var t=this.$locale().yearStart||1;if(11===this.month()&&this.date()>25){var n=i(this).startOf(l).add(1,l).date(t),a=i(this).endOf(s);if(n.isBefore(a))return 1}var r=i(this).startOf(l).date(t).startOf(s).subtract(1,"millisecond"),o=this.diff(r,s,!0);return o<0?i(this).startOf("week").week():Math.ceil(o)},n.weeks=function(e){return void 0===e&&(e=null),this.week(e)}}}()},function(e,t,n){e.exports=function(e){"use strict";function t(e){return e&&"object"==typeof e&&"default"in e?e:{default:e}}var n=t(e),a={name:"zh-cn",weekdays:"星期日_星期一_星期二_星期三_星期四_星期五_星期六".split("_"),weekdaysShort:"周日_周一_周二_周三_周四_周五_周六".split("_"),weekdaysMin:"日_一_二_三_四_五_六".split("_"),months:"一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月".split("_"),monthsShort:"1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月".split("_"),ordinal:function(e,t){return"W"===t?e+"周":e+"日"},weekStart:1,yearStart:4,formats:{LT:"HH:mm",LTS:"HH:mm:ss",L:"YYYY/MM/DD",LL:"YYYY年M月D日",LLL:"YYYY年M月D日Ah点mm分",LLLL:"YYYY年M月D日ddddAh点mm分",l:"YYYY/M/D",ll:"YYYY年M月D日",lll:"YYYY年M月D日 HH:mm",llll:"YYYY年M月D日dddd HH:mm"},relativeTime:{future:"%s内",past:"%s前",s:"几秒",m:"1 分钟",mm:"%d 分钟",h:"1 小时",hh:"%d 小时",d:"1 天",dd:"%d 天",M:"1 个月",MM:"%d 个月",y:"1 年",yy:"%d 年"},meridiem:function(e,t){var n=100*e+t;return n<600?"凌晨":n<900?"早上":n<1100?"上午":n<1300?"中午":n<1800?"下午":"晚上"}};return n.default.locale(a,null,!0),a}(n(219))},function(e,t,n){"use strict";t.__esModule=!0,t.flex=t.transition=t.animation=void 0;var r=n(215),o=n(101);function a(e){var n,a;return!!r.hasDOM&&(n=document.createElement("div"),(a=!1,o.each)(e,function(e,t){if(void 0!==n.style[t])return!(a={end:e})}),a)}var i,s;t.animation=a({WebkitAnimation:"webkitAnimationEnd",OAnimation:"oAnimationEnd",animation:"animationend"}),t.transition=a({WebkitTransition:"webkitTransitionEnd",OTransition:"oTransitionEnd",transition:"transitionend"}),t.flex=(n={display:["flex","-webkit-flex","-moz-flex","-ms-flexbox"]},!!r.hasDOM&&(i=document.createElement("div"),(s=!1,o.each)(n,function(e,t){return(0,o.each)(e,function(e){try{i.style[t]=e,s=s||i.style[t]===e}catch(e){}return!s}),!s}),s))},function(e,t,n){"use strict";t.__esModule=!0,t.getFocusNodeList=i,t.saveLastFocusNode=function(){s=document.activeElement},t.clearLastFocusNode=function(){s=null},t.backLastFocusNode=function(){if(s)try{s.focus()}catch(e){}},t.limitTabRange=function(e,t){{var n,a;t.keyCode===r.default.TAB&&(e=i(e),n=e.length-1,-1<(a=e.indexOf(document.activeElement)))&&(a=a+(t.shiftKey?-1:1),e[a=n<(a=a<0?n:a)?0:a].focus(),t.preventDefault())}};var t=n(220),r=(t=t)&&t.__esModule?t:{default:t},a=n(101);function o(e){var t=e.nodeName.toLowerCase(),n=parseInt(e.getAttribute("tabindex"),10),n=!isNaN(n)&&-1a.height)&&(r[1]=-t.top-("t"===e?t.height:0)),r},this._getParentScrollOffset=function(e){var t=0,n=0;return e&&e.offsetParent&&e.offsetParent!==document.body&&(isNaN(e.offsetParent.scrollTop)||(t+=e.offsetParent.scrollTop),isNaN(e.offsetParent.scrollLeft)||(n+=e.offsetParent.scrollLeft)),{top:t,left:n}}};var p=a;function h(e){(0,o.default)(this,h),r.call(this),this.pinElement=e.pinElement,this.baseElement=e.baseElement,this.pinFollowBaseElementWhenFixed=e.pinFollowBaseElementWhenFixed,this.container=function(e){var t=e.container,e=e.baseElement;if("undefined"==typeof document)return t;for(var n=(n=(0,i.default)(t,e))||document.body;"static"===y.dom.getStyle(n,"position");){if(!n||n===document.body)return document.body;n=n.parentNode}return n}(e),this.autoFit=e.autoFit||!1,this.align=e.align||"tl tl",this.offset=e.offset||[0,0],this.needAdjust=e.needAdjust||!1,this.isRtl=e.isRtl||!1}t.default=p,e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var w=a(n(3)),M=a(n(17)),k=n(0),S=a(k),E=a(n(19)),x=a(n(196)),C=a(n(83)),T=n(11);function a(e){return e&&e.__esModule?e:{default:e}}t.default=function(e){var t,n,a,r,o,i,s,l,u,c,d,f,p,h,m,g,y,v,_,b;return k.useState&&k.useRef&&k.useEffect?(t=void 0===(t=e.prefix)?"next-":t,r=e.animation,n=void 0===r?{in:"expandInDown",out:"expandOutUp"}:r,a=e.visible,r=e.hasMask,o=e.align,o=void 0===(s=e.points)?o?o.split(" "):void 0:s,i=e.onPosition,s=e.children,b=e.className,l=e.style,u=e.wrapperClassName,c=e.beforeOpen,d=e.onOpen,f=e.afterOpen,p=e.beforeClose,h=e.onClose,m=e.afterClose,e=(0,M.default)(e,["prefix","animation","visible","hasMask","align","points","onPosition","children","className","style","wrapperClassName","beforeOpen","onOpen","afterOpen","beforeClose","onClose","afterClose"]),g=(_=(0,k.useState)(!0))[0],y=_[1],v=(0,k.useRef)(null),_=S.default.createElement(C.default.OverlayAnimate,{visible:a,animation:n,onEnter:function(){y(!1),"function"==typeof c&&c(v.current)},onEntering:function(){"function"==typeof d&&d(v.current)},onEntered:function(){"function"==typeof f&&f(v.current)},onExit:function(){"function"==typeof p&&p(v.current)},onExiting:function(){"function"==typeof h&&h(v.current)},onExited:function(){y(!0),"function"==typeof m&&m(v.current)},timeout:300,style:l},s?(0,k.cloneElement)(s,{className:(0,E.default)([t+"overlay-inner",b,s&&s.props&&s.props.className])}):S.default.createElement("span",null)),b=(0,E.default)(((l={})[t+"overlay-wrapper v2"]=!0,l[u]=u,l.opened=a,l)),S.default.createElement(x.default,(0,w.default)({},e,{visible:a,isAnimationEnd:g,hasMask:r,wrapperClassName:b,maskClassName:t+"overlay-backdrop",maskRender:function(e){return S.default.createElement(C.default.OverlayAnimate,{visible:a,animation:!!n&&{in:"fadeIn",out:"fadeOut"},timeout:300,unmountOnExit:!0},e)},points:o,onPosition:function(e){(0,w.default)(e,{align:e.config.points}),"function"==typeof i&&i(e)},ref:v}),_)):(T.log.warning("need react version > 16.8.0"),null)},e.exports=t.default},function(n,e){function a(e,t){return n.exports=a=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(e,t){return e.__proto__=t,e},n.exports.__esModule=!0,n.exports.default=n.exports,a(e,t)}n.exports=a,n.exports.__esModule=!0,n.exports.default=n.exports},function(e,t,n){"use strict";t.__esModule=!0;var a,c=g(n(17)),d=g(n(3)),r=g(n(4)),o=g(n(7)),i=g(n(8)),l=n(0),f=g(l),p=n(25),s=n(31),u=g(n(5)),h=n(11),m=g(n(362));function g(e){return e&&e.__esModule?e:{default:e}}var y,n=h.func.noop,v=h.func.makeChain,_=h.func.bindCtx,u=(y=l.Component,(0,i.default)(b,y),b.getDerivedStateFromProps=function(e,t){return"visible"in e?(0,d.default)({},t,{visible:e.visible}):null},b.prototype.componentWillUnmount=function(){var t=this;["_timer","_hideTimer","_showTimer"].forEach(function(e){t[e]&&clearTimeout(t[e])})},b.prototype.handleVisibleChange=function(e,t,n){"visible"in this.props||this.setState({visible:e}),this.props.onVisibleChange(e,t,n)},b.prototype.handleTriggerClick=function(e){this.state.visible&&!this.props.canCloseByTrigger||this.handleVisibleChange(!this.state.visible,"fromTrigger",e)},b.prototype.handleTriggerKeyDown=function(e){var t=this.props.triggerClickKeycode;(Array.isArray(t)?t:[t]).includes(e.keyCode)&&(e.preventDefault(),this.handleTriggerClick(e))},b.prototype.handleTriggerMouseEnter=function(e){var t=this;this._mouseNotFirstOnMask=!1,this._hideTimer&&(clearTimeout(this._hideTimer),this._hideTimer=null),this._showTimer&&(clearTimeout(this._showTimer),this._showTimer=null),this.state.visible||(this._showTimer=setTimeout(function(){t.handleVisibleChange(!0,"fromTrigger",e)},this.props.delay))},b.prototype.handleTriggerMouseLeave=function(e,t){var n=this;this._showTimer&&(clearTimeout(this._showTimer),this._showTimer=null),this.state.visible&&(this._hideTimer=setTimeout(function(){n.handleVisibleChange(!1,t||"fromTrigger",e)},this.props.delay))},b.prototype.handleTriggerFocus=function(e){this.handleVisibleChange(!0,"fromTrigger",e)},b.prototype.handleTriggerBlur=function(e){this._isForwardContent||this.handleVisibleChange(!1,"fromTrigger",e),this._isForwardContent=!1},b.prototype.handleContentMouseDown=function(){this._isForwardContent=!0},b.prototype.handleContentMouseEnter=function(){clearTimeout(this._hideTimer)},b.prototype.handleContentMouseLeave=function(e){this.handleTriggerMouseLeave(e,"fromContent")},b.prototype.handleMaskMouseEnter=function(){this._mouseNotFirstOnMask||(clearTimeout(this._hideTimer),this._hideTimer=null,this._mouseNotFirstOnMask=!1)},b.prototype.handleMaskMouseLeave=function(){this._mouseNotFirstOnMask=!0},b.prototype.handleRequestClose=function(e,t){this.handleVisibleChange(!1,e,t)},b.prototype.renderTrigger=function(){var e,t,n,a,r,o,i,s=this,l=this.props,u=l.trigger,l=l.disabled,c={key:"trigger","aria-haspopup":!0,"aria-expanded":this.state.visible};return this.state.visible||(c["aria-describedby"]=void 0),l||(l=this.props.triggerType,l=Array.isArray(l)?l:[l],e=u&&u.props||{},t=e.onClick,n=e.onKeyDown,a=e.onMouseEnter,r=e.onMouseLeave,o=e.onFocus,i=e.onBlur,l.forEach(function(e){switch(e){case"click":c.onClick=v(s.handleTriggerClick,t),c.onKeyDown=v(s.handleTriggerKeyDown,n);break;case"hover":c.onMouseEnter=v(s.handleTriggerMouseEnter,a),c.onMouseLeave=v(s.handleTriggerMouseLeave,r);break;case"focus":c.onFocus=v(s.handleTriggerFocus,o),c.onBlur=v(s.handleTriggerBlur,i)}})),u&&f.default.cloneElement(u,c)},b.prototype.renderContent=function(){var t=this,e=this.props,n=e.children,e=e.triggerType,e=Array.isArray(e)?e:[e],n=l.Children.only(n),a=n.props,r=a.onMouseDown,o=a.onMouseEnter,i=a.onMouseLeave,s={key:"portal"};return e.forEach(function(e){switch(e){case"focus":s.onMouseDown=v(t.handleContentMouseDown,r);break;case"hover":s.onMouseEnter=v(t.handleContentMouseEnter,o),s.onMouseLeave=v(t.handleContentMouseLeave,i)}}),f.default.cloneElement(n,s)},b.prototype.renderPortal=function(){function e(){return(0,p.findDOMNode)(t)}var t=this,n=this.props,a=n.target,r=n.safeNode,o=n.followTrigger,i=n.triggerType,s=n.hasMask,l=n.wrapperStyle,n=(0,c.default)(n,["target","safeNode","followTrigger","triggerType","hasMask","wrapperStyle"]),u=this.props.container,r=Array.isArray(r)?[].concat(r):[r],l=(r.unshift(e),l||{});return o&&(u=function(e){return e&&e.parentNode||e},l.position="relative"),"hover"===i&&s&&(n.onMaskMouseEnter=this.handleMaskMouseEnter,n.onMaskMouseLeave=this.handleMaskMouseLeave),f.default.createElement(m.default,(0,d.default)({},n,{key:"overlay",ref:function(e){return t.overlay=e},visible:this.state.visible,target:a||e,container:u,safeNode:r,wrapperStyle:l,triggerType:i,hasMask:s,onRequestClose:this.handleRequestClose}),this.props.children&&this.renderContent())},b.prototype.render=function(){return[this.renderTrigger(),this.renderPortal()]},a=i=b,i.propTypes={children:u.default.node,trigger:u.default.element,triggerType:u.default.oneOfType([u.default.string,u.default.array]),triggerClickKeycode:u.default.oneOfType([u.default.number,u.default.array]),visible:u.default.bool,defaultVisible:u.default.bool,onVisibleChange:u.default.func,disabled:u.default.bool,autoFit:u.default.bool,delay:u.default.number,canCloseByTrigger:u.default.bool,target:u.default.any,safeNode:u.default.any,followTrigger:u.default.bool,container:u.default.any,hasMask:u.default.bool,wrapperStyle:u.default.object,rtl:u.default.bool,v2:u.default.bool,placement:u.default.string,placementOffset:u.default.number,autoAdjust:u.default.bool},i.defaultProps={triggerType:"hover",triggerClickKeycode:[h.KEYCODE.SPACE,h.KEYCODE.ENTER],defaultVisible:!1,onVisibleChange:n,disabled:!1,autoFit:!1,delay:200,canCloseByTrigger:!0,followTrigger:!1,container:function(){return document.body},rtl:!1},a);function b(e){(0,r.default)(this,b);var t=(0,o.default)(this,y.call(this,e));return t.state={visible:void 0===e.visible?e.defaultVisible:e.visible},_(t,["handleTriggerClick","handleTriggerKeyDown","handleTriggerMouseEnter","handleTriggerMouseLeave","handleTriggerFocus","handleTriggerBlur","handleContentMouseEnter","handleContentMouseLeave","handleContentMouseDown","handleRequestClose","handleMaskMouseEnter","handleMaskMouseLeave"]),t}u.displayName="Popup",t.default=(0,s.polyfill)(u),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var L=a(n(3)),O=a(n(17)),D=n(0),N=a(D),P=a(n(19)),j=a(n(196)),Y=a(n(83)),I=n(11);function a(e){return e&&e.__esModule?e:{default:e}}t.default=function(r){var e,t,o,n,a,i,s,l,u,c,d,f,p,h,m,g,y,v,_,b,w,M,k,S,E,x,C,T;return D.useState&&D.useRef&&D.useEffect?(e=void 0===(e=r.prefix)?"next-":e,E=r.animation,t=void 0===E?{in:"expandInDown",out:"expandOutUp"}:E,E=r.defaultVisible,x=r.onVisibleChange,o=void 0===x?function(){}:x,x=r.trigger,n=void 0===(n=r.triggerType)?"hover":n,C=r.overlay,a=r.onPosition,T=r.children,i=r.className,s=r.style,l=r.wrapperClassName,u=r.triggerClickKeycode,c=r.align,d=r.beforeOpen,f=r.onOpen,p=r.afterOpen,h=r.beforeClose,m=r.onClose,g=r.afterClose,y=(0,O.default)(r,["prefix","animation","defaultVisible","onVisibleChange","trigger","triggerType","overlay","onPosition","children","className","style","wrapperClassName","triggerClickKeycode","align","beforeOpen","onOpen","afterOpen","beforeClose","onClose","afterClose"]),E=(0,D.useState)(E),v=E[0],_=E[1],E=(0,D.useState)(t),b=E[0],w=E[1],M=(E=(0,D.useState)(!0))[0],k=E[1],S=(0,D.useRef)(null),(0,D.useEffect)(function(){"visible"in r&&_(r.visible)},[r.visible]),(0,D.useEffect)(function(){"animation"in r&&b!==t&&w(t)},[t]),E=C?T:x,x=N.default.createElement(Y.default.OverlayAnimate,{visible:v,animation:b,timeout:200,onEnter:function(){k(!1),"function"==typeof d&&d(S.current)},onEntering:function(){"function"==typeof f&&f(S.current)},onEntered:function(){"function"==typeof p&&p(S.current)},onExit:function(){"function"==typeof h&&h(S.current)},onExiting:function(){"function"==typeof m&&m(S.current)},onExited:function(){k(!0),"function"==typeof g&&g(S.current)},style:s},(x=C||T)?(0,D.cloneElement)(x,{className:(0,P.default)([e+"overlay-inner",i,x&&x.props&&x.props.className])}):N.default.createElement("span",null)),C=(0,P.default)(((s={})[e+"overlay-wrapper v2"]=!0,s[l]=l,s.opened=v,s)),T={},c&&(T.points=c.split(" ")),N.default.createElement(j.default.Popup,(0,L.default)({},y,T,{wrapperClassName:C,overlay:x,visible:v,isAnimationEnd:M,triggerType:n,onVisibleChange:function(e){for(var t=arguments.length,n=Array(1 16.8.0"),null)},e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var l=a(n(3)),u=a(n(17)),o=n(0),c=a(o),i=a(n(25)),s=a(n(6)),d=a(n(83)),f=a(n(165)),p=n(11);function a(e){return e&&e.__esModule?e:{default:e}}var h={top:8,maxCount:0,duration:3e3},m=s.default.config(function(e){var t=e.prefix,s=void 0===t?"next-":t,t=e.dataSource,a=void 0===t?[]:t,r=(0,o.useState)()[1];return a.forEach(function(n){n.timer||(n.timer=setTimeout(function(){var e,t=a.indexOf(n);-1a&&y.shift(),i.default.render(c.default.createElement(s.default,s.default.getContext(),c.default.createElement(m,{dataSource:y})),g),{key:n,close:function(){r.timer&&clearTimeout(r.timer);var e=y.indexOf(r);-1 16.8.0")}},e.exports=t.default},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";n(565)},function(e,t,n){},function(e,t,n){},function(e,t,n){"use strict";t.__esModule=!0,t.default=void 0;var p=l(n(3)),r=l(n(4)),o=l(n(7)),a=l(n(8)),h=l(n(42)),m=l(n(0)),i=l(n(5)),g=l(n(19)),y=n(11),s=l(n(28)),v=l(n(368));function l(e){return e&&e.__esModule?e:{default:e}}function _(e,r){var o=r.size,i=r.device,s=r.labelAlign,l=r.labelTextAlign,u=r.labelCol,c=r.wrapperCol,d=r.responsive,f=r.colon;return m.default.Children.map(e,function(e){var t,n,a;return y.obj.isReactFragment(e)?_(e.props.children,r):e&&-1<["function","object"].indexOf((0,h.default)(e.type))&&"form_item"===e.type._typeMark?(t={labelCol:e.props.labelCol||u,wrapperCol:e.props.wrapperCol||c,labelAlign:e.props.labelAlign||("phone"===i?"top":s),labelTextAlign:e.props.labelTextAlign||l,colon:"colon"in e.props?e.props.colon:f,size:e.props.size||o,responsive:d},m.default.cloneElement(e,(n=t,a={},Object.keys(n).forEach(function(e){void 0!==n[e]&&(a[e]=n[e])}),a))):e})}u=m.default.Component,(0,a.default)(b,u),b.prototype.getChildContext=function(){return{_formField:this.props.field||this._formField,_formSize:this.props.size,_formDisabled:this.props.disabled,_formPreview:this.props.isPreview,_formFullWidth:this.props.fullWidth,_formLabelForErrorMessage:this.props.useLabelForErrorMessage}},b.prototype.componentDidUpdate=function(e){var t=this.props;this._formField&&("value"in t&&t.value!==e.value&&this._formField.setValues(t.value),"error"in t)&&t.error!==e.error&&this._formField.setValues(t.error)},b.prototype.render=function(){var e=this.props,t=e.className,n=e.inline,a=e.size,r=(e.device,e.labelAlign,e.labelTextAlign,e.onSubmit),o=e.children,i=(e.labelCol,e.wrapperCol,e.style),s=e.prefix,l=e.rtl,u=e.isPreview,c=e.component,d=e.responsive,f=e.gap,n=(e.colon,(0,g.default)(((e={})[s+"form"]=!0,e[s+"inline"]=n,e[""+s+a]=a,e[s+"form-responsive-grid"]=d,e[s+"form-preview"]=u,e[t]=!!t,e))),a=_(o,this.props);return m.default.createElement(c,(0,p.default)({role:"grid"},y.obj.pickOthers(b.propTypes,this.props),{className:n,style:i,dir:l?"rtl":void 0,onSubmit:r}),d?m.default.createElement(v.default,{gap:f},a):a)},a=n=b,n.propTypes={prefix:i.default.string,inline:i.default.bool,size:i.default.oneOf(["large","medium","small"]),fullWidth:i.default.bool,labelAlign:i.default.oneOf(["top","left","inset"]),labelTextAlign:i.default.oneOf(["left","right"]),field:i.default.any,saveField:i.default.func,labelCol:i.default.object,wrapperCol:i.default.object,onSubmit:i.default.func,children:i.default.any,className:i.default.string,style:i.default.object,value:i.default.object,onChange:i.default.func,component:i.default.oneOfType([i.default.string,i.default.func]),fieldOptions:i.default.object,rtl:i.default.bool,device:i.default.oneOf(["phone","tablet","desktop"]),responsive:i.default.bool,isPreview:i.default.bool,useLabelForErrorMessage:i.default.bool,colon:i.default.bool,disabled:i.default.bool,gap:i.default.oneOfType([i.default.arrayOf(i.default.number),i.default.number])},n.defaultProps={prefix:"next-",onSubmit:function(e){e.preventDefault()},size:"medium",labelAlign:"left",onChange:y.func.noop,component:"form",saveField:y.func.noop,device:"desktop",colon:!1,disabled:!1},n.childContextTypes={_formField:i.default.object,_formSize:i.default.string,_formDisabled:i.default.bool,_formPreview:i.default.bool,_formFullWidth:i.default.bool,_formLabelForErrorMessage:i.default.bool};var u,n=a;function b(e){(0,r.default)(this,b);var t,n,a=(0,o.default)(this,u.call(this,e));return a.onChange=function(e,t){a.props.onChange(a._formField.getValues(),{name:e,value:t,field:a._formField})},a._formField=null,!1!==e.field&&(t=(0,p.default)({},e.fieldOptions,{onChange:a.onChange}),e.field?(a._formField=e.field,n=a._formField.options.onChange,t.onChange=y.func.makeChain(n,a.onChange),a._formField.setOptions&&a._formField.setOptions(t)):("value"in e&&(t.values=e.value),a._formField=new s.default(a,t)),e.locale&&e.locale.Validate&&a._formField.setOptions({messages:e.locale.Validate}),e.saveField(a._formField)),a}n.displayName="Form",t.default=n,e.exports=t.default},function(e,t,n){"use strict";var a=n(91),m=(Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,a(n(169))),i=a(n(570)),r=a(n(170)),o=a(n(116)),b=a(n(171)),w=a(n(77)),s=a(n(366)),l=a(n(367)),g=a(n(577)),M=n(586),u={state:"",valueName:"value",trigger:"onChange",inputValues:[]},a=function(){function a(e){var t=this,n=1e.length)&&(t=e.length);for(var n=0,a=new Array(t);n=a.length?n:(o=a[r],n=e(t&&t[o],n,a,r+1),t?Array.isArray(t)?((a=[].concat(t))[o]=n,a):(0,l.default)({},t,(0,i.default)({},o,n)):((r=isNaN(o)?{}:[])[o]=n,r))};t=function(){};void 0!==e&&e.env,n.warning=t}.call(this,r(103))},function(e,t,n){"use strict";t.__esModule=!0,t.cloneAndAddKey=function(e){{var t;if(e&&(0,a.isValidElement)(e))return t=e.key||"error",(0,a.cloneElement)(e,{key:t})}return e},t.scrollToFirstError=function(e){var t=e.errorsGroup,n=e.options,a=e.instance;if(t&&n.scrollToFirstError){var r,o=void 0,i=void 0;for(r in t)if(t.hasOwnProperty(r)){var s=u.default.findDOMNode(a[r]);if(!s)return;var l=s.offsetTop;(void 0===i||l), use instead of.'),S.default.cloneElement(e,{className:t,size:d||C(r)})):(0,k.isValidElement)(e)?e:S.default.createElement("span",{className:a+"btn-helper"},e)}),t=c,_=(0,b.default)({},x.obj.pickOthers(Object.keys(T.propTypes),e),{type:o,disabled:p,onClick:h,className:(0,E.default)(n)});return"button"!==t&&(delete _.type,_.disabled)&&(delete _.onClick,_.href)&&delete _.href,S.default.createElement(t,(0,b.default)({},_,{dir:g?"rtl":void 0,onMouseUp:this.onMouseUp,ref:this.buttonRefHandler}),s,u)},a=n=T,n.propTypes=(0,b.default)({},s.default.propTypes,{prefix:r.default.string,rtl:r.default.bool,type:r.default.oneOf(["primary","secondary","normal"]),size:r.default.oneOf(["small","medium","large"]),icons:r.default.shape({loading:r.default.node}),iconSize:r.default.oneOfType([r.default.oneOf(["xxs","xs","small","medium","large","xl","xxl","xxxl","inherit"]),r.default.number]),htmlType:r.default.oneOf(["submit","reset","button"]),component:r.default.oneOf(["button","a","div","span"]),loading:r.default.bool,ghost:r.default.oneOf([!0,!1,"light","dark"]),text:r.default.bool,warning:r.default.bool,disabled:r.default.bool,onClick:r.default.func,className:r.default.string,onMouseUp:r.default.func,children:r.default.node}),n.defaultProps={prefix:"next-",type:"normal",size:"medium",icons:{},htmlType:"button",component:"button",loading:!1,ghost:!1,text:!1,warning:!1,disabled:!1,onClick:function(){}};var u,s=a;function T(){var e,t;(0,o.default)(this,T);for(var n=arguments.length,a=Array(n),r=0;ra[r])return!0;if(n[r] 0, or `null`');if(U(i,"numericSeparator")&&"boolean"!=typeof i.numericSeparator)throw new TypeError('option "numericSeparator", if provided, must be `true` or `false`');var t=i.numericSeparator;if(void 0===n)return"undefined";if(null===n)return"null";if("boolean"==typeof n)return n?"true":"false";if("string"==typeof n)return function e(t,n){if(t.length>n.maxStringLength)return a=t.length-n.maxStringLength,a="... "+a+" more character"+(1"}if(z(n))return 0===n.length?"[]":(l=$(n,m),h&&!function(e){for(var t=0;t "+m(e,n))}),ne("Map",_.call(n),u,h)):function(e){if(w&&e&&"object"==typeof e)try{w.call(e);try{_.call(e)}catch(e){return 1}return e instanceof Set}catch(e){}return}(n)?(c=[],M&&M.call(n,function(e){c.push(m(e,n))}),ne("Set",w.call(n),c,h)):function(e){if(k&&e&&"object"==typeof e)try{k.call(e,k);try{S.call(e,S)}catch(e){return 1}return e instanceof WeakMap}catch(e){}return}(n)?q("WeakMap"):function(e){if(S&&e&&"object"==typeof e)try{S.call(e,S);try{k.call(e,k)}catch(e){return 1}return e instanceof WeakSet}catch(e){}return}(n)?q("WeakSet"):function(e){if(E&&e&&"object"==typeof e)try{return E.call(e),1}catch(e){}return}(n)?q("WeakRef"):"[object Number]"!==V(d=n)||j&&"object"==typeof d&&j in d?function(e){if(e&&"object"==typeof e&&D)try{return D.call(e),1}catch(e){}return}(n)?K(m(D.call(n))):"[object Boolean]"!==V(t=n)||j&&"object"==typeof t&&j in t?"[object String]"!==V(e=n)||j&&"object"==typeof e&&j in e?("[object Date]"!==V(t=n)||j&&"object"==typeof t&&j in t)&&!W(n)?(e=$(n,m),t=I?I(n)===Object.prototype:n instanceof Object||n.constructor===Object,f=n instanceof Object?"":"null prototype",p=!t&&j&&Object(n)===n&&j in n?x.call(V(n),8,-1):f?"Object":"",t=(!t&&"function"==typeof n.constructor&&n.constructor.name?n.constructor.name+" ":"")+(p||f?"["+O.call(L.call([],p||[],f||[]),": ")+"] ":""),0===e.length?t+"{}":h?t+"{"+G(e,h)+"}":t+"{ "+O.call(e,", ")+" }"):String(n):K(m(String(n))):K(J.call(n)):K(m(Number(n)))};var l=Object.prototype.hasOwnProperty||function(e){return e in this};function U(e,t){return l.call(e,t)}function V(e){return i.call(e)}function ee(e,t){if(e.indexOf)return e.indexOf(t);for(var n=0,a=e.length;nu&&!d&&(o=o.slice(0,u),e=C.default.createElement(m.default,{key:"_count",type:"primary",size:p,animation:!1},c(a,t))),0, as child."),a.push(t),e.props.children)&&(t.children=n(e.props.children))}),a}(e.children):t},N.prototype.fetchInfoFromBinaryChildren=function(e){function r(e,t){return t=t||0,e.forEach(function(e){e.children?t=r(e.children,t):t+=1}),t}var a=!1,o=[],i=[],e=(function t(){var e=0r.tRight&&(e=r.tRight,r.changedPageX=r.tRight-r.startLeft),e-r.cellLefto.clientHeight,o.scrollWidth,o.clientWidth,o={},e||(o[r]=0,o[a]=0),+i&&(o.marginBottom=-i,o.paddingBottom=i,e)&&(o[a]=i),h.dom.setStyle(this.headerNode,o)),n&&!this.props.lockType&&this.headerNode&&(r=this.headerNode.querySelector("."+t+"table-header-fixer"),e=h.dom.getStyle(this.headerNode,"height"),a=h.dom.getStyle(this.headerNode,"paddingBottom"),h.dom.setStyle(r,{width:i,height:e-a}))},o.prototype.render=function(){var e=this.props,t=e.components,n=e.className,a=e.prefix,r=e.fixedHeader,o=e.lockType,i=e.dataSource,e=(e.maxBodyHeight,(0,u.default)(e,["components","className","prefix","fixedHeader","lockType","dataSource","maxBodyHeight"]));return r&&((t=(0,l.default)({},t)).Header||(t.Header=m.default),t.Body||(t.Body=g.default),t.Wrapper||(t.Wrapper=y.default),n=(0,p.default)(((r={})[a+"table-fixed"]=!0,r[a+"table-wrap-empty"]=!i.length,r[n]=n,r))),d.default.createElement(s,(0,l.default)({},e,{dataSource:i,lockType:o,components:t,className:n,prefix:a}))},o}(d.default.Component),n.FixedHeader=m.default,n.FixedBody=g.default,n.FixedWrapper=y.default,n.propTypes=(0,l.default)({hasHeader:r.default.bool,fixedHeader:r.default.bool,maxBodyHeight:r.default.oneOfType([r.default.number,r.default.string])},s.propTypes),n.defaultProps=(0,l.default)({},s.defaultProps,{hasHeader:!0,fixedHeader:!1,maxBodyHeight:200,components:{},refs:{},prefix:"next-"}),n.childContextTypes={fixedHeader:r.default.bool,getNode:r.default.func,onFixedScrollSync:r.default.func,getTableInstanceForFixed:r.default.func,maxBodyHeight:r.default.oneOfType([r.default.number,r.default.string])};var t,n=t;return n.displayName="FixedTable",(0,o.statics)(n,s),n};var d=s(n(0)),r=s(n(5)),f=n(25),p=s(n(19)),h=n(11),m=s(n(136)),g=s(n(406)),y=s(n(137)),o=n(70);function s(e){return e&&e.__esModule?e:{default:e}}e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var i=o(n(17)),f=o(n(3)),r=o(n(4)),s=o(n(7)),l=o(n(8)),u=(t.default=function(o){e=t=function(n){function a(e,t){(0,r.default)(this,a);var d=(0,s.default)(this,n.call(this,e,t));return d.addSelection=function(e){var t=d.props,n=t.prefix,a=t.rowSelection,t=t.size,a=a.columnProps&&a.columnProps()||{};e.find(function(e){return"selection"===e.key})||e.unshift((0,f.default)({key:"selection",title:d.renderSelectionHeader.bind(d),cell:d.renderSelectionBody.bind(d),width:"small"===t?34:50,className:n+"table-selection "+n+"table-prerow",__normalized:!0},a))},d.renderSelectionHeader=function(){var e=d.selectAllRow,t={},n=d.props,a=n.rowSelection,r=n.primaryKey,o=n.dataSource,i=n.entireDataSource,n=n.locale,s=d.state.selectedRowKeys,l=a.mode||"multiple",u=!!s.length,c=!1,i=(d.flatDataSource(i||o).filter(function(e,t){return!a.getProps||!(a.getProps(e,t)||{}).disabled}).map(function(e){return e[r]}).forEach(function(e){-1===s.indexOf(e)?u=!1:c=!0}),t.onClick=b(function(e){e.stopPropagation()},t.onClick),a.titleProps&&a.titleProps()||{});return u&&(c=!1),["multiple"===l?p.default.createElement(h.default,(0,f.default)({key:"_total",indeterminate:c,"aria-label":n.selectAll,checked:u,onChange:e},t,i)):null,a.titleAddons&&a.titleAddons()]},d.renderSelectionBody=function(e,t,n){var a=d.props,r=a.rowSelection,a=a.primaryKey,o=d.state.selectedRowKeys,i=r.mode||"multiple",o=-1l.length&&(u=o),w(u.filter(function(e){return-1=Math.max(a-y,0)&&od.clientHeight;this.isLock()?(e=this.bodyLeftNode,t=this.bodyRightNode,n=this.getWrapperNode("right"),a=f?c:0,d=d.offsetHeight-c,f||(r[l]=0,r[u]=0),+c?(r.marginBottom=-c,r.paddingBottom=c):(r.marginBottom=-20,r.paddingBottom=20),d={"max-height":d},o||+c||(d[u]=0),+c&&(d[u]=-c),e&&g.dom.setStyle(e,d),t&&g.dom.setStyle(t,d),n&&+c&&g.dom.setStyle(n,i?"left":"right",a+"px")):(r.marginBottom=-c,r.paddingBottom=c,r[u]=0,f||(r[l]=0)),s&&g.dom.setStyle(s,r)},a.prototype.adjustHeaderSize=function(){var o=this;this.isLock()&&this.tableInc.groupChildren.forEach(function(e,t){var n=o.tableInc.groupChildren[t].length-1,n=o.getHeaderCellNode(t,n),a=o.getHeaderCellNode(t,0),r=o.getHeaderCellNode(t,0,"right"),t=o.getHeaderCellNode(t,0,"left");n&&r&&(n=n.offsetHeight,g.dom.setStyle(r,"height",n),setTimeout(function(){var e=o.tableRightInc.affixRef;return e&&e.getInstance()&&e.getInstance().updatePosition()})),a&&t&&(r=a.offsetHeight,g.dom.setStyle(t,"height",r),setTimeout(function(){var e=o.tableLeftInc.affixRef;return e&&e.getInstance()&&e.getInstance().updatePosition()}))})},a.prototype.adjustRowHeight=function(){var n=this;this.isLock()&&this.tableInc.props.dataSource.forEach(function(e,t){t=""+("object"===(void 0===e?"undefined":(0,r.default)(e))&&"__rowIndex"in e?e.__rowIndex:t)+(e.__expanded?"_expanded":"");n.setRowHeight(t,"left"),n.setRowHeight(t,"right")})},a.prototype.setRowHeight=function(e,t){var t=this.getRowNode(e,t),e=this.getRowNode(e),e=(M?e&&e.offsetHeight:e&&parseFloat(getComputedStyle(e).height))||"auto",n=(M?t&&t.offsetHeight:t&&parseFloat(getComputedStyle(t).height))||"auto";t&&e!==n&&g.dom.setStyle(t,"height",e)},a.prototype.getWrapperNode=function(e){e=e?e.charAt(0).toUpperCase()+e.substr(1):"";try{return(0,u.findDOMNode)(this["lock"+e+"El"])}catch(e){return null}},a.prototype.getRowNode=function(e,t){t=this["table"+(t=t?t.charAt(0).toUpperCase()+t.substr(1):"")+"Inc"];try{return(0,u.findDOMNode)(t.getRowRef(e))}catch(e){return null}},a.prototype.getHeaderCellNode=function(e,t,n){n=this["table"+(n=n?n.charAt(0).toUpperCase()+n.substr(1):"")+"Inc"];try{return(0,u.findDOMNode)(n.getHeaderCellRef(e,t))}catch(e){return null}},a.prototype.getCellNode=function(e,t,n){n=this["table"+(n=n?n.charAt(0).toUpperCase()+n.substr(1):"")+"Inc"];try{return(0,u.findDOMNode)(n.getCellRef(e,t))}catch(e){return null}},a.prototype.render=function(){var e,t=this.props,n=(t.children,t.columns,t.prefix),a=t.components,r=t.className,o=t.dataSource,i=t.tableWidth,t=(0,f.default)(t,["children","columns","prefix","components","className","dataSource","tableWidth"]),s=this.normalizeChildrenState(this.props),l=s.lockLeftChildren,u=s.lockRightChildren,s=s.children,c={left:this.getFlatenChildrenLength(l),right:this.getFlatenChildrenLength(u),origin:this.getFlatenChildrenLength(s)};return this._notNeedAdjustLockLeft&&(l=[]),this._notNeedAdjustLockRight&&(u=[]),this.lockLeftChildren=l,this.lockRightChildren=u,this.isOriginLock()?((a=(0,p.default)({},a)).Body=a.Body||v.default,a.Header=a.Header||_.default,a.Wrapper=a.Wrapper||b.default,a.Row=a.Row||y.default,r=(0,m.default)(((e={})[n+"table-lock"]=!0,e[n+"table-wrap-empty"]=!o.length,e[r]=r,e)),e=[h.default.createElement(d,(0,p.default)({},t,{dataSource:o,key:"lock-left",columns:l,className:n+"table-lock-left",lengths:c,prefix:n,lockType:"left",components:a,ref:this.saveLockLeftRef,loading:!1,"aria-hidden":!0})),h.default.createElement(d,(0,p.default)({},t,{dataSource:o,key:"lock-right",columns:u,className:n+"table-lock-right",lengths:c,prefix:n,lockType:"right",components:a,ref:this.saveLockRightRef,loading:!1,"aria-hidden":!0}))],h.default.createElement(d,(0,p.default)({},t,{tableWidth:i,dataSource:o,columns:s,prefix:n,lengths:c,wrapperContent:e,components:a,className:r}))):h.default.createElement(d,this.props)},a}(h.default.Component),t.LockRow=y.default,t.LockBody=v.default,t.LockHeader=_.default,t.propTypes=(0,p.default)({scrollToCol:a.default.number,scrollToRow:a.default.number},d.propTypes),t.defaultProps=(0,p.default)({},d.defaultProps),t.childContextTypes={getTableInstance:a.default.func,getLockNode:a.default.func,onLockBodyScroll:a.default.func,onRowMouseEnter:a.default.func,onRowMouseLeave:a.default.func};var e,t=e;return t.displayName="LockTable",(0,w.statics)(t,d),t},n(0)),h=d(l),u=n(25),a=d(n(5)),m=d(n(19)),c=d(n(182)),g=n(11),y=d(n(184)),v=d(n(407)),_=d(n(408)),b=d(n(137)),w=n(70);function d(e){return e&&e.__esModule?e:{default:e}}var M=g.env.ieVersion;function k(e){return function n(e){return e.map(function(e){var t=(0,p.default)({},e);return e.children&&(e.children=n(e.children)),t})}(e)}e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var l=s(n(17)),u=s(n(3)),r=s(n(4)),o=s(n(7)),i=s(n(8)),c=(t.default=function(s){e=t=function(n){function a(e,t){(0,r.default)(this,a);var c=(0,o.default)(this,n.call(this,e));return c.state={},c.updateOffsetArr=function(){var e=c.splitChildren||{},t=e.lockLeftChildren,n=e.lockRightChildren,e=e.originChildren,a=c.getFlatenChildren(t).length,r=c.getFlatenChildren(n).length,e=a+r+c.getFlatenChildren(e).length,a=0r.top-e.offset?(t?(l.position="absolute",l.top=a-(r.top-e.offset),u="relative"):(l.position="fixed",l.top=e.offset+n.top),c._setAffixStyle(l,!0),c._setContainerStyle(s)):e.bottom&&a{e=new Date(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}},function(e,t,n){"use strict";const a=n(186);class r extends Date{constructor(e){super(e+"Z"),this.isFloating=!0}toISOString(){return`${this.getUTCFullYear()}-${a(2,this.getUTCMonth()+1)}-`+a(2,this.getUTCDate())+"T"+(`${a(2,this.getUTCHours())}:${a(2,this.getUTCMinutes())}:${a(2,this.getUTCSeconds())}.`+a(3,this.getUTCMilliseconds()))}}e.exports=e=>{e=new r(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}},function(a,e,r){"use strict";!function(e){const t=r(186);class n extends e.Date{constructor(e){super(e),this.isDate=!0}toISOString(){return`${this.getUTCFullYear()}-${t(2,this.getUTCMonth()+1)}-`+t(2,this.getUTCDate())}}a.exports=e=>{e=new n(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}}.call(this,r(65))},function(e,t,n){"use strict";const a=n(186);class r extends Date{constructor(e){super(`0000-01-01T${e}Z`),this.isTime=!0}toISOString(){return`${a(2,this.getUTCHours())}:${a(2,this.getUTCMinutes())}:${a(2,this.getUTCSeconds())}.`+a(3,this.getUTCMilliseconds())}}e.exports=e=>{e=new r(e);if(isNaN(e))throw new TypeError("Invalid Datetime");return e}},function(e,t,n){"use strict";!function(s){e.exports=function(r,e){e=e||{};const n=e.blocksize||40960,o=new t;return new Promise((e,t)=>{s(i,0,n,e,t)});function i(e,t,n,a){if(e>=r.length)try{return n(o.finish())}catch(e){return a(l(e,r))}try{o.parse(r.slice(e,e+t)),s(i,e+t,t,n,a)}catch(e){a(l(e,r))}}};const t=n(185),l=n(187)}.call(this,n(412).setImmediate)},function(e,t,n){!function(e,p){!function(n,o){"use strict";var a,i,s,r,l,u,t,e;function c(e){delete i[e]}function d(e){if(s)setTimeout(d,0,e);else{var t=i[e];if(t){s=!0;try{var n=t,a=n.callback,r=n.args;switch(r.length){case 0:a();break;case 1:a(r[0]);break;case 2:a(r[0],r[1]);break;case 3:a(r[0],r[1],r[2]);break;default:a.apply(o,r)}}finally{c(e),s=!1}}}}function f(){function e(e){e.source===n&&"string"==typeof e.data&&0===e.data.indexOf(t)&&d(+e.data.slice(t.length))}var t="setImmediate$"+Math.random()+"$";n.addEventListener?n.addEventListener("message",e,!1):n.attachEvent("onmessage",e),l=function(e){n.postMessage(t+e,"*")}}n.setImmediate||(a=1,s=!(i={}),r=n.document,e=(e=Object.getPrototypeOf&&Object.getPrototypeOf(n))&&e.setTimeout?e:n,"[object process]"==={}.toString.call(n.process)?l=function(e){p.nextTick(function(){d(e)})}:!function(){var e,t;if(n.postMessage&&!n.importScripts)return e=!0,t=n.onmessage,n.onmessage=function(){e=!1},n.postMessage("","*"),n.onmessage=t,e}()?l=n.MessageChannel?((t=new MessageChannel).port1.onmessage=function(e){d(e.data)},function(e){t.port2.postMessage(e)}):r&&"onreadystatechange"in r.createElement("script")?(u=r.documentElement,function(e){var t=r.createElement("script");t.onreadystatechange=function(){d(e),t.onreadystatechange=null,u.removeChild(t),t=null},u.appendChild(t)}):function(e){setTimeout(d,0,e)}:f(),e.setImmediate=function(e){"function"!=typeof e&&(e=new Function(""+e));for(var t=new Array(arguments.length-1),n=0;n{let n,a=!1,r=!1;function o(){if(a=!0,!n)try{e(l.finish())}catch(e){t(e)}}function i(e){r=!0,t(e)}s.once("end",o),s.once("error",i),function e(){n=!0;let t;for(;null!==(t=s.read());)try{l.parse(t)}catch(e){return i(e)}n=!1;if(a)return o();if(r)return;s.once("readable",e)}()})}(e):function(){const a=new o;return new r.Transform({objectMode:!0,transform(e,t,n){try{a.parse(e.toString(t))}catch(e){this.emit("error",e)}n()},flush(e){try{this.push(a.finish())}catch(e){this.emit("error",e)}e()}})}()};const r=n(704),o=n(185)},function(e,t,n){e.exports=a;var c=n(188).EventEmitter;function a(){c.call(this)}n(105)(a,c),a.Readable=n(189),a.Writable=n(714),a.Duplex=n(715),a.Transform=n(716),a.PassThrough=n(717),(a.Stream=a).prototype.pipe=function(t,e){var n=this;function a(e){t.writable&&!1===t.write(e)&&n.pause&&n.pause()}function r(){n.readable&&n.resume&&n.resume()}n.on("data",a),t.on("drain",r),t._isStdio||e&&!1===e.end||(n.on("end",i),n.on("close",s));var o=!1;function i(){o||(o=!0,t.end())}function s(){o||(o=!0,"function"==typeof t.destroy&&t.destroy())}function l(e){if(u(),0===c.listenerCount(this,"error"))throw e}function u(){n.removeListener("data",a),t.removeListener("drain",r),n.removeListener("end",i),n.removeListener("close",s),n.removeListener("error",l),t.removeListener("error",l),n.removeListener("end",u),n.removeListener("close",u),t.removeListener("close",u)}return n.on("error",l),t.on("error",l),n.on("end",u),n.on("close",u),t.on("close",u),t.emit("pipe",n),t}},function(e,t){var n={}.toString;e.exports=Array.isArray||function(e){return"[object Array]"==n.call(e)}},function(e,t,n){"use strict";t.byteLength=function(e){var e=c(e),t=e[0],e=e[1];return 3*(t+e)/4-e},t.toByteArray=function(e){var t,n,a=c(e),r=a[0],a=a[1],o=new u(function(e,t){return 3*(e+t)/4-t}(r,a)),i=0,s=0>16&255,o[i++]=t>>8&255,o[i++]=255&t;2===a&&(t=l[e.charCodeAt(n)]<<2|l[e.charCodeAt(n+1)]>>4,o[i++]=255&t);1===a&&(t=l[e.charCodeAt(n)]<<10|l[e.charCodeAt(n+1)]<<4|l[e.charCodeAt(n+2)]>>2,o[i++]=t>>8&255,o[i++]=255&t);return o},t.fromByteArray=function(e){for(var t,n=e.length,a=n%3,r=[],o=0,i=n-a;o>18&63]+s[e>>12&63]+s[e>>6&63]+s[63&e]}(a));return r.join("")}(e,o,i>2]+s[t<<4&63]+"==")):2==a&&(t=(e[n-2]<<8)+e[n-1],r.push(s[t>>10]+s[t>>4&63]+s[t<<2&63]+"="));return r.join("")};for(var s=[],l=[],u="undefined"!=typeof Uint8Array?Uint8Array:Array,a="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",r=0,o=a.length;r */ -t.read=function(e,t,n,a,r){var o,i,s=8*r-a-1,l=(1<>1,c=-7,d=n?r-1:0,f=n?-1:1,r=e[t+d];for(d+=f,o=r&(1<<-c)-1,r>>=-c,c+=s;0>=-c,c+=a;0>1,d=23===r?Math.pow(2,-24)-Math.pow(2,-77):0,f=a?0:o-1,p=a?1:-1,o=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,i=u):(i=Math.floor(Math.log(t)/Math.LN2),t*(a=Math.pow(2,-i))<1&&(i--,a*=2),2<=(t+=1<=i+c?d/a:d*Math.pow(2,1-c))*a&&(i++,a/=2),u<=i+c?(s=0,i=u):1<=i+c?(s=(t*a-1)*Math.pow(2,r),i+=c):(s=t*Math.pow(2,c-1)*Math.pow(2,r),i=0));8<=r;e[n+f]=255&s,f+=p,s/=256,r-=8);for(i=i<>>0),r=this.head,o=0;r;)t=r.data,n=o,t.copy(a,n),o+=r.data.length,r=r.next;return a},r),a&&a.inspect&&a.inspect.custom&&(e.exports.prototype[a.inspect.custom]=function(){var e=a.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){!function(t){function a(e){try{if(!t.localStorage)return}catch(e){return}e=t.localStorage[e];return null!=e&&"true"===String(e).toLowerCase()}e.exports=function(e,t){if(a("noDeprecation"))return e;var n=!1;return function(){if(!n){if(a("throwDeprecation"))throw new Error(t);a("traceDeprecation")?console.trace(t):console.warn(t),n=!0}return e.apply(this,arguments)}}}.call(this,n(65))},function(e,t,n){"use strict";e.exports=r;var a=n(418),e=Object.create(n(118));function r(e){if(!(this instanceof r))return new r(e);a.call(this,e)}e.inherits=n(105),e.inherits(r,a),r.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){e.exports=n(190)},function(e,t,n){e.exports=n(92)},function(e,t,n){e.exports=n(189).Transform},function(e,t,n){e.exports=n(189).PassThrough},function(e,t,n){"use strict";function u(e){return new Error("Can only stringify objects, not "+e)}function c(t){return Object.keys(t).filter(e=>p(t[e]))}function d(e){var t,n=Array.isArray(e)?[]:Object.prototype.hasOwnProperty.call(e,"__proto__")?{["__proto__"]:void 0}:{};for(t of Object.keys(e))!e[t]||"function"!=typeof e[t].toJSON||"toISOString"in e[t]?n[t]=e[t]:n[t]=e[t].toJSON();return n}function f(t,e,n){var a,r,o=c(n=d(n));r=n,a=Object.keys(r).filter(e=>!p(r[e]));const i=[],s=e||"",l=(o.forEach(e=>{var t=h(n[e]);"undefined"!==t&&"null"!==t&&i.push(s+m(e)+" = "+g(n[e],!0))}),0{i.push(function(e,t,n,a){var r=h(a);{if("array"===r)return function(e,t,n,a){var r=h((a=d(a))[0]);if("table"!==r)throw u(r);const o=e+m(n);let i="";return a.forEach(e=>{0"\\u"+function(e,t){for(;t.lengths(e).replace(/"(?="")/g,'\\"')).join("\n");return'"'===e.slice(-1)&&(e+="\\\n"),'"""\n'+e+'"""';return}case"string":return i(t);case"string-literal":return"'"+t+"'";case"integer":return y(t);case"float":n=t;return n===1/0?"inf":n===-1/0?"-inf":Object.is(n,NaN)?"nan":Object.is(n,-0)?"-0.0":([n,a]=String(n).split("."),y(n)+"."+a);case"boolean":return String(t);case"datetime":return t.toISOString();case"array":{var a=t.filter(e=>"null"!==h(e)&&"undefined"!==h(e)&&"nan"!==h(e));a=d(a);let e="[";a=a.map(e=>l(e));60{o.push(m(e)+" = "+g(r[e],!1))}),"{ "+o.join(", ")+(0=P.KEYCODE.LEFT&&t<=P.KEYCODE.DOWN&&e.preventDefault(),e=void 0,t===P.KEYCODE.RIGHT||t===P.KEYCODE.DOWN?(e=o.getNextActiveKey(!0),o.handleTriggerEvent(o.props.triggerType,e)):t!==P.KEYCODE.LEFT&&t!==P.KEYCODE.UP||(e=o.getNextActiveKey(!1),o.handleTriggerEvent(o.props.triggerType,e)))},o.state={activeKey:o.getDefaultActiveKey(e)},o}s.displayName="Tab",t.default=(0,l.polyfill)(s),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var _=d(n(3)),a=d(n(4)),r=d(n(7)),o=d(n(8)),b=d(n(0)),i=n(24),s=d(n(5)),w=d(n(18)),M=d(n(26)),l=d(n(64)),u=d(n(50)),k=(d(n(20)),d(n(83))),h=n(11),c=n(420);function d(e){return e&&e.__esModule?e:{default:e}}var f,S={float:"right",zIndex:1},E={float:"left",zIndex:1},p={dropdown:"arrow-down",prev:"arrow-left",next:"arrow-right"},m=l.default.Popup,l=(f=b.default.Component,(0,o.default)(g,f),g.prototype.componentDidMount=function(){this.props.animation||this.initialSettings(),h.events.on(window,"resize",this.onWindowResized)},g.prototype.componentDidUpdate=function(e){var t=this;clearTimeout(this.scrollTimer),this.scrollTimer=setTimeout(function(){t.scrollToActiveTab()},410),clearTimeout(this.slideTimer),this.slideTimer=setTimeout(function(){t.setSlideBtn()},410),"dropdown"!==this.props.excessMode||(0,c.tabsArrayShallowEqual)(this.props.tabs,e.tabs)||this.getDropdownItems(this.props)},g.prototype.componentWillUnmount=function(){h.events.off(window,"resize",this.onWindowResized)},g.prototype.initialSettings=function(){this.setSlideBtn(),this.getDropdownItems(this.props)},g.prototype.setOffset=function(e){var t=!(1n&&(t.current=n),this.setState(t),this.props.onPageSizeChange(e)},I.prototype.renderPageTotal=function(){var e=this.props,t=e.prefix,n=e.total,e=e.totalRender,a=this.state,r=a.currentPageSize,a=a.current;return N.default.createElement("div",{className:t+"pagination-total"},e(n,[(a-1)*r+1,a*r]))},I.prototype.renderPageItem=function(e){var t=this.props,n=t.prefix,a=t.size,r=t.link,o=t.pageNumberRender,i=t.total,s=t.pageSize,t=t.locale,l=this.state.current,i=Y(i,s),s=parseInt(e,10)===l,a={size:a,className:(0,P.default)(((l={})[n+"pagination-item"]=!0,l[n+"current"]=s,l)),onClick:s?m:this.onPageItemClick.bind(this,e)};return r&&(a.component="a",a.href=r.replace("{page}",e)),N.default.createElement(d.default,(0,D.default)({"aria-label":j.str.template(t.total,{current:e,total:i})},a,{key:e}),o(e))},I.prototype.renderPageFirst=function(e){var t=this.props,n=t.prefix,a=t.size,r=t.shape,t=t.locale,a={disabled:e<=1,size:a,className:(0,P.default)(((a={})[n+"pagination-item"]=!0,a[n+"prev"]=!0,a)),onClick:this.onPageItemClick.bind(this,e-1)},n=N.default.createElement(c.default,{type:"arrow-left",className:n+"pagination-icon-prev"});return N.default.createElement(d.default,(0,D.default)({},a,{"aria-label":j.str.template(t.labelPrev,{current:e})}),n,"arrow-only"===r||"arrow-prev-only"===r||"no-border"===r?"":t.prev)},I.prototype.renderPageLast=function(e,t){var n=this.props,a=n.prefix,r=n.size,o=n.shape,n=n.locale,r={disabled:t<=e,size:r,className:(0,P.default)(((t={})[a+"pagination-item"]=!0,t[a+"next"]=!0,t)),onClick:this.onPageItemClick.bind(this,e+1)},t=N.default.createElement(c.default,{type:"arrow-right",className:a+"pagination-icon-next"});return N.default.createElement(d.default,(0,D.default)({},r,{"aria-label":j.str.template(n.labelNext,{current:e})}),"arrow-only"===o||"no-border"===o?"":n.next,t)},I.prototype.renderPageEllipsis=function(e){var t=this.props.prefix;return N.default.createElement(c.default,{className:t+"pagination-ellipsis "+t+"pagination-icon-ellipsis",type:"ellipsis",key:"ellipsis-"+e})},I.prototype.renderPageJump=function(){var t=this,e=this.props,n=e.prefix,a=e.size,e=e.locale,r=this.state.inputValue;return[N.default.createElement("span",{className:n+"pagination-jump-text"},e.goTo),N.default.createElement(f.default,{className:n+"pagination-jump-input",type:"text","aria-label":e.inputAriaLabel,size:a,value:r,onChange:this.onInputChange.bind(this),onKeyDown:function(e){e.keyCode===j.KEYCODE.ENTER&&t.handleJump(e)}}),N.default.createElement("span",{className:n+"pagination-jump-text"},e.page),N.default.createElement(d.default,{className:n+"pagination-jump-go",size:a,onClick:this.handleJump},e.go)]},I.prototype.renderPageDisplay=function(e,t){var n=this.props,a=n.prefix,n=n.pageNumberRender;return N.default.createElement("span",{className:a+"pagination-display"},N.default.createElement("em",null,n(e)),"/",n(t))},I.prototype.renderPageList=function(e,t){var n=this.props,a=n.prefix,n=n.pageShowCount,r=[];if(t<=n)for(var o=1;o<=t;o++)r.push(this.renderPageItem(o));else{var n=n-3,i=parseInt(n/2,10),s=void 0,l=void 0;r.push(this.renderPageItem(1)),l=e+i,(s=e-i)<=1&&(l=(s=2)+n),2=e.length&&-1=this.props.children.length?(this.update(this.props),this.changeSlide({message:"index",index:this.props.children.length-this.props.slidesToShow,currentSlide:this.state.currentSlide})):(n=[],Object.keys(t).forEach(function(e){e in a.props&&t[e]!==a.props[e]&&n.push(e)}),1===n.length&&"children"===n[0]||l.obj.shallowEqual(t,this.props)||this.update(this.props)),this.adaptHeight()},p.prototype.componentWillUnmount=function(){this.animationEndCallback&&clearTimeout(this.animationEndCallback),l.events.off(window,"resize",this.onWindowResized),this.state.autoPlayTimer&&clearInterval(this.state.autoPlayTimer)},p.prototype.onWindowResized=function(){this.update(this.props),this.setState({animating:!1}),clearTimeout(this.animationEndCallback),delete this.animationEndCallback},p.prototype.slickGoTo=function(e){"number"==typeof e&&this.changeSlide({message:"index",index:e,currentSlide:this.state.currentSlide})},p.prototype.onEnterArrow=function(e){this.arrowHoverHandler(e)},p.prototype.onLeaveArrow=function(){this.arrowHoverHandler()},p.prototype._instanceRefHandler=function(e,t){this[e]=t},p.prototype.render=function(){var e=this.props,t=e.prefix,n=e.animation,a=e.arrows,r=e.arrowSize,o=e.arrowPosition,i=e.arrowDirection,s=e.dots,l=e.dotsClass,u=e.cssEase,c=e.speed,d=e.infinite,f=e.centerMode,p=e.centerPadding,h=e.lazyLoad,m=e.dotsDirection,g=e.rtl,y=e.slidesToShow,v=e.slidesToScroll,_=e.variableWidth,b=e.vertical,w=e.verticalSwiping,M=e.focusOnSelect,k=e.children,S=e.dotsRender,e=e.triggerType,E=this.state,x=E.currentSlide,C=E.lazyLoadedList,T=E.slideCount,L=E.slideWidth,O=E.slideHeight,D=E.trackStyle,N=E.listHeight,E=E.dragging,u={prefix:t,animation:n,cssEase:u,speed:c,infinite:d,centerMode:f,focusOnSelect:M?this.selectHandler:null,currentSlide:x,lazyLoad:h,lazyLoadedList:C,rtl:g,slideWidth:L,slideHeight:O,slidesToShow:y,slidesToScroll:v,slideCount:T,trackStyle:D,variableWidth:_,vertical:b,verticalSwiping:w,triggerType:e},c=void 0,h=(!0===s&&yt.startX?1:-1),!0===this.props.verticalSwiping&&(t.swipeLength=Math.round(Math.sqrt(Math.pow(t.curY-t.startY,2))),a=t.curY>t.startY?1:-1),r=this.state.currentSlide,s=Math.ceil(this.state.slideCount/this.props.slidesToScroll),o=this.swipeDirection(this.state.touchObject),i=t.swipeLength,!1===this.props.infinite&&(0===r&&"right"===o||s<=r+1&&"left"===o)&&(i=t.swipeLength*this.props.edgeFriction,!1===this.state.edgeDragged)&&this.props.edgeEvent&&(this.props.edgeEvent(o),this.setState({edgeDragged:!0})),!1===this.state.swiped&&this.props.swipeEvent&&(this.props.swipeEvent(o),this.setState({swiped:!0})),this.setState({touchObject:t,swipeLeft:s=n+i*a,trackStyle:(0,u.getTrackCSS)((0,l.default)({left:s},this.props,this.state))}),Math.abs(t.curX-t.startX)<.8*Math.abs(t.curY-t.startY))||4t[t.length-1])e=t[t.length-1];else for(var a in t){if(e-1*n.state.swipeLeft)return t=e,!1}else if(e.offsetLeft-a+(n.getWidth(e)||0)/2>-1*n.state.swipeLeft)return t=e,!1;return!0}),Math.abs(t.dataset.index-this.state.currentSlide)||1):this.props.slidesToScroll},swipeEnd:function(e){if(this.state.dragging){var t=this.state.touchObject,n=this.state.listWidth/this.props.touchThreshold,a=this.swipeDirection(t);if(this.props.verticalSwiping&&(n=this.state.listHeight/this.props.touchThreshold),this.setState({dragging:!1,edgeDragged:!1,swiped:!1,swipeLeft:null,touchObject:{}}),t.swipeLength)if(t.swipeLength>n){e.preventDefault();var r=void 0,o=void 0;switch(a){case"left":case"down":o=this.state.currentSlide+this.getSlideCount(),r=this.props.swipeToSlide?this.checkNavigable(o):o,this.setState({currentDirection:0});break;case"right":case"up":o=this.state.currentSlide-this.getSlideCount(),r=this.props.swipeToSlide?this.checkNavigable(o):o,this.setState({currentDirection:1});break;default:r=this.state.currentSlide}this.slideHandler(r)}else{t=(0,u.getTrackLeft)((0,l.default)({slideIndex:this.state.currentSlide,trackRef:this.track},this.props,this.state));this.setState({trackStyle:(0,u.getTrackAnimateCSS)((0,l.default)({left:t},this.props,this.state))})}}else this.props.swipe&&e.preventDefault()},onInnerSliderEnter:function(){this.props.autoplay&&this.props.pauseOnHover&&this.pause()},onInnerSliderLeave:function(){this.props.autoplay&&this.props.pauseOnHover&&this.autoPlay()}},e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var g=a(n(3)),c=a(n(0)),d=a(n(24)),y=n(423);function a(e){return e&&e.__esModule?e:{default:e}}t.default={initialize:function(t){var n=this,e=d.default.findDOMNode(this.list),a=c.default.Children.count(t.children),r=this.getWidth(e)||0,o=this.getWidth(d.default.findDOMNode(this.track))||0,i=void 0,e=(i=t.vertical?r:(r-(t.centerMode&&2*parseInt(t.centerPadding)))/t.slidesToShow,this.getHeight(e.querySelector('[data-index="0"]'))||0),s=e*t.slidesToShow,l=t.slidesToShow||1,u="activeIndex"in t?t.activeIndex:t.defaultActiveIndex,l=t.rtl?a-1-(l-1)-u:u;this.setState({slideCount:a,slideWidth:i,listWidth:r,trackWidth:o,currentSlide:l,slideHeight:e,listHeight:s},function(){var e=(0,y.getTrackLeft)((0,g.default)({slideIndex:n.state.currentSlide,trackRef:n.track},t,n.state)),e=(0,y.getTrackCSS)((0,g.default)({left:e},t,n.state));n.setState({trackStyle:e}),n.autoPlay()})},update:function(e){this.initialize(e)},getWidth:function(e){return"clientWidth"in e?e.clientWidth:e&&e.getBoundingClientRect().width},getHeight:function(e){return"clientHeight"in e?e.clientHeight:e&&e.getBoundingClientRect().height},adaptHeight:function(){var e,t;this.props.adaptiveHeight&&(t='[data-index="'+this.state.currentSlide+'"]',this.list)&&(t=(e=d.default.findDOMNode(this.list)).querySelector(t).offsetHeight,e.style.height=t+"px")},canGoNext:function(e){var t=!0;return e.infinite||(e.centerMode?e.currentSlide>=e.slideCount-1&&(t=!1):(e.slideCount<=e.slidesToShow||e.currentSlide>=e.slideCount-e.slidesToShow)&&(t=!1)),t},slideHandler:function(e){var t=this,n=this.props.rtl,a=void 0,r=void 0,o=void 0;if(!this.props.waitForAnimate||!this.state.animating){if("fade"===this.props.animation)return r=this.state.currentSlide,!1===this.props.infinite&&(e<0||e>=this.state.slideCount)?void 0:(a=e<0?e+this.state.slideCount:e>=this.state.slideCount?e-this.state.slideCount:e,this.props.lazyLoad&&this.state.lazyLoadedList.indexOf(a)<0&&this.setState({lazyLoadedList:this.state.lazyLoadedList.concat(a)}),o=function(){t.setState({animating:!1}),t.props.onChange(a),delete t.animationEndCallback},this.props.onBeforeChange(this.state.currentSlide,a),this.setState({animating:!0,currentSlide:a},function(){this.animationEndCallback=setTimeout(o,this.props.speed+20)}),void this.autoPlay());a=e,n?a<0?!1===this.props.infinite?r=0:this.state.slideCount%this.props.slidesToScroll!=0?a+this.props.slidesToScroll<=0?(r=this.state.slideCount+a,a=this.state.slideCount-this.props.slidesToScroll):r=a=0:r=this.state.slideCount+a:r=a>=this.state.slideCount?!1===this.props.infinite?this.state.slideCount-this.props.slidesToShow:this.state.slideCount%this.props.slidesToScroll!=0?0:a-this.state.slideCount:a:r=a<0?!1===this.props.infinite?0:this.state.slideCount%this.props.slidesToScroll!=0?this.state.slideCount-this.state.slideCount%this.props.slidesToScroll:this.state.slideCount+a:a>=this.state.slideCount?!1===this.props.infinite?this.state.slideCount-this.props.slidesToShow:this.state.slideCount%this.props.slidesToScroll!=0?0:a-this.state.slideCount:a;var i,e=(0,y.getTrackLeft)((0,g.default)({slideIndex:a,trackRef:this.track},this.props,this.state)),s=(0,y.getTrackLeft)((0,g.default)({slideIndex:r,trackRef:this.track},this.props,this.state));if(!1===this.props.infinite&&(e=s),this.props.lazyLoad){for(var l=!0,u=[],c=this.state.slideCount,d=a<0?c+a:r,f=d;f=l.activeIndex?"visible":"hidden",c.transition="opacity "+l.speed+"ms "+l.cssEase,c.WebkitTransition="opacity "+l.speed+"ms "+l.cssEase,l.vertical?c.top=-l.activeIndex*l.slideHeight:c.left=-l.activeIndex*l.slideWidth),l.vertical&&(c.width="100%"),c),u=(c=(0,v.default)({activeIndex:e},d),a=c.prefix,u=r=i=void 0,o=(u=c.rtl?c.slideCount-1-c.activeIndex:c.activeIndex)<0||u>=c.slideCount,c.centerMode?(n=Math.floor(c.slidesToShow/2),r=(u-c.currentSlide)%c.slideCount==0,u>c.currentSlide-n-1&&u<=c.currentSlide+n&&(i=!0)):i=c.currentSlide<=u&&u=u,u=(0,k.default)(((v={})[o+"upload-list-item"]=!0,v[o+"hidden"]=u,v)),v=this.props.children||i.card.addPhoto,d=r?S.func.prevent:d,_=S.obj.pickOthers(C.propTypes,this.props),b=S.obj.pickOthers(E.default.propTypes,_);if(h&&"function"==typeof m)return e=(0,k.default)(((e={})[o+"form-preview"]=!0,e[s]=!!s,e)),M.default.createElement("div",{style:l,className:e},m(this.state.value,this.props));return M.default.createElement(E.default,(0,w.default)({className:s,style:l,listType:"card",closable:!0,locale:i,value:this.state.value,onRemove:d,onCancel:f,onPreview:c,itemRender:g,isPreview:h,uploader:this.uploaderRef,reUpload:y,showDownload:n},_),M.default.createElement(x.default,(0,w.default)({},b,{shape:"card",prefix:o,disabled:r,action:a,timeout:p,isPreview:h,value:this.state.value,onProgress:this.onProgress,onChange:this.onChange,ref:function(e){return t.saveRef(e)},className:u}),v))},c=n=C,n.displayName="Card",n.propTypes={prefix:s.default.string,locale:s.default.object,children:s.default.object,value:s.default.oneOfType([s.default.array,s.default.object]),defaultValue:s.default.oneOfType([s.default.array,s.default.object]),onPreview:s.default.func,onChange:s.default.func,onRemove:s.default.func,onCancel:s.default.func,itemRender:s.default.func,reUpload:s.default.bool,showDownload:s.default.bool,onProgress:s.default.func,isPreview:s.default.bool,renderPreview:s.default.func},n.defaultProps={prefix:"next-",locale:u.default.Upload,showDownload:!0,onChange:S.func.noop,onPreview:S.func.noop,onProgress:S.func.noop},a=function(){var n=this;this.onProgress=function(e,t){n.setState({value:e}),n.props.onProgress(e,t)},this.onChange=function(e,t){"value"in n.props||n.setState({value:e}),n.props.onChange(e,t)}};var f,i=c;function C(e){(0,r.default)(this,C);var t=(0,o.default)(this,f.call(this,e)),n=(a.call(t),void 0),n="value"in e?e.value:e.defaultValue;return t.state={value:Array.isArray(n)?n:[],uploaderRef:t.uploaderRef},t}t.default=(0,l.polyfill)(i),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var u=m(n(3)),c=m(n(16)),o=m(n(4)),i=m(n(7)),a=m(n(8)),d=m(n(0)),r=m(n(5)),f=m(n(18)),p=m(n(26)),s=n(11),l=m(n(44)),h=m(n(193));function m(e){return e&&e.__esModule?e:{default:e}}g=d.default.Component,(0,a.default)(y,g),y.prototype.abort=function(e){this.uploaderRef.abort(e)},y.prototype.startUpload=function(){this.uploaderRef.startUpload()},y.prototype.render=function(){var e=this.props,t=e.className,n=e.style,a=e.shape,r=e.locale,o=e.prefix,i=e.listType,e=(0,c.default)(e,["className","style","shape","locale","prefix","listType"]),s=o+"upload-drag",t=(0,f.default)(((l={})[s]=!0,l[s+"-over"]=this.state.dragOver,l[t]=!!t,l)),l=this.props.children||d.default.createElement("div",{className:t},d.default.createElement("p",{className:s+"-icon"},d.default.createElement(p.default,{size:"large",className:s+"-upload-icon"})),d.default.createElement("p",{className:s+"-text"},r.drag.text),d.default.createElement("p",{className:s+"-hint"},r.drag.hint));return d.default.createElement(h.default,(0,u.default)({},e,{prefix:o,shape:a,listType:i,dragable:!0,style:n,onDragOver:this.onDragOver,onDragLeave:this.onDragLeave,onDrop:this.onDrop,ref:this.saveUploaderRef}),l)},a=n=y,n.propTypes={prefix:r.default.string,locale:r.default.object,shape:r.default.string,onDragOver:r.default.func,onDragLeave:r.default.func,onDrop:r.default.func,limit:r.default.number,className:r.default.string,style:r.default.object,defaultValue:r.default.array,children:r.default.node,listType:r.default.string,timeout:r.default.number},n.defaultProps={prefix:"next-",onDragOver:s.func.noop,onDragLeave:s.func.noop,onDrop:s.func.noop,locale:l.default.Upload};var g,r=a;function y(){var e,t;(0,o.default)(this,y);for(var n=arguments.length,a=Array(n),r=0;r>1,c=-7,d=n?r-1:0,f=n?-1:1,r=e[t+d];for(d+=f,o=r&(1<<-c)-1,r>>=-c,c+=s;0>=-c,c+=a;0>1,d=23===r?Math.pow(2,-24)-Math.pow(2,-77):0,f=a?0:o-1,p=a?1:-1,o=t<0||0===t&&1/t<0?1:0;for(t=Math.abs(t),isNaN(t)||t===1/0?(s=isNaN(t)?1:0,i=u):(i=Math.floor(Math.log(t)/Math.LN2),t*(a=Math.pow(2,-i))<1&&(i--,a*=2),2<=(t+=1<=i+c?d/a:d*Math.pow(2,1-c))*a&&(i++,a/=2),u<=i+c?(s=0,i=u):1<=i+c?(s=(t*a-1)*Math.pow(2,r),i+=c):(s=t*Math.pow(2,c-1)*Math.pow(2,r),i=0));8<=r;e[n+f]=255&s,f+=p,s/=256,r-=8);for(i=i<>>0),r=this.head,o=0;r;)t=r.data,n=o,t.copy(a,n),o+=r.data.length,r=r.next;return a},r),a&&a.inspect&&a.inspect.custom&&(e.exports.prototype[a.inspect.custom]=function(){var e=a.inspect({length:this.length});return this.constructor.name+" "+e})},function(e,t){},function(e,t,n){!function(t){function a(e){try{if(!t.localStorage)return}catch(e){return}e=t.localStorage[e];return null!=e&&"true"===String(e).toLowerCase()}e.exports=function(e,t){if(a("noDeprecation"))return e;var n=!1;return function(){if(!n){if(a("throwDeprecation"))throw new Error(t);a("traceDeprecation")?console.trace(t):console.warn(t),n=!0}return e.apply(this,arguments)}}}.call(this,n(65))},function(e,t,n){"use strict";e.exports=r;var a=n(418),e=Object.create(n(118));function r(e){if(!(this instanceof r))return new r(e);a.call(this,e)}e.inherits=n(105),e.inherits(r,a),r.prototype._transform=function(e,t,n){n(null,e)}},function(e,t,n){e.exports=n(190)},function(e,t,n){e.exports=n(92)},function(e,t,n){e.exports=n(189).Transform},function(e,t,n){e.exports=n(189).PassThrough},function(e,t,n){"use strict";function u(e){return new Error("Can only stringify objects, not "+e)}function c(t){return Object.keys(t).filter(e=>p(t[e]))}function d(e){var t,n=Array.isArray(e)?[]:Object.prototype.hasOwnProperty.call(e,"__proto__")?{["__proto__"]:void 0}:{};for(t of Object.keys(e))!e[t]||"function"!=typeof e[t].toJSON||"toISOString"in e[t]?n[t]=e[t]:n[t]=e[t].toJSON();return n}function f(t,e,n){var a,r,o=c(n=d(n));r=n,a=Object.keys(r).filter(e=>!p(r[e]));const i=[],s=e||"",l=(o.forEach(e=>{var t=h(n[e]);"undefined"!==t&&"null"!==t&&i.push(s+m(e)+" = "+g(n[e],!0))}),0{i.push(function(e,t,n,a){var r=h(a);{if("array"===r)return function(e,t,n,a){var r=h((a=d(a))[0]);if("table"!==r)throw u(r);const o=e+m(n);let i="";return a.forEach(e=>{0"\\u"+function(e,t){for(;t.lengths(e).replace(/"(?="")/g,'\\"')).join("\n");return'"'===e.slice(-1)&&(e+="\\\n"),'"""\n'+e+'"""';return}case"string":return i(t);case"string-literal":return"'"+t+"'";case"integer":return y(t);case"float":n=t;return n===1/0?"inf":n===-1/0?"-inf":Object.is(n,NaN)?"nan":Object.is(n,-0)?"-0.0":([n,a]=String(n).split("."),y(n)+"."+a);case"boolean":return String(t);case"datetime":return t.toISOString();case"array":{var a=t.filter(e=>"null"!==h(e)&&"undefined"!==h(e)&&"nan"!==h(e));a=d(a);let e="[";a=a.map(e=>l(e));60{o.push(m(e)+" = "+g(r[e],!1))}),"{ "+o.join(", ")+(0=P.KEYCODE.LEFT&&t<=P.KEYCODE.DOWN&&e.preventDefault(),e=void 0,t===P.KEYCODE.RIGHT||t===P.KEYCODE.DOWN?(e=o.getNextActiveKey(!0),o.handleTriggerEvent(o.props.triggerType,e)):t!==P.KEYCODE.LEFT&&t!==P.KEYCODE.UP||(e=o.getNextActiveKey(!1),o.handleTriggerEvent(o.props.triggerType,e)))},o.state={activeKey:o.getDefaultActiveKey(e)},o}s.displayName="Tab",t.default=(0,l.polyfill)(s),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var _=d(n(3)),a=d(n(4)),r=d(n(7)),o=d(n(8)),b=d(n(0)),i=n(25),s=d(n(5)),w=d(n(19)),M=d(n(26)),l=d(n(64)),u=d(n(50)),k=(d(n(20)),d(n(83))),h=n(11),c=n(420);function d(e){return e&&e.__esModule?e:{default:e}}var f,S={float:"right",zIndex:1},E={float:"left",zIndex:1},p={dropdown:"arrow-down",prev:"arrow-left",next:"arrow-right"},m=l.default.Popup,l=(f=b.default.Component,(0,o.default)(g,f),g.prototype.componentDidMount=function(){this.props.animation||this.initialSettings(),h.events.on(window,"resize",this.onWindowResized)},g.prototype.componentDidUpdate=function(e){var t=this;clearTimeout(this.scrollTimer),this.scrollTimer=setTimeout(function(){t.scrollToActiveTab()},410),clearTimeout(this.slideTimer),this.slideTimer=setTimeout(function(){t.setSlideBtn()},410),"dropdown"!==this.props.excessMode||(0,c.tabsArrayShallowEqual)(this.props.tabs,e.tabs)||this.getDropdownItems(this.props)},g.prototype.componentWillUnmount=function(){h.events.off(window,"resize",this.onWindowResized)},g.prototype.initialSettings=function(){this.setSlideBtn(),this.getDropdownItems(this.props)},g.prototype.setOffset=function(e){var t=!(1n&&(t.current=n),this.setState(t),this.props.onPageSizeChange(e)},I.prototype.renderPageTotal=function(){var e=this.props,t=e.prefix,n=e.total,e=e.totalRender,a=this.state,r=a.currentPageSize,a=a.current;return N.default.createElement("div",{className:t+"pagination-total"},e(n,[(a-1)*r+1,a*r]))},I.prototype.renderPageItem=function(e){var t=this.props,n=t.prefix,a=t.size,r=t.link,o=t.pageNumberRender,i=t.total,s=t.pageSize,t=t.locale,l=this.state.current,i=Y(i,s),s=parseInt(e,10)===l,a={size:a,className:(0,P.default)(((l={})[n+"pagination-item"]=!0,l[n+"current"]=s,l)),onClick:s?m:this.onPageItemClick.bind(this,e)};return r&&(a.component="a",a.href=r.replace("{page}",e)),N.default.createElement(d.default,(0,D.default)({"aria-label":j.str.template(t.total,{current:e,total:i})},a,{key:e}),o(e))},I.prototype.renderPageFirst=function(e){var t=this.props,n=t.prefix,a=t.size,r=t.shape,t=t.locale,a={disabled:e<=1,size:a,className:(0,P.default)(((a={})[n+"pagination-item"]=!0,a[n+"prev"]=!0,a)),onClick:this.onPageItemClick.bind(this,e-1)},n=N.default.createElement(c.default,{type:"arrow-left",className:n+"pagination-icon-prev"});return N.default.createElement(d.default,(0,D.default)({},a,{"aria-label":j.str.template(t.labelPrev,{current:e})}),n,"arrow-only"===r||"arrow-prev-only"===r||"no-border"===r?"":t.prev)},I.prototype.renderPageLast=function(e,t){var n=this.props,a=n.prefix,r=n.size,o=n.shape,n=n.locale,r={disabled:t<=e,size:r,className:(0,P.default)(((t={})[a+"pagination-item"]=!0,t[a+"next"]=!0,t)),onClick:this.onPageItemClick.bind(this,e+1)},t=N.default.createElement(c.default,{type:"arrow-right",className:a+"pagination-icon-next"});return N.default.createElement(d.default,(0,D.default)({},r,{"aria-label":j.str.template(n.labelNext,{current:e})}),"arrow-only"===o||"no-border"===o?"":n.next,t)},I.prototype.renderPageEllipsis=function(e){var t=this.props.prefix;return N.default.createElement(c.default,{className:t+"pagination-ellipsis "+t+"pagination-icon-ellipsis",type:"ellipsis",key:"ellipsis-"+e})},I.prototype.renderPageJump=function(){var t=this,e=this.props,n=e.prefix,a=e.size,e=e.locale,r=this.state.inputValue;return[N.default.createElement("span",{className:n+"pagination-jump-text"},e.goTo),N.default.createElement(f.default,{className:n+"pagination-jump-input",type:"text","aria-label":e.inputAriaLabel,size:a,value:r,onChange:this.onInputChange.bind(this),onKeyDown:function(e){e.keyCode===j.KEYCODE.ENTER&&t.handleJump(e)}}),N.default.createElement("span",{className:n+"pagination-jump-text"},e.page),N.default.createElement(d.default,{className:n+"pagination-jump-go",size:a,onClick:this.handleJump},e.go)]},I.prototype.renderPageDisplay=function(e,t){var n=this.props,a=n.prefix,n=n.pageNumberRender;return N.default.createElement("span",{className:a+"pagination-display"},N.default.createElement("em",null,n(e)),"/",n(t))},I.prototype.renderPageList=function(e,t){var n=this.props,a=n.prefix,n=n.pageShowCount,r=[];if(t<=n)for(var o=1;o<=t;o++)r.push(this.renderPageItem(o));else{var n=n-3,i=parseInt(n/2,10),s=void 0,l=void 0;r.push(this.renderPageItem(1)),l=e+i,(s=e-i)<=1&&(l=(s=2)+n),2=e.length&&-1=this.props.children.length?(this.update(this.props),this.changeSlide({message:"index",index:this.props.children.length-this.props.slidesToShow,currentSlide:this.state.currentSlide})):(n=[],Object.keys(t).forEach(function(e){e in a.props&&t[e]!==a.props[e]&&n.push(e)}),1===n.length&&"children"===n[0]||l.obj.shallowEqual(t,this.props)||this.update(this.props)),this.adaptHeight()},p.prototype.componentWillUnmount=function(){this.animationEndCallback&&clearTimeout(this.animationEndCallback),l.events.off(window,"resize",this.onWindowResized),this.state.autoPlayTimer&&clearInterval(this.state.autoPlayTimer)},p.prototype.onWindowResized=function(){this.update(this.props),this.setState({animating:!1}),clearTimeout(this.animationEndCallback),delete this.animationEndCallback},p.prototype.slickGoTo=function(e){"number"==typeof e&&this.changeSlide({message:"index",index:e,currentSlide:this.state.currentSlide})},p.prototype.onEnterArrow=function(e){this.arrowHoverHandler(e)},p.prototype.onLeaveArrow=function(){this.arrowHoverHandler()},p.prototype._instanceRefHandler=function(e,t){this[e]=t},p.prototype.render=function(){var e=this.props,t=e.prefix,n=e.animation,a=e.arrows,r=e.arrowSize,o=e.arrowPosition,i=e.arrowDirection,s=e.dots,l=e.dotsClass,u=e.cssEase,c=e.speed,d=e.infinite,f=e.centerMode,p=e.centerPadding,h=e.lazyLoad,m=e.dotsDirection,g=e.rtl,y=e.slidesToShow,v=e.slidesToScroll,_=e.variableWidth,b=e.vertical,w=e.verticalSwiping,M=e.focusOnSelect,k=e.children,S=e.dotsRender,e=e.triggerType,E=this.state,x=E.currentSlide,C=E.lazyLoadedList,T=E.slideCount,L=E.slideWidth,O=E.slideHeight,D=E.trackStyle,N=E.listHeight,E=E.dragging,u={prefix:t,animation:n,cssEase:u,speed:c,infinite:d,centerMode:f,focusOnSelect:M?this.selectHandler:null,currentSlide:x,lazyLoad:h,lazyLoadedList:C,rtl:g,slideWidth:L,slideHeight:O,slidesToShow:y,slidesToScroll:v,slideCount:T,trackStyle:D,variableWidth:_,vertical:b,verticalSwiping:w,triggerType:e},c=void 0,h=(!0===s&&yt.startX?1:-1),!0===this.props.verticalSwiping&&(t.swipeLength=Math.round(Math.sqrt(Math.pow(t.curY-t.startY,2))),a=t.curY>t.startY?1:-1),r=this.state.currentSlide,s=Math.ceil(this.state.slideCount/this.props.slidesToScroll),o=this.swipeDirection(this.state.touchObject),i=t.swipeLength,!1===this.props.infinite&&(0===r&&"right"===o||s<=r+1&&"left"===o)&&(i=t.swipeLength*this.props.edgeFriction,!1===this.state.edgeDragged)&&this.props.edgeEvent&&(this.props.edgeEvent(o),this.setState({edgeDragged:!0})),!1===this.state.swiped&&this.props.swipeEvent&&(this.props.swipeEvent(o),this.setState({swiped:!0})),this.setState({touchObject:t,swipeLeft:s=n+i*a,trackStyle:(0,u.getTrackCSS)((0,l.default)({left:s},this.props,this.state))}),Math.abs(t.curX-t.startX)<.8*Math.abs(t.curY-t.startY))||4t[t.length-1])e=t[t.length-1];else for(var a in t){if(e-1*n.state.swipeLeft)return t=e,!1}else if(e.offsetLeft-a+(n.getWidth(e)||0)/2>-1*n.state.swipeLeft)return t=e,!1;return!0}),Math.abs(t.dataset.index-this.state.currentSlide)||1):this.props.slidesToScroll},swipeEnd:function(e){if(this.state.dragging){var t=this.state.touchObject,n=this.state.listWidth/this.props.touchThreshold,a=this.swipeDirection(t);if(this.props.verticalSwiping&&(n=this.state.listHeight/this.props.touchThreshold),this.setState({dragging:!1,edgeDragged:!1,swiped:!1,swipeLeft:null,touchObject:{}}),t.swipeLength)if(t.swipeLength>n){e.preventDefault();var r=void 0,o=void 0;switch(a){case"left":case"down":o=this.state.currentSlide+this.getSlideCount(),r=this.props.swipeToSlide?this.checkNavigable(o):o,this.setState({currentDirection:0});break;case"right":case"up":o=this.state.currentSlide-this.getSlideCount(),r=this.props.swipeToSlide?this.checkNavigable(o):o,this.setState({currentDirection:1});break;default:r=this.state.currentSlide}this.slideHandler(r)}else{t=(0,u.getTrackLeft)((0,l.default)({slideIndex:this.state.currentSlide,trackRef:this.track},this.props,this.state));this.setState({trackStyle:(0,u.getTrackAnimateCSS)((0,l.default)({left:t},this.props,this.state))})}}else this.props.swipe&&e.preventDefault()},onInnerSliderEnter:function(){this.props.autoplay&&this.props.pauseOnHover&&this.pause()},onInnerSliderLeave:function(){this.props.autoplay&&this.props.pauseOnHover&&this.autoPlay()}},e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var g=a(n(3)),c=a(n(0)),d=a(n(25)),y=n(423);function a(e){return e&&e.__esModule?e:{default:e}}t.default={initialize:function(t){var n=this,e=d.default.findDOMNode(this.list),a=c.default.Children.count(t.children),r=this.getWidth(e)||0,o=this.getWidth(d.default.findDOMNode(this.track))||0,i=void 0,e=(i=t.vertical?r:(r-(t.centerMode&&2*parseInt(t.centerPadding)))/t.slidesToShow,this.getHeight(e.querySelector('[data-index="0"]'))||0),s=e*t.slidesToShow,l=t.slidesToShow||1,u="activeIndex"in t?t.activeIndex:t.defaultActiveIndex,l=t.rtl?a-1-(l-1)-u:u;this.setState({slideCount:a,slideWidth:i,listWidth:r,trackWidth:o,currentSlide:l,slideHeight:e,listHeight:s},function(){var e=(0,y.getTrackLeft)((0,g.default)({slideIndex:n.state.currentSlide,trackRef:n.track},t,n.state)),e=(0,y.getTrackCSS)((0,g.default)({left:e},t,n.state));n.setState({trackStyle:e}),n.autoPlay()})},update:function(e){this.initialize(e)},getWidth:function(e){return"clientWidth"in e?e.clientWidth:e&&e.getBoundingClientRect().width},getHeight:function(e){return"clientHeight"in e?e.clientHeight:e&&e.getBoundingClientRect().height},adaptHeight:function(){var e,t;this.props.adaptiveHeight&&(t='[data-index="'+this.state.currentSlide+'"]',this.list)&&(t=(e=d.default.findDOMNode(this.list)).querySelector(t).offsetHeight,e.style.height=t+"px")},canGoNext:function(e){var t=!0;return e.infinite||(e.centerMode?e.currentSlide>=e.slideCount-1&&(t=!1):(e.slideCount<=e.slidesToShow||e.currentSlide>=e.slideCount-e.slidesToShow)&&(t=!1)),t},slideHandler:function(e){var t=this,n=this.props.rtl,a=void 0,r=void 0,o=void 0;if(!this.props.waitForAnimate||!this.state.animating){if("fade"===this.props.animation)return r=this.state.currentSlide,!1===this.props.infinite&&(e<0||e>=this.state.slideCount)?void 0:(a=e<0?e+this.state.slideCount:e>=this.state.slideCount?e-this.state.slideCount:e,this.props.lazyLoad&&this.state.lazyLoadedList.indexOf(a)<0&&this.setState({lazyLoadedList:this.state.lazyLoadedList.concat(a)}),o=function(){t.setState({animating:!1}),t.props.onChange(a),delete t.animationEndCallback},this.props.onBeforeChange(this.state.currentSlide,a),this.setState({animating:!0,currentSlide:a},function(){this.animationEndCallback=setTimeout(o,this.props.speed+20)}),void this.autoPlay());a=e,n?a<0?!1===this.props.infinite?r=0:this.state.slideCount%this.props.slidesToScroll!=0?a+this.props.slidesToScroll<=0?(r=this.state.slideCount+a,a=this.state.slideCount-this.props.slidesToScroll):r=a=0:r=this.state.slideCount+a:r=a>=this.state.slideCount?!1===this.props.infinite?this.state.slideCount-this.props.slidesToShow:this.state.slideCount%this.props.slidesToScroll!=0?0:a-this.state.slideCount:a:r=a<0?!1===this.props.infinite?0:this.state.slideCount%this.props.slidesToScroll!=0?this.state.slideCount-this.state.slideCount%this.props.slidesToScroll:this.state.slideCount+a:a>=this.state.slideCount?!1===this.props.infinite?this.state.slideCount-this.props.slidesToShow:this.state.slideCount%this.props.slidesToScroll!=0?0:a-this.state.slideCount:a;var i,e=(0,y.getTrackLeft)((0,g.default)({slideIndex:a,trackRef:this.track},this.props,this.state)),s=(0,y.getTrackLeft)((0,g.default)({slideIndex:r,trackRef:this.track},this.props,this.state));if(!1===this.props.infinite&&(e=s),this.props.lazyLoad){for(var l=!0,u=[],c=this.state.slideCount,d=a<0?c+a:r,f=d;f=l.activeIndex?"visible":"hidden",c.transition="opacity "+l.speed+"ms "+l.cssEase,c.WebkitTransition="opacity "+l.speed+"ms "+l.cssEase,l.vertical?c.top=-l.activeIndex*l.slideHeight:c.left=-l.activeIndex*l.slideWidth),l.vertical&&(c.width="100%"),c),u=(c=(0,v.default)({activeIndex:e},d),a=c.prefix,u=r=i=void 0,o=(u=c.rtl?c.slideCount-1-c.activeIndex:c.activeIndex)<0||u>=c.slideCount,c.centerMode?(n=Math.floor(c.slidesToShow/2),r=(u-c.currentSlide)%c.slideCount==0,u>c.currentSlide-n-1&&u<=c.currentSlide+n&&(i=!0)):i=c.currentSlide<=u&&u=u,u=(0,k.default)(((v={})[o+"upload-list-item"]=!0,v[o+"hidden"]=u,v)),v=this.props.children||i.card.addPhoto,d=r?S.func.prevent:d,_=S.obj.pickOthers(C.propTypes,this.props),b=S.obj.pickOthers(E.default.propTypes,_);if(h&&"function"==typeof m)return e=(0,k.default)(((e={})[o+"form-preview"]=!0,e[s]=!!s,e)),M.default.createElement("div",{style:l,className:e},m(this.state.value,this.props));return M.default.createElement(E.default,(0,w.default)({className:s,style:l,listType:"card",closable:!0,locale:i,value:this.state.value,onRemove:d,onCancel:f,onPreview:c,itemRender:g,isPreview:h,uploader:this.uploaderRef,reUpload:y,showDownload:n},_),M.default.createElement(x.default,(0,w.default)({},b,{shape:"card",prefix:o,disabled:r,action:a,timeout:p,isPreview:h,value:this.state.value,onProgress:this.onProgress,onChange:this.onChange,ref:function(e){return t.saveRef(e)},className:u}),v))},c=n=C,n.displayName="Card",n.propTypes={prefix:s.default.string,locale:s.default.object,children:s.default.object,value:s.default.oneOfType([s.default.array,s.default.object]),defaultValue:s.default.oneOfType([s.default.array,s.default.object]),onPreview:s.default.func,onChange:s.default.func,onRemove:s.default.func,onCancel:s.default.func,itemRender:s.default.func,reUpload:s.default.bool,showDownload:s.default.bool,onProgress:s.default.func,isPreview:s.default.bool,renderPreview:s.default.func},n.defaultProps={prefix:"next-",locale:u.default.Upload,showDownload:!0,onChange:S.func.noop,onPreview:S.func.noop,onProgress:S.func.noop},a=function(){var n=this;this.onProgress=function(e,t){n.setState({value:e}),n.props.onProgress(e,t)},this.onChange=function(e,t){"value"in n.props||n.setState({value:e}),n.props.onChange(e,t)}};var f,i=c;function C(e){(0,r.default)(this,C);var t=(0,o.default)(this,f.call(this,e)),n=(a.call(t),void 0),n="value"in e?e.value:e.defaultValue;return t.state={value:Array.isArray(n)?n:[],uploaderRef:t.uploaderRef},t}t.default=(0,l.polyfill)(i),e.exports=t.default},function(e,t,n){"use strict";t.__esModule=!0;var u=m(n(3)),c=m(n(17)),o=m(n(4)),i=m(n(7)),a=m(n(8)),d=m(n(0)),r=m(n(5)),f=m(n(19)),p=m(n(26)),s=n(11),l=m(n(44)),h=m(n(193));function m(e){return e&&e.__esModule?e:{default:e}}g=d.default.Component,(0,a.default)(y,g),y.prototype.abort=function(e){this.uploaderRef.abort(e)},y.prototype.startUpload=function(){this.uploaderRef.startUpload()},y.prototype.render=function(){var e=this.props,t=e.className,n=e.style,a=e.shape,r=e.locale,o=e.prefix,i=e.listType,e=(0,c.default)(e,["className","style","shape","locale","prefix","listType"]),s=o+"upload-drag",t=(0,f.default)(((l={})[s]=!0,l[s+"-over"]=this.state.dragOver,l[t]=!!t,l)),l=this.props.children||d.default.createElement("div",{className:t},d.default.createElement("p",{className:s+"-icon"},d.default.createElement(p.default,{size:"large",className:s+"-upload-icon"})),d.default.createElement("p",{className:s+"-text"},r.drag.text),d.default.createElement("p",{className:s+"-hint"},r.drag.hint));return d.default.createElement(h.default,(0,u.default)({},e,{prefix:o,shape:a,listType:i,dragable:!0,style:n,onDragOver:this.onDragOver,onDragLeave:this.onDragLeave,onDrop:this.onDrop,ref:this.saveUploaderRef}),l)},a=n=y,n.propTypes={prefix:r.default.string,locale:r.default.object,shape:r.default.string,onDragOver:r.default.func,onDragLeave:r.default.func,onDrop:r.default.func,limit:r.default.number,className:r.default.string,style:r.default.object,defaultValue:r.default.array,children:r.default.node,listType:r.default.string,timeout:r.default.number},n.defaultProps={prefix:"next-",onDragOver:s.func.noop,onDragLeave:s.func.noop,onDrop:s.func.noop,locale:l.default.Upload};var g,r=a;function y(){var e,t;(0,o.default)(this,y);for(var n=arguments.length,a=Array(n),r=0;r result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("ok", result.getData()); + } + + @Test + void testReadiness() throws Exception { + when(healthProxy.checkReadiness()).thenReturn(Result.success("ready")); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/health/readiness"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("ready", result.getData()); + } +} + diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java new file mode 100644 index 00000000000..1d613c64423 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/ConsoleServerStateControllerTest.java @@ -0,0 +1,117 @@ +/* + * Copyright 1999-2024 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.console.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.common.http.param.MediaType; +import com.alibaba.nacos.console.proxy.ServerStateProxy; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +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.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.when; + +/** + * ConsoleServerStateControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleServerStateControllerTest { + + @Mock + private ServerStateProxy serverStateProxy; + + @InjectMocks + private ConsoleServerStateController consoleServerStateController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleServerStateController).build(); + } + + @Test + void testServerState() throws Exception { + + Map state = new HashMap<>(); + state.put("state", "OK"); + + when(serverStateProxy.getServerState()).thenReturn(state); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/state") + .contentType(MediaType.APPLICATION_JSON); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Map result = new ObjectMapper().readValue(actualValue, + new TypeReference>() { + }); + + assertEquals("OK", result.get("state")); + } + + @Test + void testGetAnnouncement() throws Exception { + when(serverStateProxy.getAnnouncement(anyString())).thenReturn("Test Announcement"); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/announcement") + .param("language", "zh-CN"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("Test Announcement", result.getData()); + } + + @Test + void testGetConsoleUiGuide() throws Exception { + when(serverStateProxy.getConsoleUiGuide()).thenReturn("Test Guide"); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/server/guide"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("Test Guide", result.getData()); + } +} + diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java new file mode 100644 index 00000000000..559412b07f3 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleConfigControllerTest.java @@ -0,0 +1,479 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.config; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.config.NacosAuthConfig; +import com.alibaba.nacos.common.http.param.MediaType; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.config.server.controller.parameters.SameNamespaceCloneConfigBean; +import com.alibaba.nacos.config.server.model.ConfigAllInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo; +import com.alibaba.nacos.config.server.model.ConfigInfo4Beta; +import com.alibaba.nacos.config.server.model.ConfigRequestInfo; +import com.alibaba.nacos.config.server.model.SameConfigPolicy; +import com.alibaba.nacos.config.server.model.form.ConfigForm; +import com.alibaba.nacos.config.server.model.form.ConfigFormV3; +import com.alibaba.nacos.config.server.utils.RequestUtil; +import com.alibaba.nacos.console.proxy.config.ConfigProxy; +import com.alibaba.nacos.core.auth.AuthFilter; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentMatcher; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.core.env.StandardEnvironment; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import javax.servlet.http.HttpServletRequest; +import java.util.ArrayList; +import java.util.Arrays; +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; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleConfigControllerTest. + * + * @author zhangyukun on:2024/8/20 + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ConsoleConfigControllerTest { + + private static final String TEST_DATA_ID = "test"; + + private static final String TEST_GROUP = "test"; + + private static final String TEST_NAMESPACE_ID = ""; + + private static final String TEST_TAG = ""; + + private static final String TEST_CONTENT = "test config"; + + @InjectMocks + private AuthFilter authFilter; + + @Mock + private NacosAuthConfig authConfig; + + private ConsoleConfigController consoleConfigController; + + private MockMvc mockmvc; + + @Mock + private ConfigProxy configProxy; + + @BeforeEach + void setUp() { + EnvUtil.setEnvironment(new StandardEnvironment()); + consoleConfigController = new ConsoleConfigController(configProxy); + mockmvc = MockMvcBuilders.standaloneSetup(consoleConfigController).addFilter(authFilter).build(); + when(authConfig.isAuthEnabled()).thenReturn(false); + } + + @Test + void testGetConfigDetail() throws Exception { + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId("testDataId"); + configAllInfo.setGroup("testGroup"); + configAllInfo.setContent("testContent"); + + when(configProxy.getConfigDetail("testDataId", "testGroup", "testNamespace")).thenReturn(configAllInfo); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/config") + .param("dataId", "testDataId").param("groupName", "testGroup").param("namespaceId", "testNamespace"); + + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = JacksonUtils.toObj(actualValue, new TypeReference>() { + }); + ConfigAllInfo resultConfigAllInfo = result.getData(); + + assertEquals("testDataId", resultConfigAllInfo.getDataId()); + assertEquals("testGroup", resultConfigAllInfo.getGroup()); + assertEquals("testContent", resultConfigAllInfo.getContent()); + } + + @Test + void testPublishConfig() throws Exception { + + ConfigFormV3 configForm = new ConfigFormV3(); + configForm.setDataId(TEST_DATA_ID); + configForm.setGroupName(TEST_GROUP); + configForm.setNamespaceId(TEST_NAMESPACE_ID); + configForm.setContent(TEST_CONTENT); + MockHttpServletRequest request = new MockHttpServletRequest(); + + when(configProxy.publishConfig(any(ConfigForm.class), any(ConfigRequestInfo.class))).thenReturn(true); + + Result booleanResult = consoleConfigController.publishConfig(request, configForm); + + verify(configProxy).publishConfig(any(ConfigForm.class), any(ConfigRequestInfo.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), booleanResult.getCode()); + assertTrue(booleanResult.getData()); + } + + @Test + void testDeleteConfig() throws Exception { + + when(configProxy.deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(Constants.DEFAULT_NAMESPACE_ID), + eq(TEST_TAG), any(), any())).thenReturn(true); + + ConfigFormV3 configForm = new ConfigFormV3(); + configForm.setDataId(TEST_DATA_ID); + configForm.setGroupName(TEST_GROUP); + configForm.setNamespaceId(TEST_NAMESPACE_ID); + configForm.setTag(TEST_TAG); + MockHttpServletRequest request = new MockHttpServletRequest(); + Result booleanResult = consoleConfigController.deleteConfig(request, configForm); + + verify(configProxy).deleteConfig(eq(TEST_DATA_ID), eq(TEST_GROUP), eq(Constants.DEFAULT_NAMESPACE_ID), + eq(TEST_TAG), any(), any()); + + assertEquals(ErrorCode.SUCCESS.getCode(), booleanResult.getCode()); + assertTrue(booleanResult.getData()); + } + + @Test + void testBatchDeleteConfigs() throws Exception { + String clientIp = "127.0.0.1"; + String srcUser = "testUser"; + + Mockito.mockStatic(RequestUtil.class); + when(RequestUtil.getRemoteIp(any(HttpServletRequest.class))).thenReturn(clientIp); + when(RequestUtil.getSrcUserName(any(HttpServletRequest.class))).thenReturn(srcUser); + List ids = Arrays.asList(1L, 2L, 3L); + + when(configProxy.batchDeleteConfigs(eq(ids), eq(clientIp), eq(srcUser))).thenReturn(true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete("/v3/console/cs/config/batchDelete") + .param("ids", "1,2,3").header("X-Real-IP", clientIp).header("X-Forwarded-For", clientIp); + + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + int actualStatus = response.getStatus(); + + assertEquals(200, actualStatus); + + String responseBody = response.getContentAsString(); + Result actualResult = new ObjectMapper().readValue(responseBody, new TypeReference>() { + }); + + assertTrue(actualResult.getData()); + assertEquals(ErrorCode.SUCCESS.getCode(), actualResult.getCode()); + + verify(configProxy).batchDeleteConfigs(eq(ids), eq(clientIp), eq(srcUser)); + } + + @Test + void testGetConfigList() throws Exception { + + List configInfoList = new ArrayList<>(); + ConfigInfo configInfo = new ConfigInfo("testDataId", "testGroup", "testContent"); + configInfoList.add(configInfo); + + Page page = new Page<>(); + page.setTotalCount(15); + page.setPageNumber(1); + page.setPagesAvailable(2); + page.setPageItems(configInfoList); + + Map configAdvanceInfo = new HashMap<>(); + configAdvanceInfo.put("appName", "testApp"); + configAdvanceInfo.put("config_tags", "testTag"); + + when(configProxy.getConfigList(1, 10, "testDataId", "testGroup", "public", configAdvanceInfo)).thenReturn(page); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/config/list") + .param("dataId", "testDataId").param("groupName", "testGroup").param("appName", "testApp") + .param("namespaceId", "").param("configTags", "testTag").param("pageNo", "1").param("pageSize", "10"); + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + + Page pageResult = result.getData(); + List resultList = pageResult.getPageItems(); + ConfigInfo resConfigInfo = resultList.get(0); + + assertEquals(configInfoList.size(), resultList.size()); + assertEquals(configInfo.getDataId(), resConfigInfo.getDataId()); + assertEquals(configInfo.getGroup(), resConfigInfo.getGroup()); + assertEquals(configInfo.getContent(), resConfigInfo.getContent()); + } + + @Test + void testGetConfigListByContent() throws Exception { + List configInfoList = new ArrayList<>(); + ConfigInfo configInfo = new ConfigInfo("test", "test", "test"); + configInfoList.add(configInfo); + + Page page = new Page<>(); + page.setTotalCount(15); + page.setPageNumber(1); + page.setPagesAvailable(2); + page.setPageItems(configInfoList); + Map configAdvanceInfo = new HashMap<>(8); + configAdvanceInfo.put("content", "server.port"); + + when(configProxy.getConfigListByContent("blur", 1, 10, "test", "test", "public", configAdvanceInfo)).thenReturn( + page); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/config/searchDetail") + .param("dataId", "test").param("groupName", "test").param("appName", "").param("namespaceId", "") + .param("configTags", "").param("configDetail", "server.port").param("search", "blur") + .param("pageNo", "1").param("pageSize", "10"); + + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + + Page pageResult = result.getData(); + List resultList = pageResult.getPageItems(); + ConfigInfo resConfigInfo = resultList.get(0); + + assertEquals(configInfoList.size(), resultList.size()); + assertEquals(configInfo.getDataId(), resConfigInfo.getDataId()); + assertEquals(configInfo.getGroup(), resConfigInfo.getGroup()); + assertEquals(configInfo.getContent(), resConfigInfo.getContent()); + } + + @Test + void testExportConfig() throws Exception { + + String dataId = "dataId1.json"; + String group = "group2"; + String tenant = "tenant234"; + String appname = "appname2"; + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setContent("content45678"); + configAllInfo.setAppName(appname); + List dataList = new ArrayList<>(); + dataList.add(configAllInfo); + + byte[] serializedData = new ObjectMapper().writeValueAsBytes(dataList); + ResponseEntity responseEntity = new ResponseEntity<>(serializedData, HttpStatus.OK); + + Mockito.when( + configProxy.exportConfig(eq(dataId), eq(group), eq(tenant), eq(appname), eq(Arrays.asList(1L, 2L)))) + .thenReturn(responseEntity); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/config/export") + .param("dataId", dataId).param("groupName", group).param("tenant", tenant).param("appName", appname) + .param("ids", "1,2"); + + int actualValue = mockmvc.perform(builder).andReturn().getResponse().getStatus(); + assertEquals(200, actualValue); + + } + + @Test + void testExportConfigV2() throws Exception { + String dataId = "dataId2.json"; + String group = "group2"; + String tenant = "tenant234"; + String appname = "appname2"; + ConfigAllInfo configAllInfo = new ConfigAllInfo(); + configAllInfo.setDataId(dataId); + configAllInfo.setGroup(group); + configAllInfo.setTenant(tenant); + configAllInfo.setAppName(appname); + configAllInfo.setContent("content1234"); + List dataList = new ArrayList<>(); + dataList.add(configAllInfo); + + byte[] serializedData = new ObjectMapper().writeValueAsBytes(dataList); + ResponseEntity responseEntity = new ResponseEntity<>(serializedData, HttpStatus.OK); + + Mockito.when( + configProxy.exportConfigV2(eq(dataId), eq(group), eq(tenant), eq(appname), eq(Arrays.asList(1L, 2L)))) + .thenReturn(responseEntity); + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/config/export2") + .param("exportV2", "true").param("dataId", dataId).param("groupName", group).param("tenant", tenant) + .param("appName", appname).param("ids", "1,2"); + + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + int actualStatus = response.getStatus(); + + assertEquals(200, actualStatus); + + } + + @Test + void testImportAndPublishConfig() throws Exception { + String srcUser = "testUser"; + String namespaceId = "testNamespace"; + SameConfigPolicy policy = SameConfigPolicy.ABORT; + String srcIp = "127.0.0.1"; + String requestIpApp = null; + + MockMultipartFile mockFile = new MockMultipartFile("file", "test-config.yaml", "text/yaml", + "config-content".getBytes()); + + Map expectedResponse = new HashMap<>(); + expectedResponse.put("success", true); + Result> expectedResult = Result.success(expectedResponse); + + Mockito.when( + configProxy.importAndPublishConfig(eq(srcUser), eq(namespaceId), eq(policy), eq(mockFile), eq(srcIp), + eq(requestIpApp))).thenReturn(expectedResult); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.multipart("/v3/console/cs/config/import") + .file(mockFile).param("srcUser", srcUser).param("namespaceId", namespaceId) + .param("policy", policy.toString()).header("X-Real-IP", srcIp).header("X-Forwarded-For", srcIp) + .header("X-App-Name", requestIpApp != null ? requestIpApp : ""); + + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + int actualStatus = response.getStatus(); + + assertEquals(200, actualStatus); + + verify(configProxy).importAndPublishConfig(eq(srcUser), eq(namespaceId), eq(policy), eq(mockFile), eq(srcIp), + eq(requestIpApp)); + } + + @Test + void testCloneConfig() throws Exception { + SameNamespaceCloneConfigBean sameNamespaceCloneConfigBean = new SameNamespaceCloneConfigBean(); + sameNamespaceCloneConfigBean.setCfgId(1L); + sameNamespaceCloneConfigBean.setDataId("testDataId"); + sameNamespaceCloneConfigBean.setGroup("testGroup"); + List configBeansList = new ArrayList<>(); + configBeansList.add(sameNamespaceCloneConfigBean); + + Map expectedResponse = new HashMap<>(); + expectedResponse.put("status", "success"); + Result> expectedResult = Result.success(expectedResponse); + + when(configProxy.cloneConfig(eq("testUser"), eq("testNamespace"), + argThat(new ArgumentMatcher>() { + @Override + public boolean matches(List argument) { + return argument != null && argument.size() == 1 && "testDataId".equals( + argument.get(0).getDataId()) && "testGroup".equals(argument.get(0).getGroup()) + && 1L == argument.get(0).getCfgId(); + } + }), eq(SameConfigPolicy.ABORT), eq("127.0.0.1"), eq(null) // 这里模拟可能为null的情况 + )).thenReturn(expectedResult); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.post("/v3/console/cs/config/clone") + .param("srcUser", "testUser").param("targetNamespaceId", "testNamespace").param("policy", "ABORT") + .content(new ObjectMapper().writeValueAsString(configBeansList)).contentType(MediaType.APPLICATION_JSON) + .header("X-Real-IP", "127.0.0.1").header("X-Forwarded-For", "127.0.0.1"); + + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + int actualStatus = response.getStatus(); + + assertEquals(200, actualStatus); + + verify(configProxy).cloneConfig(eq("testUser"), eq("testNamespace"), + argThat(new ArgumentMatcher>() { + @Override + public boolean matches(List argument) { + return argument != null && argument.size() == 1 && "testDataId".equals( + argument.get(0).getDataId()) && "testGroup".equals(argument.get(0).getGroup()) + && 1L == argument.get(0).getCfgId(); + } + }), eq(SameConfigPolicy.ABORT), eq("127.0.0.1"), eq(null)); + } + + @Test + void testStopBeta() throws Exception { + // Mock configuration + String dataId = "testDataId"; + String group = "testGroup"; + String namespaceId = "testNamespaceId"; + when(configProxy.removeBetaConfig(anyString(), anyString(), anyString(), anyString(), anyString())).thenReturn( + true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete("/v3/console/cs/config/beta") + .param("dataId", dataId).param("groupName", group).param("namespaceId", namespaceId); + + // Execute and validate response + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals(200, response.getStatus()); + } + + @Test + void testQueryBetaSuccess() throws Exception { + // Mock configuration for successful response + String dataId = "testDataId"; + String group = "testGroup"; + + ConfigInfo4Beta mockConfigInfo = new ConfigInfo4Beta(); + mockConfigInfo.setDataId(dataId); + mockConfigInfo.setGroup(group); + Result mockResult = new Result<>(); + when(configProxy.queryBetaConfig(anyString(), anyString(), anyString())).thenReturn(mockResult); + String namespaceId = "testNamespaceId"; + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/config/beta") + .param("dataId", dataId).param("groupName", group).param("namespaceId", namespaceId); + + // Execute and validate response + MockHttpServletResponse response = mockmvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, + new TypeReference>() { + }); + + assertEquals(200, response.getStatus()); + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java new file mode 100644 index 00000000000..9db5e896ec3 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/config/ConsoleHistoryControllerTest.java @@ -0,0 +1,171 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.config; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.config.server.model.ConfigHistoryInfo; +import com.alibaba.nacos.config.server.model.ConfigInfoWrapper; +import com.alibaba.nacos.console.proxy.config.HistoryProxy; +import com.alibaba.nacos.persistence.model.Page; +import com.fasterxml.jackson.core.type.TypeReference; +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.junit.jupiter.MockitoExtension; +import org.mockito.junit.jupiter.MockitoSettings; +import org.mockito.quality.Strictness; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Collections; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.when; + +/** + * ConsoleHistoryControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +@MockitoSettings(strictness = Strictness.LENIENT) +public class ConsoleHistoryControllerTest { + + @InjectMocks + private ConsoleHistoryController consoleHistoryController; + + @Mock + private HistoryProxy historyProxy; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleHistoryController).build(); + } + + @Test + void testGetConfigHistoryInfo() throws Exception { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + + when(historyProxy.getConfigHistoryInfo("testDataId", "testGroup", Constants.DEFAULT_NAMESPACE_ID, 1L)).thenReturn(configHistoryInfo); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history") + .param("dataId", "testDataId").param("groupName", "testGroup").param("nid", "1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = JacksonUtils.toObj(actualValue, + new TypeReference>() { + }); + ConfigHistoryInfo resultConfigHistoryInfo = result.getData(); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testListConfigHistory() throws Exception { + Page page = new Page<>(); + page.setTotalCount(1); + page.setPageNumber(1); + page.setPagesAvailable(1); + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + page.setPageItems(Collections.singletonList(configHistoryInfo)); + + when(historyProxy.listConfigHistory("testDataId", "testGroup", Constants.DEFAULT_NAMESPACE_ID, 1, 100)).thenReturn(page); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/list") + .param("dataId", "testDataId").param("groupName", "testGroup").param("pageNo", "1") + .param("pageSize", "100"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + Page resultPage = result.getData(); + ConfigHistoryInfo resultConfigHistoryInfo = resultPage.getPageItems().get(0); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testGetPreviousConfigHistoryInfo() throws Exception { + ConfigHistoryInfo configHistoryInfo = new ConfigHistoryInfo(); + configHistoryInfo.setDataId("testDataId"); + configHistoryInfo.setGroup("testGroup"); + + when(historyProxy.getPreviousConfigHistoryInfo("testDataId", "testGroup", Constants.DEFAULT_NAMESPACE_ID, 1L)).thenReturn( + configHistoryInfo); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/previous") + .param("dataId", "testDataId").param("groupName", "testGroup").param("id", "1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = JacksonUtils.toObj(actualValue, + new TypeReference>() { + }); + ConfigHistoryInfo resultConfigHistoryInfo = result.getData(); + + assertEquals("testDataId", resultConfigHistoryInfo.getDataId()); + assertEquals("testGroup", resultConfigHistoryInfo.getGroup()); + } + + @Test + void testGetConfigsByTenant() throws Exception { + ConfigInfoWrapper configInfo = new ConfigInfoWrapper(); + configInfo.setDataId("testDataId"); + configInfo.setGroup("testGroup"); + List configInfoList = Collections.singletonList(configInfo); + + when(historyProxy.getConfigsByTenant("testNamespaceId")).thenReturn(configInfoList); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/cs/history/configs") + .param("namespaceId", "testNamespaceId"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = JacksonUtils.toObj(actualValue, + new TypeReference>>() { + }); + List resultConfigInfoList = result.getData(); + + assertEquals(1, resultConfigInfoList.size()); + assertEquals("testDataId", resultConfigInfoList.get(0).getDataId()); + assertEquals("testGroup", resultConfigInfoList.get(0).getGroup()); + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java new file mode 100644 index 00000000000..635f9e266df --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleClusterControllerTest.java @@ -0,0 +1,101 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.core; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.core.ClusterProxy; +import com.alibaba.nacos.core.cluster.Member; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.jupiter.api.AfterEach; +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.MockedStatic; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; +import java.util.Collection; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mockStatic; +import static org.mockito.Mockito.when; + +/** + * ConsoleClusterControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleClusterControllerTest { + + @Mock + private ClusterProxy clusterProxy; + + @InjectMocks + private ConsoleClusterController consoleClusterController; + + private MockedStatic mockedEnvUtil; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleClusterController).build(); + + mockedEnvUtil = mockStatic(EnvUtil.class); + mockedEnvUtil.when(() -> EnvUtil.getProperty(anyString())).thenReturn("default_value"); + } + + @AfterEach + void tearDown() { + mockedEnvUtil.close(); + } + + @Test + void testGetNodeList() throws Exception { + Member member = new Member(); + member.setIp("127.0.0.1"); + member.setPort(8848); + Collection members = Arrays.asList(member); + + when(clusterProxy.getNodeList(anyString())).thenReturn(members); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/cluster/nodes") + .param("keyword", "127.0.0.1"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals(1, result.getData().size()); + assertEquals("127.0.0.1", result.getData().iterator().next().getIp()); + } +} \ No newline at end of file diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java new file mode 100644 index 00000000000..73351220878 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/core/ConsoleNamespaceControllerTest.java @@ -0,0 +1,152 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.core; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.core.NamespaceProxy; +import com.alibaba.nacos.core.namespace.model.Namespace; +import com.alibaba.nacos.core.namespace.model.form.NamespaceForm; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +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.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.Arrays; +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.ArgumentMatchers.anyString; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleNamespaceControllerTest. + * + * @author zhangyukun on:2024/8/28 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleNamespaceControllerTest { + + @Mock + private NamespaceProxy namespaceProxy; + + @InjectMocks + private ConsoleNamespaceController consoleNamespaceController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleNamespaceController).build(); + } + + @Test + void testGetNamespaceList() throws Exception { + Namespace namespace = new Namespace(); + namespace.setNamespace("testNamespace"); + namespace.setNamespaceDesc("desc"); + + List namespaces = Arrays.asList(namespace); + when(namespaceProxy.getNamespaceList()).thenReturn(namespaces); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace/list"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result> result = new ObjectMapper().readValue(actualValue, + new TypeReference>>() { + }); + + assertEquals(1, result.getData().size()); + assertEquals("testNamespace", result.getData().get(0).getNamespace()); + } + + @Test + void testGetNamespaceDetail() throws Exception { + Namespace namespace = new Namespace(); + namespace.setNamespace("testNamespace"); + namespace.setNamespaceDesc("desc"); + + when(namespaceProxy.getNamespaceDetail(anyString())).thenReturn(namespace); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace") + .param("namespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertEquals("testNamespace", result.getData().getNamespace()); + } + + @Test + void testUpdateNamespace() throws Exception { + when(namespaceProxy.updateNamespace(any(NamespaceForm.class))).thenReturn(true); + + NamespaceForm namespaceForm = new NamespaceForm("testNamespace", "testNamespaceName", "testDesc"); + + Result result = consoleNamespaceController.updateNamespace(namespaceForm); + + verify(namespaceProxy).updateNamespace(any(NamespaceForm.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertTrue(result.getData()); + } + + @Test + void testDeleteNamespace() throws Exception { + when(namespaceProxy.deleteNamespace(anyString())).thenReturn(true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.delete("/v3/console/core/namespace") + .param("namespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + assertEquals(200, response.getStatus()); + } + + @Test + void testCheckNamespaceIdExist() throws Exception { + when(namespaceProxy.checkNamespaceIdExist(anyString())).thenReturn(true); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/core/namespace/exist") + .param("customNamespaceId", "testNamespace"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertTrue(result.getData()); + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java new file mode 100644 index 00000000000..bdf77a3c3a9 --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleInstanceControllerTest.java @@ -0,0 +1,116 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.naming; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.console.proxy.naming.InstanceProxy; +import com.alibaba.nacos.naming.misc.SwitchDomain; +import com.alibaba.nacos.naming.model.form.InstanceForm; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +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.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleInstanceControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleInstanceControllerTest { + + @Mock + private InstanceProxy instanceProxy; + + @Mock + private SwitchDomain switchDomain; + + @InjectMocks + private ConsoleInstanceController consoleInstanceController; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(consoleInstanceController).build(); + } + + @Test + void testGetInstanceList() throws Exception { + ObjectNode instances = JsonNodeFactory.instance.objectNode(); + when(instanceProxy.listInstances(anyString(), anyString(), anyString(), anyInt(), anyInt(), any(), + any())).thenReturn(instances); + + MockHttpServletRequestBuilder builder = MockMvcRequestBuilders.get("/v3/console/ns/instance/list") + .param("namespaceId", "default").param("serviceName", "testService").param("pageNo", "1") + .param("pageSize", "10"); + + MockHttpServletResponse response = mockMvc.perform(builder).andReturn().getResponse(); + String actualValue = response.getContentAsString(); + + Result result = new ObjectMapper().readValue(actualValue, new TypeReference>() { + }); + + assertNotNull(result.getData()); + } + + @Test + void testUpdateInstance() throws Exception { + InstanceForm instanceForm = new InstanceForm(); + instanceForm.setServiceName("testService"); + instanceForm.setIp("127.0.0.1"); + instanceForm.setPort(8080); + instanceForm.setWeight(1.0); + + Instance instance = new Instance(); + instance.setIp("127.0.0.1"); + instance.setPort(8080); + instance.setWeight(1.0); + + doNothing().when(instanceProxy).updateInstance(any(InstanceForm.class), any(Instance.class)); + + Result result = consoleInstanceController.updateInstance(instanceForm); + + verify(instanceProxy).updateInstance(any(InstanceForm.class), any(Instance.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), result.getCode()); + assertEquals("ok", result.getData()); + } +} \ No newline at end of file diff --git a/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java new file mode 100644 index 00000000000..abe479a52ac --- /dev/null +++ b/console/src/test/java/com/alibaba/nacos/console/controller/v3/naming/ConsoleServiceControllerTest.java @@ -0,0 +1,237 @@ +/* + * Copyright 1999-2024 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.console.controller.v3.naming; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.console.proxy.naming.ServiceProxy; +import com.alibaba.nacos.core.model.form.AggregationForm; +import com.alibaba.nacos.core.model.form.PageForm; +import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; +import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import com.alibaba.nacos.naming.model.form.ServiceForm; +import com.alibaba.nacos.naming.model.form.ServiceListForm; +import com.alibaba.nacos.naming.model.form.UpdateClusterForm; +import com.alibaba.nacos.naming.selector.LabelSelector; +import com.alibaba.nacos.naming.selector.SelectorManager; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +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.junit.jupiter.MockitoExtension; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertInstanceOf; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * ConsoleServiceControllerTest. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class ConsoleServiceControllerTest { + + @Mock + private ServiceProxy serviceProxy; + + @Mock + private SelectorManager selectorManager; + + @InjectMocks + private ConsoleServiceController consoleServiceController; + + @BeforeEach + void setUp() { + consoleServiceController = new ConsoleServiceController(serviceProxy, selectorManager); + } + + @Test + void testCreateService() throws Exception { + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setServiceName("testService"); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setGroupName("testGroup"); + serviceForm.setProtectThreshold(0.8f); + serviceForm.setEphemeral(true); + serviceForm.setMetadata("{\"key\":\"value\"}"); + serviceForm.setSelector("{\"type\":\"label\",\"expression\":\"role=admin\"}"); + + when(selectorManager.parseSelector(any(String.class), any(String.class))).thenReturn(new LabelSelector()); + + Result actual = consoleServiceController.createService(serviceForm); + + verify(serviceProxy).createService(eq(serviceForm), any(ServiceMetadata.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals("ok", actual.getData()); + } + + @Test + void testDeleteService() throws Exception { + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setServiceName("testService"); + serviceForm.setGroupName("testGroup"); + Result actual = consoleServiceController.deleteService(serviceForm); + + verify(serviceProxy).deleteService(eq("testNamespace"), eq("testService"), eq("testGroup")); + + assertEquals("ok", actual.getData()); + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + } + + @Test + void testUpdateService() throws Exception { + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setServiceName("testService"); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setGroupName("testGroup"); + serviceForm.setProtectThreshold(0.8f); + serviceForm.setEphemeral(true); + serviceForm.setMetadata("{\"key\":\"value\"}"); + serviceForm.setSelector("{\"type\":\"label\",\"expression\":\"role=admin\"}"); + + when(selectorManager.parseSelector(any(String.class), any(String.class))).thenReturn(new LabelSelector()); + + Result actual = consoleServiceController.updateService(serviceForm); + + verify(serviceProxy).updateService(eq(serviceForm), + eq(Service.newService("testNamespace", "testGroup", "testService")), any(ServiceMetadata.class), + any(Map.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals("ok", actual.getData()); + } + + @Test + void testGetServiceDetail() throws Exception { + Object serviceDetail = new Object(); + + when(serviceProxy.getServiceDetail(any(String.class), any(String.class), any(String.class))).thenReturn( + serviceDetail); + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setServiceName("testService"); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setGroupName("testGroup"); + Result actual = (Result) consoleServiceController.getServiceDetail(serviceForm); + + verify(serviceProxy).getServiceDetail(any(String.class), any(String.class), any(String.class)); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(serviceDetail, actual.getData()); + } + + @Test + void testGetSelectorTypeList() throws Exception { + when(serviceProxy.getSelectorTypeList()).thenReturn(Collections.singletonList("label")); + + Result> actual = consoleServiceController.getSelectorTypeList(); + + verify(serviceProxy).getSelectorTypeList(); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(1, actual.getData().size()); + assertEquals("label", actual.getData().get(0)); + } + + @Test + void testGetSubscribers() throws Exception { + ObjectNode subscribers = new ObjectMapper().createObjectNode(); + subscribers.put("subscriber", "testSubscriber"); + + when(serviceProxy.getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyString(), + anyBoolean())).thenReturn(subscribers); + + PageForm pageForm = new PageForm(); + pageForm.setPageNo(1); + pageForm.setPageSize(10); + ServiceForm serviceForm = new ServiceForm(); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setServiceName("testService"); + serviceForm.setGroupName("testGroup"); + AggregationForm aggregationForm = new AggregationForm(); + + Result actual = consoleServiceController.subscribers(serviceForm, pageForm, aggregationForm); + + verify(serviceProxy).getSubscribers(anyInt(), anyInt(), anyString(), anyString(), anyString(), anyBoolean()); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertEquals(subscribers, actual.getData()); + } + + @Test + void testGetServiceList() throws Exception { + when(serviceProxy.getServiceList(anyBoolean(), anyString(), anyInt(), anyInt(), anyString(), anyString(), + anyBoolean())).thenReturn(Collections.singletonList(new Object())); + PageForm pageForm = new PageForm(); + pageForm.setPageNo(1); + pageForm.setPageSize(10); + ServiceListForm serviceForm = new ServiceListForm(); + serviceForm.setNamespaceId("testNamespace"); + serviceForm.setServiceNameParam("testService"); + serviceForm.setGroupNameParam("testGroup"); + Result actual = consoleServiceController.getServiceList(serviceForm, pageForm); + + verify(serviceProxy).getServiceList(anyBoolean(), anyString(), anyInt(), anyInt(), anyString(), anyString(), + anyBoolean()); + + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + assertInstanceOf(List.class, actual.getData()); + assertEquals(1, ((List) actual.getData()).size()); + } + + @Test + void testUpdateCluster() throws Exception { + UpdateClusterForm updateClusterForm = getUpdateClusterForm(); + + Result actual = consoleServiceController.updateCluster(updateClusterForm); + + verify(serviceProxy).updateClusterMetadata(anyString(), anyString(), anyString(), any(ClusterMetadata.class)); + + assertEquals("ok", actual.getData()); + assertEquals(ErrorCode.SUCCESS.getCode(), actual.getCode()); + } + + private static UpdateClusterForm getUpdateClusterForm() { + UpdateClusterForm updateClusterForm = new UpdateClusterForm(); + updateClusterForm.setNamespaceId("testNamespace"); + updateClusterForm.setGroupName("testGroup"); + updateClusterForm.setClusterName("testCluster"); + updateClusterForm.setServiceName("testService"); + updateClusterForm.setCheckPort(8080); + updateClusterForm.setUseInstancePort4Check(true); + updateClusterForm.setHealthChecker("{\"type\":\"TCP\"}"); + updateClusterForm.setMetadata("{\"key\":\"value\"}"); + return updateClusterForm; + } +} diff --git a/console/src/test/java/com/alibaba/nacos/console/exception/ConsoleExceptionHandlerTest.java b/console/src/test/java/com/alibaba/nacos/console/exception/ConsoleExceptionHandlerTest.java index c1a0fd24edc..bd843cd201a 100644 --- a/console/src/test/java/com/alibaba/nacos/console/exception/ConsoleExceptionHandlerTest.java +++ b/console/src/test/java/com/alibaba/nacos/console/exception/ConsoleExceptionHandlerTest.java @@ -19,6 +19,9 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; import com.alibaba.nacos.console.controller.v2.HealthControllerV2; +import com.alibaba.nacos.core.listener.startup.NacosStartUp; +import com.alibaba.nacos.core.listener.startup.NacosStartUpManager; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -47,6 +50,11 @@ class ConsoleExceptionHandlerTest { @MockBean private HealthControllerV2 healthControllerV2; + @BeforeAll + static void beforeAll() { + NacosStartUpManager.start(NacosStartUp.CONSOLE_START_UP_PHASE); + } + @BeforeEach void before() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); diff --git a/console/src/test/java/com/alibaba/nacos/console/exception/NacosApiExceptionHandlerTest.java b/console/src/test/java/com/alibaba/nacos/console/exception/NacosApiExceptionHandlerTest.java index be8b57a0626..d7907a32ede 100644 --- a/console/src/test/java/com/alibaba/nacos/console/exception/NacosApiExceptionHandlerTest.java +++ b/console/src/test/java/com/alibaba/nacos/console/exception/NacosApiExceptionHandlerTest.java @@ -19,6 +19,9 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; import com.alibaba.nacos.console.controller.v2.NamespaceControllerV2; +import com.alibaba.nacos.core.listener.startup.NacosStartUp; +import com.alibaba.nacos.core.listener.startup.NacosStartUpManager; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -45,6 +48,11 @@ class NacosApiExceptionHandlerTest { @MockBean private NamespaceControllerV2 namespaceControllerV2; + @BeforeAll + static void beforeAll() { + NacosStartUpManager.start(NacosStartUp.CONSOLE_START_UP_PHASE); + } + @BeforeEach void before() { mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); @@ -53,8 +61,10 @@ void before() { @Test void testNacosRunTimeExceptionHandler() throws Exception { // 设置NamespaceControllerV2的行为,使其抛出NacosRuntimeException并被NacosApiExceptionHandler捕获处理 - when(namespaceControllerV2.createNamespace(any())).thenThrow(new NacosRuntimeException(NacosException.INVALID_PARAM)) - .thenThrow(new NacosRuntimeException(NacosException.SERVER_ERROR)).thenThrow(new NacosRuntimeException(503)); + when(namespaceControllerV2.createNamespace(any())).thenThrow( + new NacosRuntimeException(NacosException.INVALID_PARAM)) + .thenThrow(new NacosRuntimeException(NacosException.SERVER_ERROR)) + .thenThrow(new NacosRuntimeException(503)); // 执行请求并验证响应码 ResultActions resultActions = mockMvc.perform(post("/v2/console/namespace")); diff --git a/console/src/test/java/com/alibaba/nacos/console/paramcheck/ParamExtractorTest.java b/console/src/test/java/com/alibaba/nacos/console/paramcheck/ParamExtractorTest.java index ba6f68589b1..3cf41cf00bb 100644 --- a/console/src/test/java/com/alibaba/nacos/console/paramcheck/ParamExtractorTest.java +++ b/console/src/test/java/com/alibaba/nacos/console/paramcheck/ParamExtractorTest.java @@ -22,6 +22,8 @@ import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.paramcheck.ParamCheckerFilter; import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; @@ -34,7 +36,7 @@ import java.lang.reflect.Method; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; /** * Param Console ExtractorTest. @@ -50,15 +52,31 @@ class ParamExtractorTest { private ParamCheckerFilter filter; + private MockedStatic mockedStatic; + + private MockedStatic managerMockedStatic; + + @BeforeEach + void setUp() { + mockedStatic = Mockito.mockStatic(EnvUtil.class); + managerMockedStatic = Mockito.mockStatic(ExtractorManager.class); + } + + @AfterEach + void tearDown() { + mockedStatic.close(); + managerMockedStatic.close(); + } + @Test void testDefaultFilter() throws Exception { - MockedStatic mockedStatic = Mockito.mockStatic(EnvUtil.class); final Method check = NamespaceController.class.getMethod("getNamespaces"); - ExtractorManager.Extractor annotation = NamespaceController.class.getAnnotation(ExtractorManager.Extractor.class); - AbstractHttpParamExtractor httpExtractor = Mockito.spy(ExtractorManager.getHttpExtractor(annotation)); + ExtractorManager.Extractor annotation = NamespaceController.class.getAnnotation( + ExtractorManager.Extractor.class); + AbstractHttpParamExtractor httpExtractor = mock(AbstractHttpParamExtractor.class); - MockedStatic managerMockedStatic = Mockito.mockStatic(ExtractorManager.class); - mockedStatic.when(() -> EnvUtil.getProperty(Mockito.any(), Mockito.any(), Mockito.any())).thenAnswer((k) -> k.getArgument(2)); + mockedStatic.when(() -> EnvUtil.getProperty(Mockito.any(), Mockito.any(), Mockito.any())) + .thenAnswer((k) -> k.getArgument(2)); filter = new ParamCheckerFilter(methodsCache); managerMockedStatic.when(() -> ExtractorManager.getHttpExtractor(annotation)).thenReturn(httpExtractor); @@ -69,10 +87,7 @@ void testDefaultFilter() throws Exception { filter.doFilter(request, response, (servletRequest, servletResponse) -> { }); - assertEquals(ConsoleDefaultHttpParamExtractor.class, httpExtractor.getClass()); Mockito.verify(httpExtractor, new Times(1)).extractParam(Mockito.any()); - managerMockedStatic.close(); - mockedStatic.close(); } } diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AbstractWebAuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/AbstractWebAuthFilter.java new file mode 100644 index 00000000000..36e9a7a2853 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AbstractWebAuthFilter.java @@ -0,0 +1,145 @@ +/* + * 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.auth; + +import com.alibaba.nacos.auth.HttpProtocolAuthService; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.auth.config.NacosAuthConfig; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; +import com.alibaba.nacos.common.utils.ExceptionUtil; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.context.RequestContext; +import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Permission; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.exception.AccessException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * Abstract Auth filter. + * + * @author xiweng.yy + */ +public abstract class AbstractWebAuthFilter implements Filter { + + private final ControllerMethodsCache methodsCache; + + private final HttpProtocolAuthService protocolAuthService; + + protected AbstractWebAuthFilter(NacosAuthConfig authConfig, ControllerMethodsCache methodsCache) { + this.methodsCache = methodsCache; + this.protocolAuthService = new HttpProtocolAuthService(authConfig); + this.protocolAuthService.initialize(); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + if (!isAuthEnabled()) { + chain.doFilter(request, response); + return; + } + HttpServletRequest req = (HttpServletRequest) request; + HttpServletResponse resp = (HttpServletResponse) response; + Method method = methodsCache.getMethod(req); + if (method == null) { + chain.doFilter(request, response); + return; + } + if (!method.isAnnotationPresent(Secured.class)) { + chain.doFilter(request, response); + return; + } + try { + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI()); + } + + Secured secured = method.getAnnotation(Secured.class); + + ServerIdentityResult serverIdentityResult = checkServerIdentity(req, secured); + switch (serverIdentityResult.getStatus()) { + case FAIL: + resp.sendError(HttpServletResponse.SC_FORBIDDEN, serverIdentityResult.getMessage()); + return; + case MATCHED: + chain.doFilter(request, response); + return; + default: + break; + } + + if (!protocolAuthService.enableAuth(secured)) { + chain.doFilter(request, response); + return; + } + Resource resource = protocolAuthService.parseResource(req, secured); + IdentityContext identityContext = protocolAuthService.parseIdentity(req); + boolean result = protocolAuthService.validateIdentity(identityContext, resource); + RequestContext requestContext = RequestContextHolder.getContext(); + requestContext.getAuthContext().setIdentityContext(identityContext); + requestContext.getAuthContext().setResource(resource); + if (null == requestContext.getAuthContext().getAuthResult()) { + requestContext.getAuthContext().setAuthResult(result); + } + if (!result) { + // TODO Get reason of failure + throw new AccessException("Validate Identity failed."); + } + String action = secured.action().toString(); + result = protocolAuthService.validateAuthority(identityContext, new Permission(resource, action)); + if (!result) { + // TODO Get reason of failure + throw new AccessException("Validate Authority failed."); + } + chain.doFilter(request, response); + } catch (AccessException e) { + if (Loggers.AUTH.isDebugEnabled()) { + Loggers.AUTH.debug("access denied, request: {} {}, reason: {}", req.getMethod(), req.getRequestURI(), + e.getErrMsg()); + } + resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getErrMsg()); + } catch (IllegalArgumentException e) { + resp.sendError(HttpServletResponse.SC_BAD_REQUEST, ExceptionUtil.getAllExceptionMsg(e)); + } catch (Exception e) { + Loggers.AUTH.warn("[AUTH-FILTER] Server failed: ", e); + + } + } + + protected ServerIdentityResult checkServerIdentity(HttpServletRequest request, Secured secured) { + return protocolAuthService.checkServerIdentity(request, secured); + } + + /** + * Whether this auth filter is enabled. + * + * @return get value from {@link NacosAuthConfig#isAuthEnabled()} + */ + protected abstract boolean isAuthEnabled(); +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthConfig.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthConfig.java index ff494e53ed6..de701b9f5f3 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/AuthConfig.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthConfig.java @@ -16,8 +16,8 @@ package com.alibaba.nacos.core.auth; -import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.web.NacosWebBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -28,6 +28,7 @@ * @author mai.jh */ @Configuration +@NacosWebBean public class AuthConfig { @Bean @@ -42,7 +43,7 @@ public FilterRegistrationBean authFilterRegistration(AuthFilter auth } @Bean - public AuthFilter authFilter(AuthConfigs authConfigs, ControllerMethodsCache methodsCache) { - return new AuthFilter(authConfigs, methodsCache); + public AuthFilter authFilter(ControllerMethodsCache methodsCache) { + return new AuthFilter(NacosServerAuthConfig.getInstance(), methodsCache); } } \ No newline at end of file diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java index 7d67128e684..c0e646e76c4 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/AuthFilter.java @@ -16,31 +16,8 @@ package com.alibaba.nacos.core.auth; -import com.alibaba.nacos.auth.HttpProtocolAuthService; -import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.common.utils.ExceptionUtil; -import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.core.code.ControllerMethodsCache; -import com.alibaba.nacos.core.context.RequestContext; -import com.alibaba.nacos.core.context.RequestContextHolder; -import com.alibaba.nacos.core.utils.Loggers; -import com.alibaba.nacos.core.utils.WebUtils; -import com.alibaba.nacos.plugin.auth.api.IdentityContext; -import com.alibaba.nacos.plugin.auth.api.Permission; -import com.alibaba.nacos.plugin.auth.api.Resource; -import com.alibaba.nacos.plugin.auth.exception.AccessException; -import com.alibaba.nacos.sys.env.Constants; - -import jakarta.servlet.Filter; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.lang.reflect.Method; /** * Unified filter to handle authentication and authorization. @@ -48,99 +25,17 @@ * @author nkorange * @since 1.2.0 */ -public class AuthFilter implements Filter { - - private final AuthConfigs authConfigs; - - private final ControllerMethodsCache methodsCache; +public class AuthFilter extends AbstractWebAuthFilter { - private final HttpProtocolAuthService protocolAuthService; + private final NacosAuthConfig authConfig; - public AuthFilter(AuthConfigs authConfigs, ControllerMethodsCache methodsCache) { - this.authConfigs = authConfigs; - this.methodsCache = methodsCache; - this.protocolAuthService = new HttpProtocolAuthService(authConfigs); - this.protocolAuthService.initialize(); + public AuthFilter(NacosAuthConfig authConfig, ControllerMethodsCache methodsCache) { + super(authConfig, methodsCache); + this.authConfig = authConfig; } @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { - if (!authConfigs.isAuthEnabled()) { - chain.doFilter(request, response); - return; - } - HttpServletRequest req = (HttpServletRequest) request; - HttpServletResponse resp = (HttpServletResponse) response; - if (authConfigs.isEnableUserAgentAuthWhite()) { - String userAgent = WebUtils.getUserAgent(req); - if (StringUtils.startsWith(userAgent, Constants.NACOS_SERVER_HEADER)) { - chain.doFilter(request, response); - return; - } - } else if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey()) && StringUtils.isNotBlank( - authConfigs.getServerIdentityValue())) { - String serverIdentity = req.getHeader(authConfigs.getServerIdentityKey()); - if (StringUtils.isNotBlank(serverIdentity)) { - if (authConfigs.getServerIdentityValue().equals(serverIdentity)) { - chain.doFilter(request, response); - return; - } - Loggers.AUTH.warn("Invalid server identity value for {} from {}", authConfigs.getServerIdentityKey(), - req.getRemoteHost()); - } - } else { - resp.sendError(HttpServletResponse.SC_FORBIDDEN, - "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" - + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`"); - return; - } - try { - Method method = methodsCache.getMethod(req); - if (method == null) { - chain.doFilter(request, response); - return; - } - if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) { - if (Loggers.AUTH.isDebugEnabled()) { - Loggers.AUTH.debug("auth start, request: {} {}", req.getMethod(), req.getRequestURI()); - } - Secured secured = method.getAnnotation(Secured.class); - if (!protocolAuthService.enableAuth(secured)) { - chain.doFilter(request, response); - return; - } - Resource resource = protocolAuthService.parseResource(req, secured); - IdentityContext identityContext = protocolAuthService.parseIdentity(req); - boolean result = protocolAuthService.validateIdentity(identityContext, resource); - RequestContext requestContext = RequestContextHolder.getContext(); - requestContext.getAuthContext().setIdentityContext(identityContext); - requestContext.getAuthContext().setResource(resource); - if (null == requestContext.getAuthContext().getAuthResult()) { - requestContext.getAuthContext().setAuthResult(result); - } - if (!result) { - // TODO Get reason of failure - throw new AccessException("Validate Identity failed."); - } - String action = secured.action().toString(); - result = protocolAuthService.validateAuthority(identityContext, new Permission(resource, action)); - if (!result) { - // TODO Get reason of failure - throw new AccessException("Validate Authority failed."); - } - } - chain.doFilter(request, response); - } catch (AccessException e) { - if (Loggers.AUTH.isDebugEnabled()) { - Loggers.AUTH.debug("access denied, request: {} {}, reason: {}", req.getMethod(), req.getRequestURI(), - e.getErrMsg()); - } - resp.sendError(HttpServletResponse.SC_FORBIDDEN, e.getErrMsg()); - } catch (IllegalArgumentException e) { - resp.sendError(HttpServletResponse.SC_BAD_REQUEST, ExceptionUtil.getAllExceptionMsg(e)); - } catch (Exception e) { - Loggers.AUTH.warn("[AUTH-FILTER] Server failed: ", e); - resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Server failed, " + e.getMessage()); - } + protected boolean isAuthEnabled() { + return authConfig.isAuthEnabled(); } } diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/NacosServerAuthConfig.java b/core/src/main/java/com/alibaba/nacos/core/auth/NacosServerAuthConfig.java new file mode 100644 index 00000000000..d3a281e93af --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/auth/NacosServerAuthConfig.java @@ -0,0 +1,178 @@ +/* + * 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.auth; + +import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; +import com.alibaba.nacos.auth.config.AuthErrorCode; +import com.alibaba.nacos.auth.config.AuthModuleStateBuilder; +import com.alibaba.nacos.auth.config.NacosAuthConfig; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.config.AbstractDynamicConfig; +import com.alibaba.nacos.plugin.auth.constant.Constants; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.module.ModuleState; +import com.alibaba.nacos.sys.module.ModuleStateHolder; +import com.alibaba.nacos.sys.utils.PropertiesUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * Nacos Server auth configurations. + * + * @author xiweng.yy + */ +public class NacosServerAuthConfig extends AbstractDynamicConfig implements NacosAuthConfig { + + private static final Logger LOGGER = LoggerFactory.getLogger(NacosServerAuthConfig.class); + + private static final String PREFIX = "nacos.core.auth.plugin"; + + private static final NacosServerAuthConfig INSTANCE = new NacosServerAuthConfig(); + + /** + * Whether server auth enabled. + */ + private boolean authEnabled; + + /** + * Which auth system is in use. + */ + private String nacosAuthSystemType; + + private String serverIdentityKey; + + private String serverIdentityValue; + + private Map authPluginProperties = new HashMap<>(); + + private NacosServerAuthConfig() { + super("NacosServerAuth"); + resetConfig(); + validate(); + } + + public static NacosServerAuthConfig getInstance() { + return INSTANCE; + } + + /** + * Validate auth config. + */ + private void validate() { + if (!authEnabled) { + return; + } + if (StringUtils.isEmpty(nacosAuthSystemType)) { + throw new NacosRuntimeException(AuthErrorCode.INVALID_TYPE.getCode(), AuthErrorCode.INVALID_TYPE.getMsg()); + } + if (StringUtils.isEmpty(serverIdentityKey) || StringUtils.isEmpty(serverIdentityValue)) { + throw new NacosRuntimeException(AuthErrorCode.EMPTY_IDENTITY.getCode(), + AuthErrorCode.EMPTY_IDENTITY.getMsg()); + } + } + + private void refreshPluginProperties() { + try { + Map newProperties = new HashMap<>(1); + Properties properties = PropertiesUtil.getPropertiesWithPrefix(EnvUtil.getEnvironment(), PREFIX); + if (properties != null) { + for (String each : properties.stringPropertyNames()) { + int typeIndex = each.indexOf('.'); + String type = each.substring(0, typeIndex); + String subKey = each.substring(typeIndex + 1); + newProperties.computeIfAbsent(type, key -> new Properties()) + .setProperty(subKey, properties.getProperty(each)); + } + } + authPluginProperties = newProperties; + } catch (Exception e) { + LOGGER.warn("Refresh plugin properties failed ", e); + } + } + + /** + * server auth function is open. + * + * @return server auth function is open + */ + @Override + public boolean isAuthEnabled() { + return authEnabled; + } + + @Override + public String getNacosAuthSystemType() { + return nacosAuthSystemType; + } + + @Override + public boolean isSupportServerIdentity() { + return true; + } + + @Override + public String getServerIdentityKey() { + return serverIdentityKey; + } + + @Override + public String getServerIdentityValue() { + return serverIdentityValue; + } + + public Properties getAuthPluginProperties(String authType) { + if (!authPluginProperties.containsKey(authType)) { + LOGGER.warn("Can't find properties for type {}, will use empty properties", authType); + return new Properties(); + } + return authPluginProperties.get(authType); + } + + @Override + protected void getConfigFromEnv() { + try { + authEnabled = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_ENABLED, Boolean.class, false); + nacosAuthSystemType = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_SYSTEM_TYPE, ""); + serverIdentityKey = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_SERVER_IDENTITY_KEY, ""); + serverIdentityValue = EnvUtil.getProperty(Constants.Auth.NACOS_CORE_AUTH_SERVER_IDENTITY_VALUE, ""); + refreshPluginProperties(); + ModuleStateHolder.getInstance().getModuleState(AuthModuleStateBuilder.AUTH_MODULE) + .ifPresent(moduleState -> { + ModuleState temp = new AuthModuleStateBuilder().build(); + moduleState.getStates().putAll(temp.getStates()); + }); + } catch (Exception e) { + LOGGER.warn("Upgrade auth config from env failed, use old value", e); + } + } + + @Override + protected String printConfig() { + return toString(); + } + + @Override + public String toString() { + return "NacosServerAuthConfig{" + "authEnabled=" + authEnabled + ", nacosAuthSystemType='" + nacosAuthSystemType + + '\'' + ", serverIdentityKey='" + serverIdentityKey + '\'' + ", serverIdentityValue='" + + serverIdentityValue + '\'' + ", authPluginProperties=" + authPluginProperties + '}'; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java index 21b000cc9e4..f18f6db1ff6 100644 --- a/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java +++ b/core/src/main/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilter.java @@ -22,7 +22,8 @@ import com.alibaba.nacos.api.remote.response.Response; import com.alibaba.nacos.auth.GrpcProtocolAuthService; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; +import com.alibaba.nacos.auth.serveridentity.ServerIdentityResult; import com.alibaba.nacos.common.utils.ExceptionUtil; import com.alibaba.nacos.core.context.RequestContext; import com.alibaba.nacos.core.context.RequestContextHolder; @@ -31,6 +32,7 @@ import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.api.Permission; import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.plugin.auth.constant.Constants; import com.alibaba.nacos.plugin.auth.exception.AccessException; import org.springframework.stereotype.Component; @@ -46,13 +48,13 @@ @Component public class RemoteRequestAuthFilter extends AbstractRequestFilter { - private final AuthConfigs authConfigs; + private final NacosAuthConfig authConfig; private final GrpcProtocolAuthService protocolAuthService; - public RemoteRequestAuthFilter(AuthConfigs authConfigs) { - this.authConfigs = authConfigs; - this.protocolAuthService = new GrpcProtocolAuthService(authConfigs); + public RemoteRequestAuthFilter() { + this.authConfig = NacosServerAuthConfig.getInstance(); + this.protocolAuthService = new GrpcProtocolAuthService(authConfig); this.protocolAuthService.initialize(); } @@ -62,13 +64,26 @@ public Response filter(Request request, RequestMeta meta, Class handlerClazz) th try { Method method = getHandleMethod(handlerClazz); - if (method.isAnnotationPresent(Secured.class) && authConfigs.isAuthEnabled()) { - + if (method.isAnnotationPresent(Secured.class)) { + Secured secured = method.getAnnotation(Secured.class); + // Inner API must do check server identity. So judge api type not inner api and whether auth is enabled. + if (ApiType.INNER_API != secured.apiType() && !authConfig.isAuthEnabled()) { + return null; + } if (Loggers.AUTH.isDebugEnabled()) { Loggers.AUTH.debug("auth start, request: {}", request.getClass().getSimpleName()); } - - Secured secured = method.getAnnotation(Secured.class); + ServerIdentityResult identityResult = protocolAuthService.checkServerIdentity(request, secured); + switch (identityResult.getStatus()) { + case FAIL: + Response defaultResponseInstance = getDefaultResponseInstance(handlerClazz); + defaultResponseInstance.setErrorInfo(NacosException.NO_RIGHT, identityResult.getMessage()); + return defaultResponseInstance; + case MATCHED: + return null; + default: + break; + } if (!protocolAuthService.enableAuth(secured)) { return null; } @@ -104,7 +119,6 @@ public Response filter(Request request, RequestMeta meta, Class handlerClazz) th return defaultResponseInstance; } catch (Exception e) { Response defaultResponseInstance = getDefaultResponseInstance(handlerClazz); - defaultResponseInstance.setErrorInfo(NacosException.SERVER_ERROR, ExceptionUtil.getAllExceptionMsg(e)); return defaultResponseInstance; } diff --git a/core/src/main/java/com/alibaba/nacos/core/cluster/MemberMetaDataConstants.java b/core/src/main/java/com/alibaba/nacos/core/cluster/MemberMetaDataConstants.java index a72bc666ff2..c7eb2de6c81 100644 --- a/core/src/main/java/com/alibaba/nacos/core/cluster/MemberMetaDataConstants.java +++ b/core/src/main/java/com/alibaba/nacos/core/cluster/MemberMetaDataConstants.java @@ -42,6 +42,8 @@ public class MemberMetaDataConstants { public static final String READY_TO_UPGRADE = "readyToUpgrade"; + public static final String SUPPORT_GRAY_MODEL = "supportGrayModel"; + public static final String[] BASIC_META_KEYS = new String[] {SITE_KEY, AD_WEIGHT, RAFT_PORT, WEIGHT, VERSION, READY_TO_UPGRADE}; } diff --git a/core/src/main/java/com/alibaba/nacos/core/cluster/ServerMemberManager.java b/core/src/main/java/com/alibaba/nacos/core/cluster/ServerMemberManager.java index da3e4d96bd6..f21db435665 100644 --- a/core/src/main/java/com/alibaba/nacos/core/cluster/ServerMemberManager.java +++ b/core/src/main/java/com/alibaba/nacos/core/cluster/ServerMemberManager.java @@ -19,7 +19,6 @@ import com.alibaba.nacos.api.ability.ServerAbilities; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.response.ResponseCode; -import com.alibaba.nacos.core.cluster.remote.request.MemberReportRequest; import com.alibaba.nacos.auth.util.AuthHeaderUtil; import com.alibaba.nacos.common.JustForTest; import com.alibaba.nacos.common.http.Callback; @@ -39,8 +38,10 @@ import com.alibaba.nacos.common.utils.VersionUtils; import com.alibaba.nacos.core.ability.ServerAbilityInitializer; import com.alibaba.nacos.core.ability.ServerAbilityInitializerHolder; +import com.alibaba.nacos.core.ability.control.ServerAbilityControlManager; import com.alibaba.nacos.core.cluster.lookup.LookupFactory; import com.alibaba.nacos.core.cluster.remote.ClusterRpcClientProxy; +import com.alibaba.nacos.core.cluster.remote.request.MemberReportRequest; import com.alibaba.nacos.core.cluster.remote.response.MemberReportResponse; import com.alibaba.nacos.core.utils.Commons; import com.alibaba.nacos.core.utils.GenericType; @@ -50,12 +51,9 @@ import com.alibaba.nacos.sys.env.EnvUtil; import com.alibaba.nacos.sys.utils.ApplicationUtils; import com.alibaba.nacos.sys.utils.InetUtils; -import org.springframework.boot.web.context.WebServerInitializedEvent; -import org.springframework.context.ApplicationListener; import org.springframework.stereotype.Component; import javax.annotation.PreDestroy; -import jakarta.servlet.ServletContext; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -65,7 +63,6 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentSkipListMap; -import com.alibaba.nacos.core.ability.control.ServerAbilityControlManager; import static com.alibaba.nacos.api.exception.NacosException.CLIENT_INVALID_PARAM; @@ -89,7 +86,7 @@ * @author liaochuntao */ @Component(value = "serverMemberManager") -public class ServerMemberManager implements ApplicationListener { +public class ServerMemberManager { private final NacosAsyncRestTemplate asyncRestTemplate = HttpClientBeanHolder.getNacosAsyncRestTemplate( Loggers.CORE); @@ -139,7 +136,7 @@ public class ServerMemberManager implements ApplicationListener(); - EnvUtil.setContextPath(servletContext.getContextPath()); init(); } @@ -164,8 +160,10 @@ protected void init() throws NacosException { this.localAddress = InetUtils.getSelfIP() + ":" + port; this.self = MemberUtil.singleParse(this.localAddress); this.self.setExtendVal(MemberMetaDataConstants.VERSION, VersionUtils.version); + //works for gray model upgrade,can delete after compatibility period. + this.self.setExtendVal(MemberMetaDataConstants.SUPPORT_GRAY_MODEL, true); this.self.setGrpcReportEnabled(true); - + // init abilities. this.self.setAbilities(initMemberAbilities()); @@ -197,7 +195,7 @@ private ServerAbilities initMemberAbilities() { } return serverAbilities; } - + private void registerClusterEvent() { // Register node change events NotifyCenter.registerToPublisher(MembersChangeEvent.class, @@ -236,7 +234,7 @@ private void initAndStartLookup() throws NacosException { isUseAddressServer = this.lookup.useAddressServer(); this.lookup.start(); } - + /** * switch look up. * @@ -248,7 +246,7 @@ public void switchLookup(String name) throws NacosException { isUseAddressServer = this.lookup.useAddressServer(); this.lookup.start(); } - + public static boolean isUseAddressServer() { return isUseAddressServer; } @@ -464,7 +462,7 @@ public boolean stateCheck(String address, List nodeStates) { } return false; } - + /** * this member {@link Member#getState()} is health. * @@ -483,20 +481,13 @@ public boolean isFirstIp() { return Objects.equals(serverList.firstKey(), this.localAddress); } - @Override - public void onApplicationEvent(WebServerInitializedEvent event) { - String serverNamespace = event.getApplicationContext().getServerNamespace(); - if (SPRING_MANAGEMENT_CONTEXT_NAMESPACE.equals(serverNamespace)) { - // ignore - // fix#issue https://github.com/alibaba/nacos/issues/7230 - return; - } + public void setSelfReady(int port) { getSelf().setState(NodeState.UP); if (!EnvUtil.getStandaloneMode()) { GlobalExecutor.scheduleByCommon(this.infoReportTask, DEFAULT_TASK_DELAY_TIME); GlobalExecutor.scheduleByCommon(this.unhealthyMemberInfoReportTask, DEFAULT_TASK_DELAY_TIME); } - EnvUtil.setPort(event.getWebServer().getPort()); + EnvUtil.setPort(port); EnvUtil.setLocalAddress(this.localAddress); Loggers.CLUSTER.info("This node is ready to provide external services"); } @@ -552,9 +543,9 @@ class MemberInfoReportTask extends Task { private int cursor = 0; private ClusterRpcClientProxy clusterRpcClientProxy; - + public static final long REPORT_INTERVAL = 50000L; - + @Override protected void executeBody() { List members = ServerMemberManager.this.allMembersWithoutSelf(); @@ -564,7 +555,7 @@ protected void executeBody() { Loggers.CLUSTER.info("[serverlist] membercount={}", members.size() + 1); memberReportTs = System.currentTimeMillis(); } - + if (members.isEmpty()) { return; } @@ -573,7 +564,7 @@ protected void executeBody() { Member target = members.get(cursor); Loggers.CLUSTER.debug("report the metadata to the node : {}", target.getAddress()); - + // adapt old version if (target.getAbilities().getRemoteAbility().isGrpcReportEnabled() || target.isGrpcReportEnabled()) { reportByGrpc(target); @@ -581,7 +572,7 @@ protected void executeBody() { reportByHttp(target); } } - + protected void reportByHttp(Member target) { final String url = HttpUtils.buildUrl(false, target.getAddress(), EnvUtil.getContextPath(), Commons.NACOS_CORE_CONTEXT, "/cluster/report"); @@ -628,7 +619,7 @@ public void onCancel() { target.getAbilities().getRemoteAbility().setGrpcReportEnabled(true); } } - + protected void reportByGrpc(Member target) { //Todo circular reference if (Objects.isNull(clusterRpcClientProxy)) { @@ -639,9 +630,9 @@ protected void reportByGrpc(Member target) { new NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: " + target)); return; } - + MemberReportRequest memberReportRequest = new MemberReportRequest(getSelf()); - + try { MemberReportResponse response = (MemberReportResponse) clusterRpcClientProxy.sendRequest(target, memberReportRequest); @@ -664,7 +655,7 @@ protected void reportByGrpc(Member target) { protected void after() { GlobalExecutor.scheduleByCommon(this, 2_000L); } - + private void handleReportResult(String reportResult, Member target) { if (isBooleanResult(reportResult)) { MemberUtil.onSuccess(ServerMemberManager.this, target); @@ -679,18 +670,18 @@ private void handleReportResult(String reportResult, Member target) { MemberUtil.onSuccess(ServerMemberManager.this, target); } } - + private boolean isBooleanResult(String reportResult) { return Boolean.TRUE.toString().equals(reportResult) || Boolean.FALSE.toString().equals(reportResult); } } - + class UnhealthyMemberInfoReportTask extends MemberInfoReportTask { - + @Override protected void executeBody() { List members = ServerMemberManager.this.allMembersWithoutSelf(); - + if (members.isEmpty()) { return; } @@ -702,11 +693,11 @@ protected void executeBody() { reportByHttp(member); } Loggers.CLUSTER.warn("report the metadata to the unhealthy node : {}", member.getAddress()); - + } } } - + @Override protected void after() { GlobalExecutor.scheduleByCommon(this, 5_000L); diff --git a/core/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java b/core/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java index 3ea64d7510e..d93088ad8ac 100644 --- a/core/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java +++ b/core/src/main/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxy.java @@ -21,21 +21,22 @@ import com.alibaba.nacos.api.remote.RequestCallBack; import com.alibaba.nacos.api.remote.request.Request; import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.remote.ConnectionType; import com.alibaba.nacos.common.remote.client.RpcClient; import com.alibaba.nacos.common.remote.client.RpcClientFactory; import com.alibaba.nacos.common.remote.client.RpcClientTlsConfig; -import com.alibaba.nacos.common.remote.client.ServerListFactory; import com.alibaba.nacos.common.remote.client.RpcClientTlsConfigFactory; +import com.alibaba.nacos.common.remote.client.ServerListFactory; import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.cluster.Member; import com.alibaba.nacos.core.cluster.MemberChangeListener; import com.alibaba.nacos.core.cluster.MembersChangeEvent; import com.alibaba.nacos.core.cluster.ServerMemberManager; import com.alibaba.nacos.core.utils.Loggers; import com.alibaba.nacos.sys.env.EnvUtil; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import javax.annotation.PostConstruct; @@ -60,8 +61,14 @@ public class ClusterRpcClientProxy extends MemberChangeListener { private static final long DEFAULT_REQUEST_TIME_OUT = 3000L; - @Autowired - ServerMemberManager serverMemberManager; + final ServerMemberManager serverMemberManager; + + final AuthConfigs authConfigs; + + public ClusterRpcClientProxy(ServerMemberManager serverMemberManager, AuthConfigs authConfigs) { + this.serverMemberManager = serverMemberManager; + this.authConfigs = authConfigs; + } /** * init after constructor. @@ -184,6 +191,7 @@ public Response sendRequest(Member member, Request request) throws NacosExceptio public Response sendRequest(Member member, Request request, long timeoutMills) throws NacosException { RpcClient client = RpcClientFactory.getClient(memberClientKey(member)); if (client != null) { + injectorServerIdentity(request); return client.request(request, timeoutMills); } else { throw new NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: " + member); @@ -201,6 +209,7 @@ public Response sendRequest(Member member, Request request, long timeoutMills) t public void asyncRequest(Member member, Request request, RequestCallBack callBack) throws NacosException { RpcClient client = RpcClientFactory.getClient(memberClientKey(member)); if (client != null) { + injectorServerIdentity(request); client.asyncRequest(request, callBack); } else { throw new NacosException(CLIENT_INVALID_PARAM, "No rpc client related to member: " + member); @@ -243,4 +252,10 @@ public boolean isRunning(Member member) { } return client.isRunning(); } + + private void injectorServerIdentity(Request request) { + if (StringUtils.isNotBlank(authConfigs.getServerIdentityKey())) { + request.putHeader(authConfigs.getServerIdentityKey(), authConfigs.getServerIdentityValue()); + } + } } diff --git a/core/src/main/java/com/alibaba/nacos/core/cluster/remote/MemberReportHandler.java b/core/src/main/java/com/alibaba/nacos/core/cluster/remote/MemberReportHandler.java index a2e9372c568..09c87db5b45 100644 --- a/core/src/main/java/com/alibaba/nacos/core/cluster/remote/MemberReportHandler.java +++ b/core/src/main/java/com/alibaba/nacos/core/cluster/remote/MemberReportHandler.java @@ -21,6 +21,7 @@ import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.remote.RemoteConstants; import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.utils.LoggerUtils; import com.alibaba.nacos.core.cluster.Member; import com.alibaba.nacos.core.cluster.NodeState; @@ -30,6 +31,8 @@ import com.alibaba.nacos.core.remote.RequestHandler; import com.alibaba.nacos.core.remote.grpc.InvokeSource; import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.plugin.auth.constant.SignType; import org.springframework.stereotype.Component; /** @@ -48,6 +51,7 @@ public MemberReportHandler(ServerMemberManager memberManager) { } @Override + @Secured(resource = "report", signType = SignType.SPECIFIED, apiType = ApiType.INNER_API) public MemberReportResponse handle(MemberReportRequest request, RequestMeta meta) throws NacosException { Member node = request.getNode(); if (!node.check()) { diff --git a/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfig.java b/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfig.java index 2ffd05f71f7..dfb6f36ab37 100644 --- a/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfig.java +++ b/core/src/main/java/com/alibaba/nacos/core/context/remote/HttpRequestContextConfig.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.core.context.remote; +import com.alibaba.nacos.core.web.NacosWebBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -26,6 +27,7 @@ * @author xiweng.yy */ @Configuration +@NacosWebBean public class HttpRequestContextConfig { @Bean diff --git a/core/src/main/java/com/alibaba/nacos/core/control/http/HttpTpsPointRegistry.java b/core/src/main/java/com/alibaba/nacos/core/control/http/HttpTpsPointRegistry.java index 7752eebce10..19f07ebacc5 100644 --- a/core/src/main/java/com/alibaba/nacos/core/control/http/HttpTpsPointRegistry.java +++ b/core/src/main/java/com/alibaba/nacos/core/control/http/HttpTpsPointRegistry.java @@ -20,6 +20,7 @@ import com.alibaba.nacos.core.control.TpsControl; import com.alibaba.nacos.core.control.TpsControlConfig; +import com.alibaba.nacos.core.web.NacosWebBean; import com.alibaba.nacos.plugin.control.ControlManagerCenter; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; @@ -39,6 +40,7 @@ * @version $Id: RequestHandlerRegistry.java, v 0.1 2020年07月13日 8:24 PM liuzunfei Exp $ */ @Service +@NacosWebBean public class HttpTpsPointRegistry implements ApplicationListener { private volatile AtomicBoolean isInit = new AtomicBoolean(false); diff --git a/core/src/main/java/com/alibaba/nacos/core/control/http/NacosHttpTpsControlRegistration.java b/core/src/main/java/com/alibaba/nacos/core/control/http/NacosHttpTpsControlRegistration.java index 9d1ba224bd7..a8f19c89db1 100644 --- a/core/src/main/java/com/alibaba/nacos/core/control/http/NacosHttpTpsControlRegistration.java +++ b/core/src/main/java/com/alibaba/nacos/core/control/http/NacosHttpTpsControlRegistration.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.core.control.http; import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.web.NacosWebBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -27,6 +28,7 @@ * @author xiweng.yy */ @Configuration +@NacosWebBean public class NacosHttpTpsControlRegistration { @Bean 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 409584ef384..9b59fcae11b 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 @@ -17,6 +17,7 @@ package com.alibaba.nacos.core.controller; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.common.model.RestResult; import com.alibaba.nacos.common.model.RestResultUtils; @@ -24,6 +25,7 @@ import com.alibaba.nacos.core.distributed.id.IdGeneratorManager; import com.alibaba.nacos.core.utils.Commons; import com.alibaba.nacos.core.utils.Loggers; +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; @@ -44,6 +46,7 @@ */ @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT + "/ops") +@Deprecated public class CoreOpsController { private final ProtocolManager protocolManager; @@ -64,6 +67,7 @@ public CoreOpsController(ProtocolManager protocolManager, IdGeneratorManager idG @PostMapping(value = "/raft") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin") + @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); } @@ -74,6 +78,7 @@ public RestResult raftOps(@RequestBody Map commands) { * @return {@link RestResult} */ @GetMapping(value = "/idInfo") + @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())); @@ -82,6 +87,7 @@ public RestResult>> idInfo() { @PutMapping(value = "/log") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) + @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 746bd026ccd..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 @@ -25,9 +25,11 @@ import com.alibaba.nacos.core.cluster.Member; import com.alibaba.nacos.core.cluster.NodeState; import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; 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; @@ -46,6 +48,7 @@ */ @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT + "/cluster") +@Deprecated public class NacosClusterController { private final ServerMemberManager memberManager; @@ -56,6 +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, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/self") public RestResult self() { return RestResultUtils.success(memberManager.getSelf()); } @@ -68,6 +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/admin/core/cluster/node/list") public RestResult> listNodes( @RequestParam(value = "keyword", required = false) String ipKeyWord) { Collection members = memberManager.allMembers(); @@ -92,12 +97,14 @@ public RestResult> listNodes( @GetMapping(value = "/simple/nodes") @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.ADMIN_API) public RestResult> listSimpleNodes() { return RestResultUtils.success(memberManager.getMemberAddressInfos()); } @GetMapping("/health") @Secured(resource = Commons.NACOS_CORE_CONTEXT + "/cluster", action = ActionTypes.READ, signType = SignType.CONSOLE) + @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()); } @@ -110,7 +117,9 @@ 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()) { return RestResultUtils.failedWithMsg(400, "Node information is illegal"); @@ -129,7 +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) + @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); @@ -147,7 +158,9 @@ 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 { return RestResultUtils.failed(405, null, "/v1/core/cluster/server/leave API not allow to use temporarily."); 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 8b4fc602020..3f4eb4f5c0a 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 @@ -29,6 +29,7 @@ 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.controller.compatibility.Compatibility; import com.alibaba.nacos.core.remote.Connection; import com.alibaba.nacos.core.remote.ConnectionManager; import com.alibaba.nacos.core.remote.core.ServerLoaderInfoRequestHandler; @@ -36,6 +37,7 @@ import com.alibaba.nacos.core.utils.Commons; import com.alibaba.nacos.core.utils.RemoteUtils; 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.http.ResponseEntity; @@ -64,6 +66,7 @@ */ @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/loader") +@Deprecated public class ServerLoaderController { private static final Logger LOGGER = LoggerFactory.getLogger(ServerLoaderController.class); @@ -107,6 +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, alternatives = "GET {contextPath:nacos}/v3/core/loader/current") public ResponseEntity> currentClients() { Map stringConnectionMap = connectionManager.currentClients(); return ResponseEntity.ok().body(stringConnectionMap); @@ -119,6 +123,7 @@ public ResponseEntity> currentClients() { */ @Secured(resource = Commons.NACOS_CORE_CONTEXT_V2 + "/loader", action = ActionTypes.WRITE) @GetMapping("/reloadCurrent") + @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); @@ -126,13 +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, 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) { @@ -243,6 +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, 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); @@ -256,6 +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, alternatives = "GET {contextPath:nacos}/v3/core/loader/cluster") public ResponseEntity> loaderMetrics() { Map serverLoadMetrics = getServerLoadMetrics(); @@ -313,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/compatibility/ApiCompatibilityConfig.java b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityConfig.java new file mode 100644 index 00000000000..6e2df3e3308 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityConfig.java @@ -0,0 +1,98 @@ +/* + * 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.controller.compatibility; + +import com.alibaba.nacos.core.config.AbstractDynamicConfig; +import com.alibaba.nacos.sys.env.EnvUtil; + +/** + * Compatible for old version API configuration. + * + * @author xiweng.yy + */ +public class ApiCompatibilityConfig extends AbstractDynamicConfig { + + private static final String API_COMPATIBILITY = "ApiCompatibility"; + + private static final ApiCompatibilityConfig INSTANCE = new ApiCompatibilityConfig(); + + private static final String PREFIX = "nacos.core.api.compatibility"; + + public static final String CLIENT_API_COMPATIBILITY_KEY = PREFIX + ".client.enabled"; + + public static final String CONSOLE_API_COMPATIBILITY_KEY = PREFIX + ".console.enabled"; + + public static final String ADMIN_API_COMPATIBILITY_KEY = PREFIX + ".admin.enabled"; + + private boolean clientApiCompatibility; + + private boolean consoleApiCompatibility; + + private boolean adminApiCompatibility; + + protected ApiCompatibilityConfig() { + super(API_COMPATIBILITY); + resetConfig(); + } + + public static ApiCompatibilityConfig getInstance() { + return INSTANCE; + } + + @Override + protected void getConfigFromEnv() { + clientApiCompatibility = EnvUtil.getProperty(CLIENT_API_COMPATIBILITY_KEY, Boolean.class, true); + consoleApiCompatibility = EnvUtil.getProperty(CONSOLE_API_COMPATIBILITY_KEY, Boolean.class, false); + adminApiCompatibility = EnvUtil.getProperty(ADMIN_API_COMPATIBILITY_KEY, Boolean.class, true); + } + + @Override + protected String printConfig() { + return toString(); + } + + @Override + public String toString() { + return "ApiCompatibilityConfig{" + "clientApiCompatibility=" + clientApiCompatibility + + ", consoleApiCompatibility=" + consoleApiCompatibility + ", adminApiCompatibility=" + + adminApiCompatibility + '}'; + } + + public boolean isClientApiCompatibility() { + return clientApiCompatibility; + } + + public void setClientApiCompatibility(boolean clientApiCompatibility) { + this.clientApiCompatibility = clientApiCompatibility; + } + + public boolean isConsoleApiCompatibility() { + return consoleApiCompatibility; + } + + public void setConsoleApiCompatibility(boolean consoleApiCompatibility) { + this.consoleApiCompatibility = consoleApiCompatibility; + } + + public boolean isAdminApiCompatibility() { + return adminApiCompatibility; + } + + public void setAdminApiCompatibility(boolean adminApiCompatibility) { + this.adminApiCompatibility = adminApiCompatibility; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityFilter.java b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityFilter.java new file mode 100644 index 00000000000..8893fa74264 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityFilter.java @@ -0,0 +1,119 @@ +/* + * 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.controller.compatibility; + +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.plugin.auth.constant.ApiType; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Method; + +/** + * HTTP Filter for API Compatibility. + * + * @author xiweng.yy + */ +public class ApiCompatibilityFilter implements Filter { + + private static final String MESSAGE_NO_REPLACED_API = "Current API will be deprecated, If wanted continue to use, " + + "please set `%s=true` in application.properties."; + + private static final String MESSAGE_REPLACED_API = + "Current API will be deprecated, please use API(s) `%s` instead, " + + "or set `%s=true` in application.properties."; + + private final ControllerMethodsCache methodsCache; + + public ApiCompatibilityFilter(ControllerMethodsCache methodsCache) { + this.methodsCache = methodsCache; + } + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + try { + Method method = methodsCache.getMethod(request); + if (method == null) { + filterChain.doFilter(servletRequest, servletResponse); + return; + } + Compatibility compatibility = method.getAnnotation(Compatibility.class); + if (null == compatibility) { + filterChain.doFilter(servletRequest, servletResponse); + return; + } + ApiType apiType = compatibility.apiType(); + switch (apiType) { + case ADMIN_API: + if (!ApiCompatibilityConfig.getInstance().isAdminApiCompatibility()) { + responseReject(response, compatibility, ApiCompatibilityConfig.ADMIN_API_COMPATIBILITY_KEY); + return; + } + break; + case OPEN_API: + if (!ApiCompatibilityConfig.getInstance().isClientApiCompatibility()) { + responseReject(response, compatibility, ApiCompatibilityConfig.CLIENT_API_COMPATIBILITY_KEY); + return; + } + break; + case CONSOLE_API: + if (!ApiCompatibilityConfig.getInstance().isConsoleApiCompatibility()) { + responseReject(response, compatibility, ApiCompatibilityConfig.CONSOLE_API_COMPATIBILITY_KEY); + return; + } + break; + case INNER_API: + // TODO add inner api compatibility when upgrading and disabled when upgraded. + default: + filterChain.doFilter(servletRequest, servletResponse); + return; + } + filterChain.doFilter(servletRequest, servletResponse); + } catch (Exception e) { + Loggers.CORE.error("Filter for API {} Compatibility failed.", request.getRequestURI(), e); + response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, + "Handle API Compatibility failed, please see log for detail."); + } + } + + private void responseReject(HttpServletResponse response, Compatibility compatibility, String switchName) + throws IOException { + String message; + String replacedApis = compatibility.alternatives(); + if (StringUtils.isBlank(replacedApis)) { + message = String.format(MESSAGE_NO_REPLACED_API, switchName); + } else { + message = String.format(MESSAGE_REPLACED_API, replacedApis, switchName); + } + Result result = Result.failure(ErrorCode.API_DEPRECATED.getCode(), message, null); + response.sendError(HttpServletResponse.SC_GONE, JacksonUtils.toJson(result)); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilitySpringConfig.java b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilitySpringConfig.java new file mode 100644 index 00000000000..f556e3b3355 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilitySpringConfig.java @@ -0,0 +1,49 @@ +/* + * 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.controller.compatibility; + +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.web.NacosWebBean; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configuration of Api Compatibility. + * + * @author xiweng.yy + */ +@Configuration +@NacosWebBean +public class ApiCompatibilitySpringConfig { + + @Bean + public ApiCompatibilityFilter apiCompatibilityFilter(ControllerMethodsCache methodsCache) { + return new ApiCompatibilityFilter(methodsCache); + } + + @Bean + public FilterRegistrationBean apiCompatibilityFilterRegistration( + ApiCompatibilityFilter apiCompatibilityFilter) { + FilterRegistrationBean registration = new FilterRegistrationBean<>(); + registration.setFilter(apiCompatibilityFilter); + registration.addUrlPatterns("/v1/*", "/v2/*"); + registration.setName("apiCompatibilityFilter"); + registration.setOrder(5); + return registration; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/Compatibility.java b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/Compatibility.java new file mode 100644 index 00000000000..88f1ce9d7d0 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/controller/compatibility/Compatibility.java @@ -0,0 +1,49 @@ +/* + * 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.controller.compatibility; + +import com.alibaba.nacos.plugin.auth.constant.ApiType; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Nacos old API compatibility annotation. + *

+ * Marked old API will be deprecated in future version, but for some users need time to refactor and move to new API. + * In this situation, change the configuration in {@link ApiCompatibilityConfig} to open the old API usage. + *

+ * + * @author xiweng.yy + */ +@Retention(RetentionPolicy.RUNTIME) +public @interface Compatibility { + + /** + * The type of API. Distinguishing {@link ApiType}. + * + * @return the type of the API + */ + ApiType apiType() default ApiType.OPEN_API; + + /** + * APIs can replace this deprecated API. + * + * @return API list. + */ + String alternatives() default ""; +} 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 a13f0241fbf..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 @@ -17,6 +17,7 @@ package com.alibaba.nacos.core.controller.v2; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.common.Beta; import com.alibaba.nacos.common.model.RestResult; @@ -27,6 +28,7 @@ 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.ApiType; import com.alibaba.nacos.plugin.auth.constant.SignType; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; @@ -47,6 +49,7 @@ @Beta @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/ops") +@Deprecated public class CoreOpsV2Controller { private final ProtocolManager protocolManager; @@ -61,17 +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, alternatives = "POST {contextPath:nacos}/v3/admin/core/ops/raft") public RestResult raftOps(@RequestBody Map commands) { return protocolManager.getCpProtocol().execute(commands); } @@ -82,6 +84,7 @@ public RestResult raftOps(@RequestBody Map commands) { * @return {@link RestResult} */ @GetMapping(value = "/ids") + @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) -> { @@ -101,6 +104,7 @@ public RestResult> ids() { @PutMapping(value = "/log") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) + @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 c4604e1e62d..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 @@ -27,10 +27,12 @@ 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.controller.compatibility.Compatibility; 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.DeleteMapping; @@ -53,6 +55,7 @@ @NacosApi @RestController @RequestMapping(Commons.NACOS_CORE_CONTEXT_V2 + "/cluster") +@Deprecated public class NacosClusterControllerV2 { private final NacosClusterOperationService nacosClusterOperationService; @@ -63,6 +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, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/self") public Result self() { return Result.success(nacosClusterOperationService.self()); } @@ -76,6 +80,7 @@ public Result self() { */ @GetMapping(value = "/node/list") @Secured(action = ActionTypes.READ, resource = "nacos/admin", signType = SignType.CONSOLE) + @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 { @@ -84,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)); @@ -92,6 +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, alternatives = "GET {contextPath:nacos}/v3/admin/core/cluster/node/self/health") public Result selfHealth() { return Result.success(nacosClusterOperationService.selfHealth()); } @@ -107,6 +114,7 @@ public Result selfHealth() { */ @PutMapping(value = "/node/list") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) + @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, @@ -123,6 +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, 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, @@ -140,6 +149,7 @@ public Result updateLookup(LookupUpdateRequest request) throws NacosExc */ @DeleteMapping("/nodes") @Secured(action = ActionTypes.WRITE, resource = "nacos/admin", signType = SignType.CONSOLE) + @Compatibility(apiType = ApiType.ADMIN_API) public RestResult deleteNodes(@RequestParam("addresses") List addresses) throws Exception { return RestResultUtils.failed(405, null, "DELETE /v2/core/cluster/nodes API not allow to use temporarily."); 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/listener/StartingApplicationListener.java b/core/src/main/java/com/alibaba/nacos/core/listener/StartingApplicationListener.java index cd1c21838c5..f0019887c08 100644 --- a/core/src/main/java/com/alibaba/nacos/core/listener/StartingApplicationListener.java +++ b/core/src/main/java/com/alibaba/nacos/core/listener/StartingApplicationListener.java @@ -16,35 +16,15 @@ package com.alibaba.nacos.core.listener; -import com.alibaba.nacos.api.exception.NacosException; -import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; -import com.alibaba.nacos.common.event.ServerConfigChangeEvent; -import com.alibaba.nacos.common.executor.ExecutorFactory; -import com.alibaba.nacos.common.executor.NameThreadFactory; -import com.alibaba.nacos.common.executor.ThreadPoolManager; -import com.alibaba.nacos.common.notify.NotifyCenter; -import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.listener.startup.NacosStartUp; +import com.alibaba.nacos.core.listener.startup.NacosStartUpManager; import com.alibaba.nacos.sys.env.EnvUtil; -import com.alibaba.nacos.sys.file.FileChangeEvent; -import com.alibaba.nacos.sys.file.FileWatcher; -import com.alibaba.nacos.sys.file.WatchFileCenter; -import com.alibaba.nacos.sys.utils.ApplicationUtils; -import com.alibaba.nacos.sys.utils.DiskUtils; -import com.alibaba.nacos.sys.utils.InetUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.boot.env.OriginTrackedMapPropertySource; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; -import java.io.File; -import java.io.IOException; import java.nio.file.Paths; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; /** * init environment config. @@ -56,233 +36,44 @@ public class StartingApplicationListener implements NacosApplicationListener { private static final Logger LOGGER = LoggerFactory.getLogger(StartingApplicationListener.class); - private static final String MODE_PROPERTY_KEY_STAND_MODE = "nacos.mode"; - - private static final String MODE_PROPERTY_KEY_FUNCTION_MODE = "nacos.function.mode"; - - private static final String LOCAL_IP_PROPERTY_KEY = "nacos.local.ip"; - - private static final String NACOS_APPLICATION_CONF = "nacos_application_conf"; - - private static final String NACOS_MODE_STAND_ALONE = "stand alone"; - - private static final String NACOS_MODE_CLUSTER = "cluster"; - - private static final String DEFAULT_FUNCTION_MODE = "All"; - - private static final String DEFAULT_DATABASE = "mysql"; - - /** - * May be removed with the upgrade of springboot version. - */ - public static final String DATASOURCE_PLATFORM_PROPERTY_OLD = "spring.datasource.platform"; - - private static final String DATASOURCE_PLATFORM_PROPERTY = "spring.sql.init.platform"; - - private static final String DERBY_DATABASE = "derby"; - - private static final String DEFAULT_DATASOURCE_PLATFORM = ""; - - private static final String DATASOURCE_MODE_EXTERNAL = "external"; - - private static final String DATASOURCE_MODE_EMBEDDED = "embedded"; - - private static final Map SOURCES = new ConcurrentHashMap<>(); - - private ScheduledExecutorService scheduledExecutorService; - - private volatile boolean starting; - @Override public void starting() { - starting = true; + NacosStartUpManager.getCurrentStartUp().starting(); } @Override public void environmentPrepared(ConfigurableEnvironment environment) { - makeWorkDir(); - - injectEnvironment(environment); - - loadPreProperties(environment); - - initSystemProperty(); + NacosStartUp currentStartUp = NacosStartUpManager.getCurrentStartUp(); + currentStartUp.makeWorkDir(); + currentStartUp.injectEnvironment(environment); + currentStartUp.loadPreProperties(environment); + currentStartUp.initSystemProperty(); } @Override public void contextPrepared(ConfigurableApplicationContext context) { - logClusterConf(); - - logStarting(); + NacosStartUpManager.getCurrentStartUp().logStartingInfo(LOGGER); } @Override public void contextLoaded(ConfigurableApplicationContext context) { - EnvUtil.customEnvironment(); + NacosStartUpManager.getCurrentStartUp().customEnvironment(); } @Override public void started(ConfigurableApplicationContext context) { - starting = false; - - closeExecutor(); - - ApplicationUtils.setStarted(true); - judgeStorageMode(context.getEnvironment()); + NacosStartUp currentStartUp = NacosStartUpManager.getCurrentStartUp(); + currentStartUp.started(); + currentStartUp.logStarted(LOGGER); } @Override public void failed(ConfigurableApplicationContext context, Throwable exception) { - starting = false; - - makeWorkDir(); - + for (NacosStartUp each : NacosStartUpManager.getReverseStartedList()) { + each.failed(exception, context); + } LOGGER.error("Startup errors : ", exception); - ThreadPoolManager.shutdown(); - WatchFileCenter.shutdown(); - NotifyCenter.shutdown(); - - closeExecutor(); - - context.close(); - LOGGER.error("Nacos failed to start, please see {} for more details.", Paths.get(EnvUtil.getNacosHome(), "logs/nacos.log")); } - - private void injectEnvironment(ConfigurableEnvironment environment) { - EnvUtil.setEnvironment(environment); - } - - private void loadPreProperties(ConfigurableEnvironment environment) { - try { - SOURCES.putAll(EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource())); - environment.getPropertySources() - .addLast(new OriginTrackedMapPropertySource(NACOS_APPLICATION_CONF, SOURCES)); - registerWatcher(); - } catch (Exception e) { - throw new NacosRuntimeException(NacosException.SERVER_ERROR, e); - } - } - - private void registerWatcher() throws NacosException { - - WatchFileCenter.registerWatcher(EnvUtil.getConfPath(), new FileWatcher() { - @Override - public void onChange(FileChangeEvent event) { - try { - Map tmp = EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource()); - SOURCES.putAll(tmp); - NotifyCenter.publishEvent(ServerConfigChangeEvent.newEvent()); - } catch (IOException ignore) { - LOGGER.warn("Failed to monitor file ", ignore); - } - } - - @Override - public boolean interest(String context) { - return StringUtils.contains(context, "application.properties"); - } - }); - - } - - private void initSystemProperty() { - if (EnvUtil.getStandaloneMode()) { - System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_STAND_ALONE); - } else { - System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_CLUSTER); - } - if (EnvUtil.getFunctionMode() == null) { - System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, DEFAULT_FUNCTION_MODE); - } else if (EnvUtil.FUNCTION_MODE_CONFIG.equals(EnvUtil.getFunctionMode())) { - System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_CONFIG); - } else if (EnvUtil.FUNCTION_MODE_NAMING.equals(EnvUtil.getFunctionMode())) { - System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_NAMING); - } - - System.setProperty(LOCAL_IP_PROPERTY_KEY, InetUtils.getSelfIP()); - } - - private void logClusterConf() { - if (!EnvUtil.getStandaloneMode()) { - try { - List clusterConf = EnvUtil.readClusterConf(); - LOGGER.info("The server IP list of Nacos is {}", clusterConf); - } catch (IOException e) { - LOGGER.error("read cluster conf fail", e); - } - } - } - - private void closeExecutor() { - if (scheduledExecutorService != null) { - scheduledExecutorService.shutdownNow(); - } - } - - private void makeWorkDir() { - String[] dirNames = new String[] {"logs", "conf", "data"}; - for (String dirName : dirNames) { - LOGGER.info("Nacos Log files: {}", Paths.get(EnvUtil.getNacosHome(), dirName)); - try { - DiskUtils.forceMkdir(new File(Paths.get(EnvUtil.getNacosHome(), dirName).toUri())); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - } - - private void logStarting() { - if (!EnvUtil.getStandaloneMode()) { - scheduledExecutorService = ExecutorFactory - .newSingleScheduledExecutorService(new NameThreadFactory("com.alibaba.nacos.core.nacos-starting")); - - scheduledExecutorService.scheduleWithFixedDelay(() -> { - if (starting) { - LOGGER.info("Nacos is starting..."); - } - }, 1, 1, TimeUnit.SECONDS); - } - } - - private void judgeStorageMode(ConfigurableEnvironment env) { - - // External data sources are used by default in cluster mode - String platform = this.getDatasourcePlatform(env); - boolean useExternalStorage = - !DEFAULT_DATASOURCE_PLATFORM.equalsIgnoreCase(platform) && !DERBY_DATABASE.equalsIgnoreCase(platform); - - // must initialize after setUseExternalDb - // This value is true in stand-alone mode and false in cluster mode - // If this value is set to true in cluster mode, nacos's distributed storage engine is turned on - // default value is depend on ${nacos.standalone} - - if (!useExternalStorage) { - boolean embeddedStorage = EnvUtil.getStandaloneMode() || Boolean.getBoolean("embeddedStorage"); - // If the embedded data source storage is not turned on, it is automatically - // upgraded to the external data source storage, as before - if (!embeddedStorage) { - useExternalStorage = true; - } - } - - LOGGER.info("Nacos started successfully in {} mode. use {} storage", - System.getProperty(MODE_PROPERTY_KEY_STAND_MODE), - useExternalStorage ? DATASOURCE_MODE_EXTERNAL : DATASOURCE_MODE_EMBEDDED); - } - - /** - * get datasource platform. - * - * @param env ConfigurableEnvironment. - * @return - */ - private String getDatasourcePlatform(ConfigurableEnvironment env) { - String platform = env.getProperty(DATASOURCE_PLATFORM_PROPERTY, DEFAULT_DATASOURCE_PLATFORM); - if (StringUtils.isBlank(platform)) { - platform = env.getProperty(DATASOURCE_PLATFORM_PROPERTY_OLD, DEFAULT_DATASOURCE_PLATFORM); - } - return platform; - } } diff --git a/core/src/main/java/com/alibaba/nacos/core/listener/startup/AbstractNacosStartUp.java b/core/src/main/java/com/alibaba/nacos/core/listener/startup/AbstractNacosStartUp.java new file mode 100644 index 00000000000..b1f237540d0 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/listener/startup/AbstractNacosStartUp.java @@ -0,0 +1,95 @@ +/* + * 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.listener.startup; + +import com.alibaba.nacos.common.executor.ExecutorFactory; +import com.alibaba.nacos.common.executor.NameThreadFactory; +import org.slf4j.Logger; +import org.springframework.context.ConfigurableApplicationContext; + +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/** + * Abstract Nacos start up. + * + * @author xiweng.yy + */ +public abstract class AbstractNacosStartUp implements NacosStartUp { + + private final String phase; + + private volatile ScheduledExecutorService startLoggingScheduledExecutor; + + private volatile boolean starting; + + private volatile long startTimestamp; + + protected AbstractNacosStartUp(String phase) { + this.phase = phase; + } + + @Override + public String startUpPhase() { + return phase; + } + + @Override + public void starting() { + starting = true; + startTimestamp = System.currentTimeMillis(); + this.startLoggingScheduledExecutor = ExecutorFactory.newSingleScheduledExecutorService( + new NameThreadFactory(String.format("com.alibaba.nacos.%s.nacos-starting", phase))); + } + + @Override + public void logStartingInfo(Logger logger) { + startLoggingScheduledExecutor.scheduleWithFixedDelay(() -> { + if (starting) { + logger.info(String.format("%s is starting...", getPhaseNameInStartingInfo())); + } + }, 1, 1, TimeUnit.SECONDS); + } + + @Override + public void started() { + starting = false; + closeExecutor(); + } + + @Override + public void failed(Throwable exception, ConfigurableApplicationContext context) { + starting = false; + closeExecutor(); + context.close(); + } + + protected long getStartTimestamp() { + return startTimestamp; + } + + /** + * Get phase name in starting info. + * + * @return phase name + */ + protected abstract String getPhaseNameInStartingInfo(); + + private void closeExecutor() { + startLoggingScheduledExecutor.shutdownNow(); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosCoreStartUp.java b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosCoreStartUp.java new file mode 100644 index 00000000000..a36eeeb820b --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosCoreStartUp.java @@ -0,0 +1,231 @@ +/* + * 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.listener.startup; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.exception.runtime.NacosRuntimeException; +import com.alibaba.nacos.common.event.ServerConfigChangeEvent; +import com.alibaba.nacos.common.executor.ThreadPoolManager; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.exception.ErrorCode; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.file.FileChangeEvent; +import com.alibaba.nacos.sys.file.FileWatcher; +import com.alibaba.nacos.sys.file.WatchFileCenter; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import com.alibaba.nacos.sys.utils.DiskUtils; +import com.alibaba.nacos.sys.utils.InetUtils; +import org.slf4j.Logger; +import org.springframework.boot.env.OriginTrackedMapPropertySource; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Nacos Server Core start up phase. + * + * @author xiweng.yy + */ +public class NacosCoreStartUp extends AbstractNacosStartUp { + + private static final String MODE_PROPERTY_KEY_STAND_MODE = "nacos.mode"; + + private static final String MODE_PROPERTY_KEY_FUNCTION_MODE = "nacos.function.mode"; + + private static final String LOCAL_IP_PROPERTY_KEY = "nacos.local.ip"; + + private static final String NACOS_APPLICATION_CONF = "nacos_application_conf"; + + private static final String NACOS_MODE_STAND_ALONE = "stand alone"; + + private static final String NACOS_MODE_CLUSTER = "cluster"; + + private static final String DEFAULT_FUNCTION_MODE = "All"; + + private static final String DATASOURCE_PLATFORM_PROPERTY = "spring.sql.init.platform"; + + private static final String DERBY_DATABASE = "derby"; + + private static final String DEFAULT_DATASOURCE_PLATFORM = ""; + + private static final String DATASOURCE_MODE_EXTERNAL = "external"; + + private static final String DATASOURCE_MODE_EMBEDDED = "embedded"; + + private static final Map SOURCES = new ConcurrentHashMap<>(); + + public NacosCoreStartUp() { + super(NacosStartUp.CORE_START_UP_PHASE); + } + + @Override + public String[] makeWorkDir() { + String[] dirNames = new String[] {"logs", "conf", "data"}; + List result = new ArrayList<>(dirNames.length); + for (String dirName : dirNames) { + try { + Path path = Paths.get(EnvUtil.getNacosHome(), dirName); + DiskUtils.forceMkdir(new File(path.toUri())); + result.add(path.toString()); + } catch (Exception e) { + throw new NacosRuntimeException(ErrorCode.IOMakeDirError.getCode(), e); + } + } + return result.toArray(new String[0]); + } + + @Override + public void injectEnvironment(ConfigurableEnvironment environment) { + EnvUtil.setEnvironment(environment); + } + + @Override + public void loadPreProperties(ConfigurableEnvironment environment) { + try { + SOURCES.putAll(EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource())); + environment.getPropertySources() + .addLast(new OriginTrackedMapPropertySource(NACOS_APPLICATION_CONF, SOURCES)); + registerWatcher(); + } catch (Exception e) { + throw new NacosRuntimeException(NacosException.SERVER_ERROR, e); + } + } + + @Override + public void initSystemProperty() { + if (EnvUtil.getStandaloneMode()) { + System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_STAND_ALONE); + } else { + System.setProperty(MODE_PROPERTY_KEY_STAND_MODE, NACOS_MODE_CLUSTER); + } + if (EnvUtil.getFunctionMode() == null) { + System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, DEFAULT_FUNCTION_MODE); + } else if (EnvUtil.FUNCTION_MODE_CONFIG.equals(EnvUtil.getFunctionMode())) { + System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_CONFIG); + } else if (EnvUtil.FUNCTION_MODE_NAMING.equals(EnvUtil.getFunctionMode())) { + System.setProperty(MODE_PROPERTY_KEY_FUNCTION_MODE, EnvUtil.FUNCTION_MODE_NAMING); + } + + System.setProperty(LOCAL_IP_PROPERTY_KEY, InetUtils.getSelfIP()); + } + + @Override + public void logStartingInfo(Logger logger) { + logClusterConf(logger); + super.logStartingInfo(logger); + } + + @Override + public void customEnvironment() { + EnvUtil.customEnvironment(); + } + + @Override + public void started() { + super.started(); + ApplicationUtils.setStarted(true); + } + + @Override + protected String getPhaseNameInStartingInfo() { + return "Nacos Server"; + } + + @Override + public void logStarted(Logger logger) { + long endTimestamp = System.currentTimeMillis(); + long startupCost = endTimestamp - getStartTimestamp(); + boolean useExternalStorage = judgeStorageMode(EnvUtil.getEnvironment()); + logger.info("Nacos started successfully in {} mode with {} storage in {} ms", + System.getProperty(MODE_PROPERTY_KEY_STAND_MODE), + useExternalStorage ? DATASOURCE_MODE_EXTERNAL : DATASOURCE_MODE_EMBEDDED, startupCost); + } + + @Override + public void failed(Throwable exception, ConfigurableApplicationContext context) { + super.failed(exception, context); + ThreadPoolManager.shutdown(); + WatchFileCenter.shutdown(); + NotifyCenter.shutdown(); + } + + private void registerWatcher() throws NacosException { + WatchFileCenter.registerWatcher(EnvUtil.getConfPath(), new FileWatcher() { + @Override + public void onChange(FileChangeEvent event) { + try { + Map tmp = EnvUtil.loadProperties(EnvUtil.getApplicationConfFileResource()); + SOURCES.putAll(tmp); + NotifyCenter.publishEvent(ServerConfigChangeEvent.newEvent()); + } catch (IOException ignore) { + } + } + + @Override + public boolean interest(String context) { + return StringUtils.contains(context, "application.properties"); + } + }); + } + + private void logClusterConf(Logger logger) { + if (!EnvUtil.getStandaloneMode()) { + try { + List clusterConf = EnvUtil.readClusterConf(); + logger.info("The server IP list of Nacos is {}", clusterConf); + } catch (IOException e) { + logger.error("read cluster conf fail", e); + } + } + } + + private boolean judgeStorageMode(ConfigurableEnvironment env) { + + // External data sources are used by default in cluster mode + String platform = this.getDatasourcePlatform(env); + boolean useExternalStorage = + !DEFAULT_DATASOURCE_PLATFORM.equalsIgnoreCase(platform) && !DERBY_DATABASE.equalsIgnoreCase(platform); + + // must initialize after setUseExternalDB + // This value is true in stand-alone mode and false in cluster mode + // If this value is set to true in cluster mode, nacos's distributed storage engine is turned on + // default value is depend on ${nacos.standalone} + + if (!useExternalStorage) { + boolean embeddedStorage = EnvUtil.getStandaloneMode() || Boolean.getBoolean("embeddedStorage"); + // If the embedded data source storage is not turned on, it is automatically + // upgraded to the external data source storage, as before + if (!embeddedStorage) { + useExternalStorage = true; + } + } + return useExternalStorage; + } + + private String getDatasourcePlatform(ConfigurableEnvironment env) { + return env.getProperty(DATASOURCE_PLATFORM_PROPERTY, DEFAULT_DATASOURCE_PLATFORM); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosStartUp.java b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosStartUp.java new file mode 100644 index 00000000000..39cecbb921c --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosStartUp.java @@ -0,0 +1,110 @@ +/* + * 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.listener.startup; + +import org.slf4j.Logger; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.env.ConfigurableEnvironment; + +/** + * Nacos start up phases. + * + * @author xiweng.yy + */ +public interface NacosStartUp { + + String CORE_START_UP_PHASE = "core"; + + String WEB_START_UP_PHASE = "web"; + + String CONSOLE_START_UP_PHASE = "console"; + + /** + * Current Nacos Server start up phase. + * + * @return {@link #CORE_START_UP_PHASE} or {@link #WEB_START_UP_PHASE} or {@link #CONSOLE_START_UP_PHASE}. + */ + String startUpPhase(); + + /** + * Current Nacos Server start to stand up. + */ + void starting(); + + /** + * Current Nacos Server start do make work dir if necessary. + * @return created work dirs + */ + default String[] makeWorkDir() { + return new String[0]; + } + + /** + * Inject Environment to Current Nacos Server if necessary. + * + * @param environment environment + */ + default void injectEnvironment(ConfigurableEnvironment environment) { + } + + /** + * Load Pre Properties from Environment if necessary. + * + * @param environment environment + */ + default void loadPreProperties(ConfigurableEnvironment environment) { + } + + /** + * Init System Property to current Nacos Server JVM if necessary. + */ + default void initSystemProperty() { + } + + /** + * Log Starting Info for current Nacos Server. + * + * @param logger logger for print info + */ + void logStartingInfo(Logger logger); + + /** + * If current Nacos Server need use custom environment plugin, implement this method. + */ + default void customEnvironment() { + } + + /** + * Current Nacos Server finished to stand up. + */ + void started(); + + /** + * Log started info for current Nacos Server. + * + * @param logger logger for print info + */ + void logStarted(Logger logger); + + /** + * Current Nacos Server start up failed, close relative resources and do hints according to exception. + * + * @param exception exception during start up + * @param context current application context + */ + void failed(Throwable exception, ConfigurableApplicationContext context); +} diff --git a/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosStartUpManager.java b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosStartUpManager.java new file mode 100644 index 00000000000..886917d5384 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosStartUpManager.java @@ -0,0 +1,90 @@ +/* + * 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.listener.startup; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.common.utils.StringUtils; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Nacos start up phase manager. + * + * @author xiweng.yy + */ +public class NacosStartUpManager { + + private static final NacosStartUpManager INSTANCE = new NacosStartUpManager(); + + private String currentStartUpPhase; + + private final Map startUpMap; + + private final List startedList; + + private NacosStartUpManager() { + startUpMap = new HashMap<>(); + for (NacosStartUp each : NacosServiceLoader.load(NacosStartUp.class)) { + startUpMap.put(each.startUpPhase(), each); + } + startedList = new ArrayList<>(startUpMap.size()); + } + + private NacosStartUp getStartUp(String phase) { + return startUpMap.get(phase); + } + + /** + * Mark step into new nacos start up phase. + * @param phase phase name. + * @throws IllegalArgumentException when phase is unknown. + */ + public static void start(String phase) { + NacosStartUp startUp = INSTANCE.getStartUp(phase); + if (null == startUp) { + throw new IllegalArgumentException("Unknown nacos start up phase " + phase); + } + INSTANCE.currentStartUpPhase = phase; + INSTANCE.startedList.add(startUp); + } + + /** + * Get current nacos start up phase. + * @return current start up phase. + * @throws IllegalStateException when nacos not start up. + */ + public static NacosStartUp getCurrentStartUp() { + if (StringUtils.isBlank(INSTANCE.currentStartUpPhase)) { + throw new IllegalStateException("Nacos don't start up."); + } + return INSTANCE.getStartUp(INSTANCE.currentStartUpPhase); + } + + /** + * Get reversed nacos start up which has been started list. + * @return reversed nacos start up + */ + public static List getReverseStartedList() { + List result = new ArrayList<>(INSTANCE.startedList); + Collections.reverse(result); + return result; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosWebStartUp.java b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosWebStartUp.java new file mode 100644 index 00000000000..ec2d2ada34a --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/listener/startup/NacosWebStartUp.java @@ -0,0 +1,43 @@ +/* + * 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.listener.startup; + +import org.slf4j.Logger; + +/** + * Nacos Server Web API start up phase. + * + * @author xiweng.yy + */ +public class NacosWebStartUp extends AbstractNacosStartUp { + + public NacosWebStartUp() { + super(NacosStartUp.WEB_START_UP_PHASE); + } + + @Override + protected String getPhaseNameInStartingInfo() { + return "Nacos Server API"; + } + + @Override + public void logStarted(Logger logger) { + long endTimestamp = System.currentTimeMillis(); + long startupCost = endTimestamp - getStartTimestamp(); + logger.info("Nacos Server API started successfully in {} ms", startupCost); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/model/form/AggregationForm.java b/core/src/main/java/com/alibaba/nacos/core/model/form/AggregationForm.java new file mode 100644 index 00000000000..710041766ab --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/model/form/AggregationForm.java @@ -0,0 +1,43 @@ +/* + * 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; + +import com.alibaba.nacos.api.exception.api.NacosApiException; + +/** + * Nacos HTTP Aggregatable API Form. + * + * @author xiweng.yy + */ +public class AggregationForm implements NacosForm { + + private static final long serialVersionUID = 3585575371677025046L; + + private boolean aggregation = Boolean.TRUE; + + @Override + public void validate() throws NacosApiException { + } + + public boolean isAggregation() { + return aggregation; + } + + public void setAggregation(boolean aggregation) { + this.aggregation = aggregation; + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/model/form/NacosForm.java b/core/src/main/java/com/alibaba/nacos/core/model/form/NacosForm.java new file mode 100644 index 00000000000..50c3d02174c --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/model/form/NacosForm.java @@ -0,0 +1,36 @@ +/* + * 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; + +import com.alibaba.nacos.api.exception.api.NacosApiException; + +import java.io.Serializable; + +/** + * Nacos HTTP Form API Object. + * + * @author xiweng.yy + */ +public interface NacosForm extends Serializable { + + /** + * check form parameters while valid. + * + * @throws NacosApiException when form parameters is invalid. + */ + void validate() throws NacosApiException; +} diff --git a/core/src/main/java/com/alibaba/nacos/core/model/form/PageForm.java b/core/src/main/java/com/alibaba/nacos/core/model/form/PageForm.java new file mode 100644 index 00000000000..59a6b7c14a9 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/model/form/PageForm.java @@ -0,0 +1,63 @@ +/* + * 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; + +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import org.springframework.http.HttpStatus; + +/** + * Nacos HTTP page API form. + * + * @author xiweng.yy + */ +public class PageForm implements NacosForm { + + private static final long serialVersionUID = -8912131925234465033L; + + private int pageNo = 1; + + private int pageSize = 100; + + @Override + public void validate() throws NacosApiException { + if (pageNo < 1) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_VALIDATE_ERROR, + String.format("Required parameter 'pageNo' should be positive integer, current is %d", pageNo)); + } + if (pageSize < 1) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_VALIDATE_ERROR, + String.format("Required parameter 'pageSize' should be positive integer, current is %d", pageSize)); + } + } + + public int getPageNo() { + return pageNo; + } + + public void setPageNo(int pageNo) { + this.pageNo = pageNo; + } + + public int getPageSize() { + return pageSize; + } + + public void setPageSize(int pageSize) { + this.pageSize = pageSize; + } +} 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/monitor/NacosMeterRegistryCenter.java b/core/src/main/java/com/alibaba/nacos/core/monitor/NacosMeterRegistryCenter.java index 889630c4a3c..7618d73a96b 100644 --- a/core/src/main/java/com/alibaba/nacos/core/monitor/NacosMeterRegistryCenter.java +++ b/core/src/main/java/com/alibaba/nacos/core/monitor/NacosMeterRegistryCenter.java @@ -49,7 +49,9 @@ public final class NacosMeterRegistryCenter { // control plugin registeres. public static final String CONTROL_DENIED_REGISTRY = "CONTROL_DENIED_REGISTRY"; - + + public static final String LOCK_STABLE_REGISTRY = "LOCK_STABLE_REGISTRY"; + private static final ConcurrentHashMap METER_REGISTRIES = new ConcurrentHashMap<>(); private static CompositeMeterRegistry METER_REGISTRY = null; @@ -61,7 +63,7 @@ public final class NacosMeterRegistryCenter { Loggers.CORE.warn("Metrics init failed :", t); } registry(CORE_STABLE_REGISTRY, CONFIG_STABLE_REGISTRY, NAMING_STABLE_REGISTRY, TOPN_CONFIG_CHANGE_REGISTRY, - TOPN_SERVICE_CHANGE_REGISTRY, CONTROL_DENIED_REGISTRY); + TOPN_SERVICE_CHANGE_REGISTRY, CONTROL_DENIED_REGISTRY, LOCK_STABLE_REGISTRY); } diff --git a/core/src/main/java/com/alibaba/nacos/core/namespace/model/form/CreateNamespaceForm.java b/core/src/main/java/com/alibaba/nacos/core/namespace/model/form/CreateNamespaceForm.java new file mode 100644 index 00000000000..dd2299cb56a --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/namespace/model/form/CreateNamespaceForm.java @@ -0,0 +1,57 @@ +/* + * 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.namespace.model.form; + +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.common.utils.StringUtils; +import org.springframework.http.HttpStatus; + +import java.util.UUID; + +/** + * Extend namespace form to adapt create nacos namespace API. + * + * @author xiweng.yy + */ +public class CreateNamespaceForm extends NamespaceForm { + + private static final long serialVersionUID = 1069121416033814056L; + + private String customNamespaceId; + + public String getCustomNamespaceId() { + return customNamespaceId; + } + + public void setCustomNamespaceId(String customNamespaceId) { + this.customNamespaceId = customNamespaceId; + } + + @Override + public void validate() throws NacosApiException { + if (null == super.getNamespaceName()) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "required parameter 'namespaceName' is missing"); + } + if (StringUtils.isBlank(customNamespaceId)) { + customNamespaceId = UUID.randomUUID().toString(); + } else { + customNamespaceId = customNamespaceId.trim(); + } + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/namespace/model/form/NamespaceForm.java b/core/src/main/java/com/alibaba/nacos/core/namespace/model/form/NamespaceForm.java index 5e5af373778..5e718132d2f 100644 --- a/core/src/main/java/com/alibaba/nacos/core/namespace/model/form/NamespaceForm.java +++ b/core/src/main/java/com/alibaba/nacos/core/namespace/model/form/NamespaceForm.java @@ -16,20 +16,18 @@ package com.alibaba.nacos.core.namespace.model.form; -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.core.model.form.NacosForm; import org.springframework.http.HttpStatus; -import java.io.Serializable; -import java.util.Objects; - /** - * NamespaceForm. + * Nacos HTTP namespace API basic form. + * + * @author xiweng.yy * @author dongyafei - * @date 2022/8/16 */ -public class NamespaceForm implements Serializable { +public class NamespaceForm implements NacosForm { private static final long serialVersionUID = -1078976569495343487L; @@ -73,39 +71,14 @@ public void setNamespaceDesc(String namespaceDesc) { } @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - NamespaceForm that = (NamespaceForm) o; - return Objects.equals(namespaceId, that.namespaceId) && Objects.equals(namespaceName, that.namespaceName) - && Objects.equals(namespaceDesc, that.namespaceDesc); - } - - @Override - public int hashCode() { - return Objects.hash(namespaceId, namespaceName, namespaceDesc); - } - - @Override - public String toString() { - return "NamespaceVo{" + "namespaceId='" + namespaceId + '\'' + ", namespaceName='" + namespaceName + '\'' - + ", namespaceDesc='" + namespaceDesc + '\'' + '}'; - } - - /** - * check required param. - * @throws NacosException NacosException - */ - public void validate() throws NacosException { + public void validate() throws NacosApiException { if (null == namespaceId) { - throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, "required parameter 'namespaceId' is missing"); + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "required parameter 'namespaceId' is missing"); } if (null == namespaceName) { - throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, "required parameter 'namespaceName' is missing"); + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "required parameter 'namespaceName' is missing"); } } } diff --git a/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java b/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java index efde5f6d189..2f06f0c92f4 100644 --- a/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java +++ b/core/src/main/java/com/alibaba/nacos/core/paramcheck/CheckConfiguration.java @@ -17,6 +17,7 @@ package com.alibaba.nacos.core.paramcheck; import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.web.NacosWebBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -28,6 +29,7 @@ * @date 2023/11/7 17:52 */ @Configuration +@NacosWebBean public class CheckConfiguration { @Bean diff --git a/core/src/main/java/com/alibaba/nacos/core/remote/core/ServerLoaderInfoRequestHandler.java b/core/src/main/java/com/alibaba/nacos/core/remote/core/ServerLoaderInfoRequestHandler.java index 0cf208e0384..2c1badc8caa 100644 --- a/core/src/main/java/com/alibaba/nacos/core/remote/core/ServerLoaderInfoRequestHandler.java +++ b/core/src/main/java/com/alibaba/nacos/core/remote/core/ServerLoaderInfoRequestHandler.java @@ -21,9 +21,12 @@ import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.api.remote.request.ServerLoaderInfoRequest; import com.alibaba.nacos.api.remote.response.ServerLoaderInfoResponse; +import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.core.remote.ConnectionManager; import com.alibaba.nacos.core.remote.RequestHandler; import com.alibaba.nacos.core.remote.grpc.InvokeSource; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.plugin.auth.constant.SignType; import com.alibaba.nacos.sys.env.EnvUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -45,6 +48,7 @@ public class ServerLoaderInfoRequestHandler extends RequestHandler getNamespaceList() { // TODO 获取用kp List tenantInfos = namespacePersistService.findTenantByKp(DEFAULT_KP); - Namespace namespace0 = new Namespace(NamespaceUtil.getNamespaceDefaultId(), DEFAULT_NAMESPACE, DEFAULT_QUOTA, 0, - NamespaceTypeEnum.GLOBAL.getType()); + Namespace namespace0 = new Namespace(NamespaceUtil.getNamespaceDefaultId(), DEFAULT_NAMESPACE_SHOW_NAME, + DEFAULT_NAMESPACE_DESCRIPTION, DEFAULT_QUOTA, 0, NamespaceTypeEnum.GLOBAL.getType()); NamespaceDetailInjectorHolder.getInstance().injectDetail(namespace0); List namespaceList = new ArrayList<>(); namespaceList.add(namespace0); @@ -116,9 +114,13 @@ public Namespace getNamespace(String namespaceId) throws NacosException { public Boolean createNamespace(String namespaceId, String namespaceName, String namespaceDesc) throws NacosException { // TODO 获取用kp + if (NamespaceUtil.isDefaultNamespaceId(namespaceId)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.NAMESPACE_ALREADY_EXIST, + "namespaceId [" + namespaceId + "] is default namespace id and already exist."); + } if (namespacePersistService.tenantInfoCountByTenantId(namespaceId) > 0) { - throw new NacosApiException(HttpStatus.INTERNAL_SERVER_ERROR.value(), ErrorCode.NAMESPACE_ALREADY_EXIST, - "namespaceId [" + namespaceId + "] already exist"); + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.NAMESPACE_ALREADY_EXIST, + "namespaceId [" + namespaceId + "] already exist."); } namespacePersistService 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/main/java/com/alibaba/nacos/core/web/NacosCoreWebConfiguration.java b/core/src/main/java/com/alibaba/nacos/core/web/NacosCoreWebConfiguration.java new file mode 100644 index 00000000000..8f3b663f264 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/web/NacosCoreWebConfiguration.java @@ -0,0 +1,43 @@ +/* + * 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.web; + +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import org.springframework.context.annotation.Configuration; + +import javax.annotation.PostConstruct; + +/** + * Nacos core web configuration. + * + * @author xiweng.yy + */ +@Configuration +@NacosWebBean +public class NacosCoreWebConfiguration { + + private final ControllerMethodsCache methodsCache; + + public NacosCoreWebConfiguration(ControllerMethodsCache methodsCache) { + this.methodsCache = methodsCache; + } + + @PostConstruct + public void init() { + methodsCache.initClassMethod("com.alibaba.nacos.core.controller"); + } +} diff --git a/core/src/main/java/com/alibaba/nacos/core/web/NacosWebBean.java b/core/src/main/java/com/alibaba/nacos/core/web/NacosWebBean.java new file mode 100644 index 00000000000..9677cccee69 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/web/NacosWebBean.java @@ -0,0 +1,33 @@ +/* + * 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.web; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Annotation to mark custom nacos beans which force depend web container. + * + * @author xiweng.yy + */ +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface NacosWebBean { + +} diff --git a/core/src/main/java/com/alibaba/nacos/core/web/NacosWebServerListener.java b/core/src/main/java/com/alibaba/nacos/core/web/NacosWebServerListener.java new file mode 100644 index 00000000000..0c7a6413a60 --- /dev/null +++ b/core/src/main/java/com/alibaba/nacos/core/web/NacosWebServerListener.java @@ -0,0 +1,55 @@ +/* + * 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.web; + +import com.alibaba.nacos.core.cluster.ServerMemberManager; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.springframework.boot.web.context.WebServerInitializedEvent; +import org.springframework.context.ApplicationListener; +import org.springframework.stereotype.Component; + +import javax.servlet.ServletContext; + +/** + * Nacos web server listener which listen web container ready and listen the context path changed. + * + * @author xiweng.yy + */ +@Component +@NacosWebBean +public class NacosWebServerListener implements ApplicationListener { + + private static final String SPRING_MANAGEMENT_CONTEXT_NAMESPACE = "management"; + + private final ServerMemberManager serverMemberManager; + + public NacosWebServerListener(ServerMemberManager serverMemberManager, ServletContext servletContext) { + this.serverMemberManager = serverMemberManager; + EnvUtil.setContextPath(servletContext.getContextPath()); + } + + @Override + public void onApplicationEvent(WebServerInitializedEvent event) { + String serverNamespace = event.getApplicationContext().getServerNamespace(); + if (SPRING_MANAGEMENT_CONTEXT_NAMESPACE.equals(serverNamespace)) { + // ignore + // fix#issue https://github.com/alibaba/nacos/issues/7230 + return; + } + serverMemberManager.setSelfReady(event.getWebServer().getPort()); + } +} diff --git a/core/src/main/resources/META-INF/logback/nacos.xml b/core/src/main/resources/META-INF/logback/nacos.xml index 3c08cb5122f..40c5a413a68 100644 --- a/core/src/main/resources/META-INF/logback/nacos.xml +++ b/core/src/main/resources/META-INF/logback/nacos.xml @@ -272,7 +272,6 @@ - diff --git a/core/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.startup.NacosStartUp b/core/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.startup.NacosStartUp new file mode 100644 index 00000000000..7dff9b94d64 --- /dev/null +++ b/core/src/main/resources/META-INF/services/com.alibaba.nacos.core.listener.startup.NacosStartUp @@ -0,0 +1,18 @@ +# +# 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. +# + +com.alibaba.nacos.core.listener.startup.NacosCoreStartUp +com.alibaba.nacos.core.listener.startup.NacosWebStartUp \ No newline at end of file diff --git a/core/src/main/resources/core-banner.txt b/core/src/main/resources/core-banner.txt new file mode 100644 index 00000000000..5b32f7c852e --- /dev/null +++ b/core/src/main/resources/core-banner.txt @@ -0,0 +1,15 @@ + + ,--. + ,--.'| + ,--,: : | Nacos Server ${application.version} +,`--.'`| ' : ,---. Running in ${nacos.mode} mode, ${nacos.function.mode} function modules +| : : | | ' ,'\ .--.--. +: | \ | : ,--.--. ,---. / / | / / ' Pid: ${pid} +| : ' '; | / \ / \. ; ,. :| : /`./ +' ' ;. ;.--. .-. | / / '' | |: :| : ;_ +| | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io +' : | ; .' ," .--.; |' ; :__| : | `----. \ +| | '`--' / / ,. |' | '.'|\ \ / / /`--' / +' : | ; : .' \ : : `----' '--'. / +; |.' | , .-./\ \ / `--'---' +'---' `--`---' `----' diff --git a/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java b/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java index b5397060a6f..e7f7e9253b8 100644 --- a/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/auth/AuthFilterTest.java @@ -17,30 +17,38 @@ package com.alibaba.nacos.core.auth; +import com.alibaba.nacos.auth.HttpProtocolAuthService; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.common.constant.HttpHeaderConsts; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.core.code.ControllerMethodsCache; import com.alibaba.nacos.core.context.RequestContextHolder; -import com.alibaba.nacos.sys.env.Constants; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Permission; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.exception.AccessException; import org.junit.jupiter.api.AfterEach; +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.web.MockHttpServletRequest; -import org.springframework.mock.web.MockHttpServletResponse; +import org.springframework.test.util.ReflectionTestUtils; -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.ServletRequest; -import jakarta.servlet.ServletResponse; -import jakarta.servlet.http.HttpServletResponse; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; /** * {@link AuthFilter} unit test. @@ -51,61 +59,167 @@ @ExtendWith(MockitoExtension.class) class AuthFilterTest { - @InjectMocks private AuthFilter authFilter; @Mock - private AuthConfigs authConfigs; + private NacosAuthConfig authConfig; @Mock private ControllerMethodsCache methodsCache; + @Mock + FilterChain filterChain; + + @Mock + HttpServletRequest request; + + @Mock + HttpServletResponse response; + + @BeforeEach + void setUp() { + authFilter = new AuthFilter(authConfig, methodsCache); + } + @AfterEach void tearDown() { RequestContextHolder.removeContext(); } @Test - void testDoFilter() { - try { - FilterChain filterChain = new MockFilterChain(); - Mockito.when(authConfigs.isAuthEnabled()).thenReturn(true); - MockHttpServletRequest request = new MockHttpServletRequest(); - HttpServletResponse response = new MockHttpServletResponse(); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(authConfigs.isEnableUserAgentAuthWhite()).thenReturn(true); - request.addHeader(HttpHeaderConsts.USER_AGENT_HEADER, Constants.NACOS_SERVER_HEADER); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(authConfigs.isEnableUserAgentAuthWhite()).thenReturn(false); - Mockito.when(authConfigs.getServerIdentityKey()).thenReturn("1"); - Mockito.when(authConfigs.getServerIdentityValue()).thenReturn("2"); - request.addHeader("1", "2"); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(authConfigs.getServerIdentityValue()).thenReturn("3"); - authFilter.doFilter(request, response, filterChain); - - Mockito.when(methodsCache.getMethod(Mockito.any())).thenReturn(filterChain.getClass().getMethod("testSecured")); - authFilter.doFilter(request, response, filterChain); - - } catch (Exception e) { - e.printStackTrace(); - fail(e.getMessage()); - } + void testDoFilterDisabledAuth() throws ServletException, IOException { + when(authConfig.isAuthEnabled()).thenReturn(false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithoutServerIdentity() throws ServletException, IOException, NoSuchMethodException { + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithoutServerIdentity")); + when(authConfig.isAuthEnabled()).thenReturn(true); + authFilter.doFilter(request, response, filterChain); + verify(filterChain, never()).doFilter(request, response); + verify(response).sendError(403, + "Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`"); + } + + @Test + @Secured + void testDoFilterWithServerIdentity() throws ServletException, IOException, NoSuchMethodException { + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithServerIdentity")); + when(authConfig.isAuthEnabled()).thenReturn(true); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + when(request.getHeader("1")).thenReturn("2"); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithoutMethod() throws ServletException, IOException { + when(authConfig.isAuthEnabled()).thenReturn(true); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + void testDoFilterWithoutSecured() throws ServletException, IOException, NoSuchMethodException { + when(authConfig.isAuthEnabled()).thenReturn(true); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithoutSecured")); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithNoNeedAuthSecured() throws NoSuchMethodException, ServletException, IOException { + when(authConfig.isAuthEnabled()).thenReturn(true); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNoNeedAuthSecured")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithNeedAuthSecuredSuccess() + throws NoSuchMethodException, ServletException, IOException, AccessException { + when(authConfig.isAuthEnabled()).thenReturn(true); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNeedAuthSecuredSuccess")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(true); + when(protocolAuthService.validateAuthority(any(IdentityContext.class), any(Permission.class))).thenReturn(true); + authFilter.doFilter(request, response, filterChain); + verify(filterChain).doFilter(request, response); + verify(response, never()).sendError(anyInt(), anyString()); + } + + @Test + @Secured + void testDoFilterWithNeedAuthSecuredIdentityFailure() + throws NoSuchMethodException, ServletException, IOException, AccessException { + when(authConfig.isAuthEnabled()).thenReturn(true); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNeedAuthSecuredIdentityFailure")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain, never()).doFilter(request, response); + verify(response).sendError(eq(403), anyString()); + } + + @Test + @Secured + void testDoFilterWithNeedAuthSecuredAuthorityFailure() + throws NoSuchMethodException, ServletException, IOException, AccessException { + when(authConfig.isAuthEnabled()).thenReturn(true); + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + when(methodsCache.getMethod(request)).thenReturn( + this.getClass().getDeclaredMethod("testDoFilterWithNeedAuthSecuredAuthorityFailure")); + HttpProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(true); + when(protocolAuthService.validateAuthority(any(IdentityContext.class), any(Permission.class))).thenReturn( + false); + authFilter.doFilter(request, response, filterChain); + verify(filterChain, never()).doFilter(request, response); + verify(response).sendError(eq(403), anyString()); } - class MockFilterChain implements FilterChain { - - @Override - public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException, ServletException { - System.out.println("filter chain executed"); - } - - @Secured(resource = "xx") - public void testSecured() { - - } + private HttpProtocolAuthService injectMockPlugins() { + HttpProtocolAuthService protocolAuthService = new HttpProtocolAuthService(authConfig); + protocolAuthService.initialize(); + HttpProtocolAuthService spyProtocolAuthService = spy(protocolAuthService); + ReflectionTestUtils.setField(authFilter, "protocolAuthService", spyProtocolAuthService); + return spyProtocolAuthService; } } diff --git a/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java b/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java index 61f5963fcb2..2bda81398e8 100644 --- a/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/auth/RemoteRequestAuthFilterTest.java @@ -18,24 +18,37 @@ package com.alibaba.nacos.core.auth; import com.alibaba.nacos.api.exception.NacosException; -import com.alibaba.nacos.api.remote.request.HealthCheckRequest; import com.alibaba.nacos.api.remote.request.Request; import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.api.remote.response.HealthCheckResponse; import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.api.remote.response.ResponseCode; +import com.alibaba.nacos.auth.GrpcProtocolAuthService; import com.alibaba.nacos.auth.annotation.Secured; -import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.auth.config.NacosAuthConfig; import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.remote.HealthCheckRequestHandler; import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.api.Permission; +import com.alibaba.nacos.plugin.auth.api.Resource; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import org.junit.jupiter.api.AfterEach; +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.test.util.ReflectionTestUtils; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.fail; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; /** * {@link RemoteRequestAuthFilter} unit test. @@ -46,11 +59,25 @@ @ExtendWith(MockitoExtension.class) class RemoteRequestAuthFilterTest { - @InjectMocks - private RemoteRequestAuthFilter remoteRequestAuthFilter; + private RemoteRequestAuthFilter authFilter; @Mock - private AuthConfigs authConfigs; + private NacosAuthConfig authConfig; + + @Mock + Request request; + + @Mock + RequestMeta requestMeta; + + @BeforeEach + void setUp() { + authFilter = new RemoteRequestAuthFilter(); + GrpcProtocolAuthService protocolAuthService = new GrpcProtocolAuthService(authConfig); + protocolAuthService.initialize(); + ReflectionTestUtils.setField(authFilter, "protocolAuthService", protocolAuthService); + ReflectionTestUtils.setField(authFilter, "authConfig", authConfig); + } @AfterEach void tearDown() { @@ -58,26 +85,123 @@ void tearDown() { } @Test - void testFilter() { - Mockito.when(authConfigs.isAuthEnabled()).thenReturn(true); - - Request healthCheckRequest = new HealthCheckRequest(); + void testFilterWithoutSecured() throws NacosException { + Response actual = authFilter.filter(request, requestMeta, HealthCheckRequestHandler.class); + assertNull(actual); + } + + @Test + void testFilterDisabledAuth() throws NacosException { + when(authConfig.isAuthEnabled()).thenReturn(false); + Response actual = authFilter.filter(request, requestMeta, MockRequestHandler.class); + assertNull(actual); + } + + @Test + void testFilterWithoutServerIdentity() throws NacosException { + Response actual = authFilter.filter(request, requestMeta, MockInnerRequestHandler.class); + assertNotNull(actual); + assertEquals(ResponseCode.FAIL.getCode(), actual.getResultCode()); + assertEquals(403, actual.getErrorCode()); + assertEquals("Invalid server identity key or value, Please make sure set `nacos.core.auth.server.identity.key`" + + " and `nacos.core.auth.server.identity.value`, or open `nacos.core.auth.enable.userAgentAuthWhite`", + actual.getMessage()); + } + + @Test + void testFilterDisabledAuthWithInnerApi() throws NacosException { + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + Response actual = authFilter.filter(request, requestMeta, MockInnerRequestHandler.class); + assertNull(actual); + } + + @Test + void testFilterWithServerIdentity() throws NacosException { + when(authConfig.getServerIdentityKey()).thenReturn("1"); + when(authConfig.getServerIdentityValue()).thenReturn("2"); + when(request.getHeader("1")).thenReturn("2"); + Response actual = authFilter.filter(request, requestMeta, MockInnerRequestHandler.class); + assertNull(actual); + } + + @Test + void testFilterWithNoNeedAuthSecured() throws NacosException { + when(authConfig.isAuthEnabled()).thenReturn(true); + GrpcProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(false); + Response actual = authFilter.filter(request, requestMeta, MockRequestHandler.class); + assertNull(actual); + } + + @Test + void testFilterWithNeedAuthSecuredSuccess() throws NacosException { + when(authConfig.isAuthEnabled()).thenReturn(true); + GrpcProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(true); + when(protocolAuthService.validateAuthority(any(IdentityContext.class), any(Permission.class))).thenReturn(true); + Response actual = authFilter.filter(request, requestMeta, MockRequestHandler.class); + assertNull(actual); + } + + @Test + @Secured + void testFilterWithNeedAuthSecuredIdentityFailure() throws NacosException { + when(authConfig.isAuthEnabled()).thenReturn(true); + GrpcProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(false); + Response actual = authFilter.filter(request, requestMeta, MockRequestHandler.class); + assertNotNull(actual); + assertEquals(ResponseCode.FAIL.getCode(), actual.getResultCode()); + assertEquals(403, actual.getErrorCode()); + } + + @Test + @Secured + void testDoFilterWithNeedAuthSecuredAuthorityFailure() throws NacosException { + when(authConfig.isAuthEnabled()).thenReturn(true); + GrpcProtocolAuthService protocolAuthService = injectMockPlugins(); + when(protocolAuthService.enableAuth(any(Secured.class))).thenReturn(true); + doReturn(new IdentityContext()).when(protocolAuthService).parseIdentity(eq(request)); + doReturn(Resource.EMPTY_RESOURCE).when(protocolAuthService).parseResource(eq(request), any(Secured.class)); + when(protocolAuthService.validateIdentity(any(IdentityContext.class), any(Resource.class))).thenReturn(true); + when(protocolAuthService.validateAuthority(any(IdentityContext.class), any(Permission.class))).thenReturn( + false); + Response actual = authFilter.filter(request, requestMeta, MockRequestHandler.class); + assertNotNull(actual); + assertEquals(ResponseCode.FAIL.getCode(), actual.getResultCode()); + assertEquals(403, actual.getErrorCode()); + } + + private GrpcProtocolAuthService injectMockPlugins() { + GrpcProtocolAuthService protocolAuthService = new GrpcProtocolAuthService(authConfig); + protocolAuthService.initialize(); + GrpcProtocolAuthService spyProtocolAuthService = spy(protocolAuthService); + ReflectionTestUtils.setField(authFilter, "protocolAuthService", spyProtocolAuthService); + return spyProtocolAuthService; + } + + static class MockRequestHandler extends RequestHandler { - try { - Response healthCheckResponse = remoteRequestAuthFilter.filter(healthCheckRequest, new RequestMeta(), MockRequestHandler.class); - assertNull(healthCheckResponse); - } catch (NacosException e) { - e.printStackTrace(); - fail(e.getMessage()); + @Secured(resource = "xxx") + @Override + public HealthCheckResponse handle(Request request, RequestMeta meta) throws NacosException { + return new HealthCheckResponse(); } } - class MockRequestHandler extends RequestHandler { + static class MockInnerRequestHandler extends RequestHandler { - @Secured(resource = "xxx") + @Secured(resource = "xxx", apiType = ApiType.INNER_API) @Override - public Response handle(Request request, RequestMeta meta) throws NacosException { - return null; + public HealthCheckResponse handle(Request request, RequestMeta meta) throws NacosException { + return new HealthCheckResponse(); } } } diff --git a/core/src/test/java/com/alibaba/nacos/core/cluster/ServerMemberManagerTest.java b/core/src/test/java/com/alibaba/nacos/core/cluster/ServerMemberManagerTest.java index 070f2e58f14..bbe611db2d7 100644 --- a/core/src/test/java/com/alibaba/nacos/core/cluster/ServerMemberManagerTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/cluster/ServerMemberManagerTest.java @@ -35,17 +35,13 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.Mock; -import org.mockito.Mockito; import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoSettings; import org.mockito.quality.Strictness; -import org.springframework.boot.web.context.WebServerInitializedEvent; -import org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.test.util.ReflectionTestUtils; -import jakarta.servlet.ServletContext; import java.util.Collections; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; @@ -71,24 +67,15 @@ class ServerMemberManagerTest { @Mock private ConfigurableEnvironment environment; - @Mock - private ServletContext servletContext; - @Mock private EventPublisher eventPublisher; - @Mock - private WebServerInitializedEvent mockEvent; - @Mock private AuthConfigs authConfigs; @Mock private ConfigurableApplicationContext context; - @Mock - private ClusterRpcClientProxy clusterRpcClientProxy; - private ServerMemberManager serverMemberManager; @BeforeEach @@ -99,8 +86,7 @@ void setUp() throws Exception { ApplicationUtils.injectContext(context); EnvUtil.setEnvironment(environment); EnvUtil.setIsStandalone(true); - when(servletContext.getContextPath()).thenReturn(""); - serverMemberManager = new ServerMemberManager(servletContext); + serverMemberManager = new ServerMemberManager(); serverMemberManager.updateMember(Member.builder().ip("1.1.1.1").port(8848).state(NodeState.UP).build()); serverMemberManager.getMemberAddressInfos().add("1.1.1.1:8848"); NotifyCenter.getPublisherMap().put(MembersChangeEvent.class.getCanonicalName(), eventPublisher); @@ -133,7 +119,8 @@ void testUpdateVersionMember() { newMember.setExtendVal(MemberMetaDataConstants.VERSION, "testVersion"); assertTrue(serverMemberManager.update(newMember)); assertTrue(serverMemberManager.getMemberAddressInfos().contains("1.1.1.1:8848")); - assertEquals("testVersion", serverMemberManager.getServerList().get("1.1.1.1:8848").getExtendVal(MemberMetaDataConstants.VERSION)); + assertEquals("testVersion", + serverMemberManager.getServerList().get("1.1.1.1:8848").getExtendVal(MemberMetaDataConstants.VERSION)); verify(eventPublisher).publish(any(MembersChangeEvent.class)); } @@ -180,16 +167,6 @@ void testGetServerList() { assertEquals(2, serverMemberManager.getServerList().size()); } - @Test - void testEnvSetPort() { - ServletWebServerApplicationContext context = new ServletWebServerApplicationContext(); - context.setServerNamespace("management"); - Mockito.when(mockEvent.getApplicationContext()).thenReturn(context); - serverMemberManager.onApplicationEvent(mockEvent); - int port = EnvUtil.getPort(); - assertEquals(8848, port); - } - @Test void testHttpReportTaskWithoutMemberInfo() throws NacosException { Member testMember = Member.builder().ip("1.1.1.1").port(8848).state(NodeState.DOWN) @@ -198,7 +175,8 @@ void testHttpReportTaskWithoutMemberInfo() throws NacosException { testMember.getAbilities().getRemoteAbility().setSupportRemoteConnection(true); testMember.getAbilities().getRemoteAbility().setGrpcReportEnabled(false); serverMemberManager.updateMember(testMember); - assertTrue(serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); + assertTrue( + serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); NacosAsyncRestTemplate mockAsyncRestTemplate = mock(NacosAsyncRestTemplate.class); ReflectionTestUtils.setField(serverMemberManager, "asyncRestTemplate", mockAsyncRestTemplate); doAnswer(invocationOnMock -> { @@ -219,7 +197,8 @@ void testGrpcReportTaskWithoutMemberInfo() throws NacosException { testMember.setAbilities(new ServerAbilities()); testMember.getAbilities().getRemoteAbility().setSupportRemoteConnection(true); serverMemberManager.updateMember(testMember); - assertTrue(serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); + assertTrue( + serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); ServerMemberManager.MemberInfoReportTask infoReportTask = serverMemberManager.getInfoReportTask(); ClusterRpcClientProxy clusterRpcClientProxy = mock(ClusterRpcClientProxy.class); ReflectionTestUtils.setField(infoReportTask, "clusterRpcClientProxy", clusterRpcClientProxy); @@ -239,7 +218,8 @@ void testHttpReportTaskWithMemberInfoChanged() { testMember.getAbilities().getRemoteAbility().setSupportRemoteConnection(true); testMember.getAbilities().getRemoteAbility().setGrpcReportEnabled(false); serverMemberManager.updateMember(testMember); - assertTrue(serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); + assertTrue( + serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); Member newMember = Member.builder().ip("1.1.1.1").port(8848).state(NodeState.DOWN) .extendInfo(Collections.singletonMap(MemberMetaDataConstants.VERSION, "new")).build(); NacosAsyncRestTemplate mockAsyncRestTemplate = mock(NacosAsyncRestTemplate.class); @@ -262,7 +242,8 @@ void testGrpcReportTaskWithMemberInfoChanged() throws NacosException { testMember.setAbilities(new ServerAbilities()); testMember.getAbilities().getRemoteAbility().setSupportRemoteConnection(true); serverMemberManager.updateMember(testMember); - assertTrue(serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); + assertTrue( + serverMemberManager.find("1.1.1.1:8848").getExtendInfo().containsKey(MemberMetaDataConstants.VERSION)); Member newMember = Member.builder().ip("1.1.1.1").port(8848).state(NodeState.UP) .extendInfo(Collections.singletonMap(MemberMetaDataConstants.VERSION, "new")).build(); ServerMemberManager.MemberInfoReportTask infoReportTask = serverMemberManager.getInfoReportTask(); diff --git a/core/src/test/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxyTest.java b/core/src/test/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxyTest.java index dfbbea95e72..80deadb76bc 100644 --- a/core/src/test/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxyTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/cluster/remote/ClusterRpcClientProxyTest.java @@ -23,6 +23,7 @@ import com.alibaba.nacos.api.remote.ability.ServerRemoteAbility; import com.alibaba.nacos.api.remote.request.HealthCheckRequest; import com.alibaba.nacos.api.remote.response.Response; +import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.common.remote.ConnectionType; import com.alibaba.nacos.common.remote.client.RpcClient; import com.alibaba.nacos.common.remote.client.RpcClientFactory; @@ -69,6 +70,9 @@ class ClusterRpcClientProxyTest { @Mock private ServerMemberManager serverMemberManager; + @Mock + private AuthConfigs authConfigs; + @Mock private RpcClient client; @@ -96,6 +100,8 @@ void setUp() throws NacosException { clientMap.remove("Cluster-" + member.getAddress()).shutdown(); clientMap.put("Cluster-" + member.getAddress(), client); when(client.getConnectionType()).thenReturn(ConnectionType.GRPC); + when(authConfigs.getServerIdentityKey()).thenReturn("MockIdentityKey"); + when(authConfigs.getServerIdentityValue()).thenReturn("MockIdentityValue"); } @Test diff --git a/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityConfigTest.java b/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityConfigTest.java new file mode 100644 index 00000000000..0e23c3d90a5 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityConfigTest.java @@ -0,0 +1,82 @@ +/* + * 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.controller.compatibility; + +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.mock.env.MockEnvironment; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class ApiCompatibilityConfigTest { + + @BeforeEach + void setUp() { + EnvUtil.setEnvironment(new MockEnvironment()); + } + + @AfterEach + void tearDown() { + EnvUtil.setEnvironment(null); + ApiCompatibilityConfig config = ApiCompatibilityConfig.getInstance(); + config.setConsoleApiCompatibility(false); + config.setClientApiCompatibility(true); + config.setAdminApiCompatibility(true); + } + + @Test + void testGetInstance() { + ApiCompatibilityConfig instance1 = ApiCompatibilityConfig.getInstance(); + ApiCompatibilityConfig instance2 = ApiCompatibilityConfig.getInstance(); + assertEquals(instance1, instance2); + } + + @Test + void testGetConfigWithDefaultValues() { + ApiCompatibilityConfig config = ApiCompatibilityConfig.getInstance(); + config.getConfigFromEnv(); + assertTrue(config.isClientApiCompatibility()); + assertFalse(config.isConsoleApiCompatibility()); + assertTrue(config.isAdminApiCompatibility()); + assertEquals( + "ApiCompatibilityConfig{clientApiCompatibility=true, consoleApiCompatibility=false, adminApiCompatibility=true}", + config.printConfig()); + } + + @Test + void testGetConfigWithCustomValues() { + MockEnvironment properties = new MockEnvironment(); + properties.setProperty(ApiCompatibilityConfig.CLIENT_API_COMPATIBILITY_KEY, "false"); + properties.setProperty(ApiCompatibilityConfig.CONSOLE_API_COMPATIBILITY_KEY, "true"); + properties.setProperty(ApiCompatibilityConfig.ADMIN_API_COMPATIBILITY_KEY, "false"); + EnvUtil.setEnvironment(properties); + + ApiCompatibilityConfig config = ApiCompatibilityConfig.getInstance(); + config.getConfigFromEnv(); + + assertFalse(config.isClientApiCompatibility()); + assertTrue(config.isConsoleApiCompatibility()); + assertFalse(config.isAdminApiCompatibility()); + assertEquals( + "ApiCompatibilityConfig{clientApiCompatibility=false, consoleApiCompatibility=true, adminApiCompatibility=false}", + config.printConfig()); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityFilterTest.java b/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityFilterTest.java new file mode 100644 index 00000000000..ad23d24c4f9 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilityFilterTest.java @@ -0,0 +1,228 @@ +/* + * 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.controller.compatibility; + +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.plugin.auth.constant.ApiType; +import com.alibaba.nacos.sys.env.EnvUtil; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.env.MockEnvironment; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.lang.reflect.Method; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.matches; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class ApiCompatibilityFilterTest { + + @Mock + ControllerMethodsCache methodsCache; + + @Mock + HttpServletRequest servletRequest; + + @Mock + HttpServletResponse servletResponse; + + @Mock + FilterChain filterChain; + + ApiCompatibilityFilter filter; + + @BeforeEach + void setUp() { + EnvUtil.setEnvironment(new MockEnvironment()); + filter = new ApiCompatibilityFilter(methodsCache); + } + + @AfterEach + void tearDown() { + EnvUtil.setEnvironment(null); + ApiCompatibilityConfig config = ApiCompatibilityConfig.getInstance(); + config.setConsoleApiCompatibility(false); + config.setClientApiCompatibility(true); + config.setAdminApiCompatibility(true); + } + + @Test + void testDoFilterWithoutMethod() throws ServletException, IOException { + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain).doFilter(servletRequest, servletResponse); + verify(servletResponse, never()).sendError(anyInt(), anyString()); + } + + @Test + void testDoFilterWithoutCompatibility() throws ServletException, IOException, NoSuchMethodException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod("testDoFilterWithoutCompatibility"); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain).doFilter(servletRequest, servletResponse); + verify(servletResponse, never()).sendError(anyInt(), anyString()); + } + + @Test + @Compatibility(apiType = ApiType.ADMIN_API) + void testDoFilterWithAdminApiAndCompatibilityEnabled() throws ServletException, IOException, NoSuchMethodException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithAdminApiAndCompatibilityEnabled"); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + ApiCompatibilityConfig.getInstance().setAdminApiCompatibility(true); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain).doFilter(servletRequest, servletResponse); + verify(servletResponse, never()).sendError(anyInt(), anyString()); + } + + @Test + @Compatibility(apiType = ApiType.ADMIN_API) + void testDoFilterWithAdminApiAndCompatibilityDisabled() + throws ServletException, IOException, NoSuchMethodException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithAdminApiAndCompatibilityDisabled"); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + ApiCompatibilityConfig.getInstance().setAdminApiCompatibility(false); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain, never()).doFilter(servletRequest, servletResponse); + verify(servletResponse).sendError(eq(HttpServletResponse.SC_GONE), + matches(".*" + ApiCompatibilityConfig.ADMIN_API_COMPATIBILITY_KEY + ".*")); + } + + @Test + @Compatibility(apiType = ApiType.ADMIN_API, alternatives = "/test/admin") + void testDoFilterWithAdminApiAndCompatibilityDisabledAndAlternatives() + throws NoSuchMethodException, ServletException, IOException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithAdminApiAndCompatibilityDisabledAndAlternatives"); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + ApiCompatibilityConfig.getInstance().setAdminApiCompatibility(false); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain, never()).doFilter(servletRequest, servletResponse); + verify(servletResponse).sendError(eq(HttpServletResponse.SC_GONE), matches(".*/test/admin.*")); + } + + @Test + @Compatibility(apiType = ApiType.CONSOLE_API) + void testDoFilterWithConsoleApiAndCompatibilityEnabled() + throws ServletException, IOException, NoSuchMethodException { + ApiCompatibilityConfig.getInstance().setConsoleApiCompatibility(true); + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithConsoleApiAndCompatibilityEnabled"); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain).doFilter(servletRequest, servletResponse); + verify(servletResponse, never()).sendError(anyInt(), anyString()); + } + + @Test + @Compatibility(apiType = ApiType.CONSOLE_API) + void testDoFilterWithConsoleApiAndCompatibilityDisabled() + throws ServletException, IOException, NoSuchMethodException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithConsoleApiAndCompatibilityDisabled"); + ApiCompatibilityConfig.getInstance().setConsoleApiCompatibility(false); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain, never()).doFilter(servletRequest, servletResponse); + verify(servletResponse).sendError(eq(HttpServletResponse.SC_GONE), + matches(".*" + ApiCompatibilityConfig.CONSOLE_API_COMPATIBILITY_KEY + ".*")); + } + + @Test + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "/test/console") + void testDoFilterWithConsoleApiAndCompatibilityDisabledAndAlternatives() + throws NoSuchMethodException, ServletException, IOException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithConsoleApiAndCompatibilityDisabledAndAlternatives"); + ApiCompatibilityConfig.getInstance().setConsoleApiCompatibility(false); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain, never()).doFilter(servletRequest, servletResponse); + verify(servletResponse).sendError(eq(HttpServletResponse.SC_GONE), matches(".*/test/console.*")); + } + + @Test + @Compatibility(apiType = ApiType.OPEN_API) + void testDoFilterWithOpenApiAndCompatibilityEnabled() throws ServletException, IOException, NoSuchMethodException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithOpenApiAndCompatibilityEnabled"); + ApiCompatibilityConfig.getInstance().setClientApiCompatibility(true); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain).doFilter(servletRequest, servletResponse); + verify(servletResponse, never()).sendError(anyInt(), anyString()); + } + + @Test + @Compatibility(apiType = ApiType.OPEN_API) + void testDoFilterWithOpenApiAndCompatibilityDisabled() throws ServletException, IOException, NoSuchMethodException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithOpenApiAndCompatibilityDisabled"); + ApiCompatibilityConfig.getInstance().setClientApiCompatibility(false); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain, never()).doFilter(servletRequest, servletResponse); + verify(servletResponse).sendError(eq(HttpServletResponse.SC_GONE), + matches(".*" + ApiCompatibilityConfig.CLIENT_API_COMPATIBILITY_KEY + ".*")); + } + + @Test + @Compatibility(apiType = ApiType.OPEN_API, alternatives = "/test/client") + void testDoFilterWithOpenApiAndCompatibilityDisabledAndAlternatives() + throws NoSuchMethodException, ServletException, IOException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod( + "testDoFilterWithOpenApiAndCompatibilityDisabledAndAlternatives"); + ApiCompatibilityConfig.getInstance().setClientApiCompatibility(false); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain, never()).doFilter(servletRequest, servletResponse); + verify(servletResponse).sendError(eq(HttpServletResponse.SC_GONE), matches(".*/test/client.*")); + } + + @Test + @Compatibility(apiType = ApiType.INNER_API) + void testDoFilterWithInnerApi() throws NoSuchMethodException, ServletException, IOException { + Method method = ApiCompatibilityFilterTest.class.getDeclaredMethod("testDoFilterWithInnerApi"); + when(methodsCache.getMethod(servletRequest)).thenReturn(method); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(filterChain).doFilter(servletRequest, servletResponse); + verify(servletResponse, never()).sendError(anyInt(), anyString()); + } + + @Test + void testDoFilterWithException() throws ServletException, IOException { + doThrow(new ServletException()).when(filterChain).doFilter(servletRequest, servletResponse); + filter.doFilter(servletRequest, servletResponse, filterChain); + verify(servletResponse).sendError(eq(HttpServletResponse.SC_INTERNAL_SERVER_ERROR), + eq("Handle API Compatibility failed, please see log for detail.")); + } +} \ No newline at end of file diff --git a/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilitySpringConfigTest.java b/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilitySpringConfigTest.java new file mode 100644 index 00000000000..c529f2fda33 --- /dev/null +++ b/core/src/test/java/com/alibaba/nacos/core/controller/compatibility/ApiCompatibilitySpringConfigTest.java @@ -0,0 +1,53 @@ +/* + * 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.controller.compatibility; + +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.boot.web.servlet.FilterRegistrationBean; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(MockitoExtension.class) +class ApiCompatibilitySpringConfigTest { + + @Mock + ControllerMethodsCache controllerMethodsCache; + + ApiCompatibilitySpringConfig apiCompatibilitySpringConfig; + + @BeforeEach + void setUp() { + apiCompatibilitySpringConfig = new ApiCompatibilitySpringConfig(); + } + + @Test + public void testApiCompatibilityFilterRegistration() { + ApiCompatibilityFilter apiCompatibilityFilter = apiCompatibilitySpringConfig.apiCompatibilityFilter( + controllerMethodsCache); + FilterRegistrationBean registrationBean = apiCompatibilitySpringConfig.apiCompatibilityFilterRegistration( + apiCompatibilityFilter); + assertEquals(5, registrationBean.getOrder()); + assertTrue(registrationBean.getUrlPatterns().contains("/v1/*")); + assertTrue(registrationBean.getUrlPatterns().contains("/v2/*")); + } +} \ No newline at end of file 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()); + } +} diff --git a/core/src/test/java/com/alibaba/nacos/core/listener/StandaloneProfileApplicationListenerTest.java b/core/src/test/java/com/alibaba/nacos/core/listener/StandaloneProfileApplicationListenerTest.java index 97595561c34..837c8f77718 100644 --- a/core/src/test/java/com/alibaba/nacos/core/listener/StandaloneProfileApplicationListenerTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/listener/StandaloneProfileApplicationListenerTest.java @@ -18,6 +18,8 @@ import com.alibaba.nacos.common.utils.ArrayUtils; import com.alibaba.nacos.core.code.StandaloneProfileApplicationListener; +import com.alibaba.nacos.core.listener.startup.NacosStartUp; +import com.alibaba.nacos.core.listener.startup.NacosStartUpManager; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -45,6 +47,7 @@ class StandaloneProfileApplicationListenerTest { @BeforeAll static void init() { System.setProperty("nacos.standalone", "true"); + NacosStartUpManager.start(NacosStartUp.CORE_START_UP_PHASE); } @Test diff --git a/core/src/test/java/com/alibaba/nacos/core/service/NamespaceOperationServiceTest.java b/core/src/test/java/com/alibaba/nacos/core/service/NamespaceOperationServiceTest.java index 6cb70e825bb..061c8c4f1db 100644 --- a/core/src/test/java/com/alibaba/nacos/core/service/NamespaceOperationServiceTest.java +++ b/core/src/test/java/com/alibaba/nacos/core/service/NamespaceOperationServiceTest.java @@ -16,8 +16,10 @@ package com.alibaba.nacos.core.service; +import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.api.exception.NacosException; import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.common.utils.NamespaceUtil; import com.alibaba.nacos.core.namespace.injector.AbstractNamespaceDetailInjector; import com.alibaba.nacos.core.namespace.model.Namespace; import com.alibaba.nacos.core.namespace.model.NamespaceTypeEnum; @@ -57,7 +59,9 @@ class NamespaceOperationServiceTest { private static final String TEST_NAMESPACE_DESC = "testDesc"; - private static final String DEFAULT_NAMESPACE = "public"; + private static final String DEFAULT_NAMESPACE_SHOW_NAME = "public"; + + private static final String DEFAULT_NAMESPACE_DESCRIPTION = "Default Namespace"; private static final int DEFAULT_QUOTA = 200; @@ -92,8 +96,9 @@ void testGetNamespaceList() { List list = namespaceOperationService.getNamespaceList(); assertEquals(2, list.size()); Namespace namespaceA = list.get(0); - assertEquals("", namespaceA.getNamespace()); - assertEquals(DEFAULT_NAMESPACE, namespaceA.getNamespaceShowName()); + assertEquals(Constants.DEFAULT_NAMESPACE_ID, namespaceA.getNamespace()); + assertEquals(DEFAULT_NAMESPACE_SHOW_NAME, namespaceA.getNamespaceShowName()); + assertEquals(DEFAULT_NAMESPACE_DESCRIPTION, namespaceA.getNamespaceDesc()); assertEquals(DEFAULT_QUOTA, namespaceA.getQuota()); assertEquals(1, namespaceA.getConfigCount()); @@ -104,7 +109,7 @@ void testGetNamespaceList() { } @Test - void testGetNamespace() throws NacosException { + void testGetNamespace() { assertThrows(NacosApiException.class, () -> { TenantInfo tenantInfo = new TenantInfo(); @@ -113,8 +118,8 @@ void testGetNamespace() throws NacosException { tenantInfo.setTenantDesc(TEST_NAMESPACE_DESC); when(namespacePersistService.findTenantByKp(DEFAULT_KP, TEST_NAMESPACE_ID)).thenReturn(tenantInfo); when(namespacePersistService.findTenantByKp(DEFAULT_KP, "test_not_exist_id")).thenReturn(null); - Namespace namespaceAllInfo = new Namespace(TEST_NAMESPACE_ID, TEST_NAMESPACE_NAME, TEST_NAMESPACE_DESC, DEFAULT_QUOTA, 1, - NamespaceTypeEnum.GLOBAL.getType()); + Namespace namespaceAllInfo = new Namespace(TEST_NAMESPACE_ID, TEST_NAMESPACE_NAME, TEST_NAMESPACE_DESC, + DEFAULT_QUOTA, 1, NamespaceTypeEnum.GLOBAL.getType()); Namespace namespace = namespaceOperationService.getNamespace(TEST_NAMESPACE_ID); assertEquals(namespaceAllInfo.getNamespace(), namespace.getNamespace()); assertEquals(namespaceAllInfo.getNamespaceShowName(), namespace.getNamespaceShowName()); @@ -132,14 +137,22 @@ void testGetNamespace() throws NacosException { void testCreateNamespace() throws NacosException { when(namespacePersistService.tenantInfoCountByTenantId(anyString())).thenReturn(0); namespaceOperationService.createNamespace(TEST_NAMESPACE_ID, TEST_NAMESPACE_NAME, TEST_NAMESPACE_DESC); - verify(namespacePersistService).insertTenantInfoAtomic(eq(DEFAULT_KP), eq(TEST_NAMESPACE_ID), eq(TEST_NAMESPACE_NAME), - eq(TEST_NAMESPACE_DESC), any(), anyLong()); + verify(namespacePersistService).insertTenantInfoAtomic(eq(DEFAULT_KP), eq(TEST_NAMESPACE_ID), + eq(TEST_NAMESPACE_NAME), eq(TEST_NAMESPACE_DESC), any(), anyLong()); + } + + @Test + void testCreateNamespaceForDefaultNamespace() throws NacosException { + assertThrows(NacosApiException.class, + () -> namespaceOperationService.createNamespace(NamespaceUtil.getNamespaceDefaultId(), + TEST_NAMESPACE_NAME, TEST_NAMESPACE_DESC)); } @Test void testEditNamespace() { namespaceOperationService.editNamespace(TEST_NAMESPACE_ID, TEST_NAMESPACE_NAME, TEST_NAMESPACE_DESC); - verify(namespacePersistService).updateTenantNameAtomic(DEFAULT_KP, TEST_NAMESPACE_ID, TEST_NAMESPACE_NAME, TEST_NAMESPACE_DESC); + verify(namespacePersistService).updateTenantNameAtomic(DEFAULT_KP, TEST_NAMESPACE_ID, TEST_NAMESPACE_NAME, + TEST_NAMESPACE_DESC); } @Test diff --git a/distribution/bin/startup.cmd b/distribution/bin/startup.cmd index 805f2a02cc8..9ec13ddc8f0 100755 --- a/distribution/bin/startup.cmd +++ b/distribution/bin/startup.cmd @@ -31,7 +31,8 @@ set FUNCTION_MODE_INDEX=-1 set SERVER_INDEX=-1 set EMBEDDED_STORAGE_INDEX=-1 set EMBEDDED_STORAGE="" - +set DEPLOYMENT_INDEX=-1 +set DEPLOYMENT="merged" set i=0 for %%a in (%*) do ( @@ -39,6 +40,7 @@ for %%a in (%*) do ( if "%%a" == "-f" ( set /a FUNCTION_MODE_INDEX=!i!+1 ) if "%%a" == "-s" ( set /a SERVER_INDEX=!i!+1 ) if "%%a" == "-p" ( set /a EMBEDDED_STORAGE_INDEX=!i!+1 ) + if "%%a" == "-d" ( set /a DEPLOYMENT_INDEX=!i!+1 ) set /a i+=1 ) @@ -48,6 +50,7 @@ for %%a in (%*) do ( if %FUNCTION_MODE_INDEX% == !i! ( set FUNCTION_MODE="%%a" ) if %SERVER_INDEX% == !i! (set SERVER="%%a") if %EMBEDDED_STORAGE_INDEX% == !i! (set EMBEDDED_STORAGE="%%a") + if %DEPLOYMENT_INDEX% == !i! (set DEPLOYMENT="%%a") set /a i+=1 ) @@ -79,6 +82,7 @@ if %FUNCTION_MODE% == "naming" ( ) rem set nacos options +set "NACOS_OPTS=%NACOS_OPTS% -Dnacos.deployment.mode=%DEPLOYMENT%" set "NACOS_OPTS=%NACOS_OPTS% -Dloader.path=%BASE_DIR%/plugins,%BASE_DIR%/plugins/health,%BASE_DIR%/plugins/cmdb,%BASE_DIR%/plugins/selector" set "NACOS_OPTS=%NACOS_OPTS% -Dnacos.home=%BASE_DIR%" set "NACOS_OPTS=%NACOS_OPTS% -jar %BASE_DIR%\target\%SERVER%.jar" diff --git a/distribution/bin/startup.sh b/distribution/bin/startup.sh index cfe2f486126..1e7e970b0e7 100644 --- a/distribution/bin/startup.sh +++ b/distribution/bin/startup.sh @@ -51,6 +51,9 @@ if [ -z "$JAVA_HOME" ]; then if [ -z "$JAVA_PATH" ]; then error_exit "Please set the JAVA_HOME variable in your environment, We need java(x64)! jdk8 or later is better!" fi + if [ -L "$JAVA_PATH" ]; then + JAVA_PATH=$(readlink -f "$JAVA_PATH") + fi JAVA_HOME=$(dirname "$JAVA_PATH")/.. export JAVA_HOME=$(cd "$JAVA_HOME" && pwd) fi @@ -61,7 +64,8 @@ export MODE="cluster" export FUNCTION_MODE="all" export MEMBER_LIST="" export EMBEDDED_STORAGE="" -while getopts ":m:f:s:c:p:" opt +export DEPLOYMENT="merged" +while getopts ":m:f:s:c:p:d:" opt do case $opt in m) @@ -74,6 +78,8 @@ do MEMBER_LIST=$OPTARG;; p) EMBEDDED_STORAGE=$OPTARG;; + d) + DEPLOYMENT=$OPTARG;; ?) echo "Unknown parameter" exit 1;; @@ -117,6 +123,7 @@ else JAVA_OPT="${JAVA_OPT} -Xloggc:${BASE_DIR}/logs/nacos_gc.log -verbose:gc -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M" fi +JAVA_OPT="${JAVA_OPT} -Dnacos.deployment.type=${DEPLOYMENT}" JAVA_OPT="${JAVA_OPT} -Dloader.path=${BASE_DIR}/plugins,${BASE_DIR}/plugins/health,${BASE_DIR}/plugins/cmdb,${BASE_DIR}/plugins/selector" JAVA_OPT="${JAVA_OPT} -Dnacos.home=${BASE_DIR}" JAVA_OPT="${JAVA_OPT} -jar ${BASE_DIR}/target/${SERVER}.jar" diff --git a/distribution/conf/announcement_en-US.conf b/distribution/conf/announcement_en-US.conf index 19e8dbcd589..2ad141b91f5 100644 --- a/distribution/conf/announcement_en-US.conf +++ b/distribution/conf/announcement_en-US.conf @@ -1 +1 @@ -Authentication has not been enabled in cluster, please refer to Documentation to enable~ \ No newline at end of file +Authentication has not been enabled in cluster, please refer to Documentation to enable~ \ No newline at end of file diff --git a/distribution/conf/announcement_zh-CN.conf b/distribution/conf/announcement_zh-CN.conf index c18d8a41fc1..3047c864765 100644 --- a/distribution/conf/announcement_zh-CN.conf +++ b/distribution/conf/announcement_zh-CN.conf @@ -1 +1 @@ -当前集群没有开启鉴权,请参考文档开启鉴权~ \ No newline at end of file +当前集群没有开启鉴权,请参考文档开启鉴权~ \ No newline at end of file diff --git a/distribution/conf/application.properties b/distribution/conf/application.properties index 61ab2ef32fb..e45a7a015d9 100644 --- a/distribution/conf/application.properties +++ b/distribution/conf/application.properties @@ -1,5 +1,5 @@ # -# Copyright 1999-2021 Alibaba Group Holding Ltd. +# Copyright 1999-2025 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. @@ -14,13 +14,11 @@ # limitations under the License. # -#*************** Spring Boot Related Configurations ***************# -### Default web context path: -server.servlet.contextPath=/nacos -### Include message field -server.error.include-message=ALWAYS -### Default web server port: -server.port=8848 +#--------------- Nacos Common Configurations ---------------# + +#*************** Nacos port Related Configurations ***************# +### Nacos Server Main port +nacos.server.main.port=8848 #*************** Network Related Configurations ***************# ### If prefer hostname over ip for Nacos server addresses in cluster.conf: @@ -29,137 +27,216 @@ server.port=8848 ### Specify local server's IP: # nacos.inetutils.ip-address= - -#*************** Config Module Related Configurations ***************# -### If use MySQL as datasource: -### Deprecated configuration property, it is recommended to use `spring.sql.init.platform` replaced. -# spring.datasource.platform=mysql -# spring.sql.init.platform=mysql - +#*************** Datasource Related Configurations ***************# +### nacos.plugin.datasource.log.enabled=true +#spring.sql.init.platform=mysql ### Count of DB: # db.num=1 ### Connect URL of DB: # db.url.0=jdbc:mysql://127.0.0.1:3306/nacos?characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useUnicode=true&useSSL=false&serverTimezone=UTC -# db.user.0=nacos -# db.password.0=nacos +# db.user=nacos +# db.password=nacos -### Connection pool configuration: hikariCP -db.pool.config.connectionTimeout=30000 -db.pool.config.validationTimeout=10000 -db.pool.config.maximumPoolSize=20 -db.pool.config.minimumIdle=2 +#*************** Metrics Related Configurations ***************# +### Metrics for prometheus +#management.endpoints.web.exposure.include=prometheus -### the maximum retry times for push -nacos.config.push.maxRetryTime=50 +### Metrics for elastic search +management.metrics.export.elastic.enabled=false +#management.metrics.export.elastic.host=http://localhost:9200 -#*************** Naming Module Related Configurations ***************# +### Metrics for influx +management.metrics.export.influx.enabled=false +#management.metrics.export.influx.db=springboot +#management.metrics.export.influx.uri=http://localhost:8086 +#management.metrics.export.influx.auto-create-db=true +#management.metrics.export.influx.consistency=one +#management.metrics.export.influx.compressed=true -### If enable data warmup. If set to false, the server would accept request without local data preparation: -# nacos.naming.data.warmup=true +#*************** Core Related Configurations ***************# -### If enable the instance auto expiration, kind like of health check of instance: -# nacos.naming.expireInstance=true +### set the WorkerID manually +# nacos.core.snowflake.worker-id= -### Add in 2.0.0 -### The interval to clean empty service, unit: milliseconds. -# nacos.naming.clean.empty-service.interval=60000 +### Member-MetaData +# nacos.core.member.meta.site= +# nacos.core.member.meta.adweight= +# nacos.core.member.meta.weight= -### The expired time to clean empty service, unit: milliseconds. -# nacos.naming.clean.empty-service.expired-time=60000 +### MemberLookup +### Addressing pattern category, If set, the priority is highest +# nacos.core.member.lookup.type=[file,address-server] -### The interval to clean expired metadata, unit: milliseconds. -# nacos.naming.clean.expired-metadata.interval=5000 +## Set the cluster list with a configuration file or command-line argument +# nacos.member.list=192.168.16.101:8847?raft_port=8807,192.168.16.101?raft_port=8808,192.168.16.101:8849?raft_port=8809 -### The expired time to clean metadata, unit: milliseconds. -# nacos.naming.clean.expired-metadata.expired-time=60000 +## for AddressServerMemberLookup +# Maximum number of retries to query the address server upon initialization +# nacos.core.address-server.retry=5 +## Server domain name address of [address-server] mode +# address.server.domain=jmenv.tbsite.net +## Server port of [address-server] mode +# address.server.port=8080 +## Request address of [address-server] mode +# address.server.url=/nacos/serverlist -### The delay time before push task to execute from service changed, unit: milliseconds. -# nacos.naming.push.pushTaskDelay=500 +#*************** JRaft Related Configurations ***************# -### The timeout for push task execute, unit: milliseconds. -# nacos.naming.push.pushTaskTimeout=5000 +### Sets the Raft cluster election timeout, default value is 5 second +# nacos.core.protocol.raft.data.election_timeout_ms=5000 +### Sets the amount of time the Raft snapshot will execute periodically, default is 30 minute +# nacos.core.protocol.raft.data.snapshot_interval_secs=30 +### raft internal worker threads +# nacos.core.protocol.raft.data.core_thread_num=8 +### Number of threads required for raft business request processing +# nacos.core.protocol.raft.data.cli_service_thread_num=4 +### raft linear read strategy. Safe linear reads are used by default, that is, the Leader tenure is confirmed by heartbeat +# nacos.core.protocol.raft.data.read_index_type=ReadOnlySafe +### rpc request timeout, default 5 seconds +# nacos.core.protocol.raft.data.rpc_request_timeout_ms=5000 +### enable to support prometheus service discovery +#nacos.prometheus.metrics.enabled=true -### The delay time for retrying failed push task, unit: milliseconds. -# nacos.naming.push.pushTaskRetryDelay=1000 +#*************** Distro Related Configurations ***************# -### Since 2.0.3 -### The expired time for inactive client, unit: milliseconds. -# nacos.naming.client.expired.time=180000 +### Distro data sync delay time, when sync task delayed, task will be merged for same data key. Default 1 second. +# nacos.core.protocol.distro.data.sync.delayMs=1000 +### Distro data sync timeout for one sync data, default 3 seconds. +# nacos.core.protocol.distro.data.sync.timeoutMs=3000 +### Distro data sync retry delay time when sync data failed or timeout, same behavior with delayMs, default 3 seconds. +# nacos.core.protocol.distro.data.sync.retryDelayMs=3000 +### Distro data verify interval time, verify synced data whether expired for a interval. Default 5 seconds. +# nacos.core.protocol.distro.data.verify.intervalMs=5000 +### Distro data verify timeout for one verify, default 3 seconds. +# nacos.core.protocol.distro.data.verify.timeoutMs=3000 +### Distro data load retry delay when load snapshot data failed, default 30 seconds. +# nacos.core.protocol.distro.data.load.retryDelayMs=30000 +### enable to support prometheus service discovery +#nacos.prometheus.metrics.enabled=true -#*************** CMDB Module Related Configurations ***************# -### The interval to dump external CMDB in seconds: -# nacos.cmdb.dumpTaskInterval=3600 +#*************** Grpc Configurations ***************# -### The interval of polling data change event in seconds: -# nacos.cmdb.eventTaskInterval=10 +### Sets the maximum message size allowed to be received on the server. +#nacos.remote.server.grpc.sdk.max-inbound-message-size=10485760 +### Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. +#nacos.remote.server.grpc.sdk.keep-alive-time=7200000 +### Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. +#nacos.remote.server.grpc.sdk.keep-alive-timeout=20000 +### Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes +#nacos.remote.server.grpc.sdk.permit-keep-alive-time=300000 +### cluster grpc(inside the nacos server) configuration +#nacos.remote.server.grpc.cluster.max-inbound-message-size=10485760 +### Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. +#nacos.remote.server.grpc.cluster.keep-alive-time=7200000 +### Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. +#nacos.remote.server.grpc.cluster.keep-alive-timeout=20000 +### Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes +#nacos.remote.server.grpc.cluster.permit-keep-alive-time=300000 -### The interval of loading labels in seconds: -# nacos.cmdb.labelTaskInterval=300 +#*************** Config Module Related Configurations ***************# -### If turn on data loading task: -# nacos.cmdb.loadDataAtStart=false +### the maximum retry times for push +nacos.config.push.maxRetryTime=50 -#***********Metrics for tomcat **************************# -server.tomcat.mbeanregistry.enabled=true +#*************** Naming Module Related Configurations ***************# +### Data dispatch task execution period in milliseconds: -#***********Expose prometheus and health **************************# -#management.endpoints.web.exposure.include=prometheus,health +### If enable data warmup. If set to false, the server would accept request without local data preparation: +# nacos.naming.data.warmup=true -### Metrics for elastic search -management.metrics.export.elastic.enabled=false -#management.metrics.export.elastic.host=http://localhost:9200 +### If enable the instance auto expiration, kind like of health check of instance: +# nacos.naming.expireInstance=true -### Metrics for influx -management.metrics.export.influx.enabled=false -#management.metrics.export.influx.db=springboot -#management.metrics.export.influx.uri=http://localhost:8086 -#management.metrics.export.influx.auto-create-db=true -#management.metrics.export.influx.consistency=one -#management.metrics.export.influx.compressed=true +nacos.naming.empty-service.auto-clean=true +nacos.naming.empty-service.clean.initial-delay-ms=50000 +nacos.naming.empty-service.clean.period-time-ms=30000 + +#--------------- Nacos Web Server Configurations ---------------# + +#*************** Nacos Web Server Related Configurations ***************# +### Nacos Server Web context path: +nacos.server.contextPath=/nacos #*************** Access Log Related Configurations ***************# ### If turn on the access log: server.tomcat.accesslog.enabled=true -### file name pattern, one file per hour -server.tomcat.accesslog.rotate=true -server.tomcat.accesslog.file-date-format=.yyyy-MM-dd-HH +### accesslog automatic cleaning time +server.tomcat.accesslog.max-days=30 + ### The access log pattern: server.tomcat.accesslog.pattern=%h %l %u %t "%r" %s %b %D %{User-Agent}i %{Request-Source}i ### The directory of access log: server.tomcat.basedir=file:. -#*************** Access Control Related Configurations ***************# -### If enable spring security, this option is deprecated in 1.2.0: -#spring.security.enabled=false +#*************** API Related Configurations ***************# +### Include message field +server.error.include-message=ALWAYS + +### Enabled for open API compatibility +# nacos.core.api.compatibility.client.enabled=true +### Enabled for admin API compatibility +# nacos.core.api.compatibility.admin.enabled=true +### Enabled for console API compatibility +# nacos.core.api.compatibility.console.enabled=false + +#--------------- Nacos Console Configurations ---------------# + +#*************** Nacos Console Related Configurations ***************# +### Nacos Console Main port +nacos.console.port=8080 +### Nacos Server Web context path: +nacos.console.contextPath= + +#************** Console UI Configuration ***************# + +### Turn on/off the nacos console ui. +#nacos.console.ui.enabled=true + +#--------------- Nacos Plugin Configurations ---------------# -### The ignore urls of auth +#*************** CMDB Plugin Related Configurations ***************# +### The interval to dump external CMDB in seconds: +# nacos.cmdb.dumpTaskInterval=3600 + +### The interval of polling data change event in seconds: +# nacos.cmdb.eventTaskInterval=10 + +### The interval of loading labels in seconds: +# nacos.cmdb.labelTaskInterval=300 + +### If turn on data loading task: +# nacos.cmdb.loadDataAtStart=false + +#*************** Auth Plugin Related Configurations ***************# +### The ignore urls of auth, will be deprecated in the future: nacos.security.ignore.urls=/,/error,/**/*.css,/**/*.js,/**/*.html,/**/*.map,/**/*.svg,/**/*.png,/**/*.ico,/console-ui/public/**,/v1/auth/**,/v1/console/health/**,/actuator/**,/v1/console/server/** -### The auth system to use, currently only 'nacos' and 'ldap' is supported: +### The auth system to use, default 'nacos' and 'ldap' is supported, other type should be implemented by yourself: nacos.core.auth.system.type=nacos ### If turn on auth system: +# Whether open nacos server API auth system nacos.core.auth.enabled=false +# Whether open nacos console API auth system +nacos.core.auth.console.enabled=true ### Turn on/off caching of auth information. By turning on this switch, the update of auth information would have a 15 seconds delay. nacos.core.auth.caching.enabled=true -### Since 1.4.1, Turn on/off white auth for user-agent: nacos-server, only for upgrade from old version. -nacos.core.auth.enable.userAgentAuthWhite=false - -### Since 1.4.1, worked when nacos.core.auth.enabled=true and nacos.core.auth.enable.userAgentAuthWhite=false. +### worked when nacos.core.auth.enabled=true ### The two properties is the white list for auth and used by identity the request from other server. nacos.core.auth.server.identity.key= nacos.core.auth.server.identity.value= -### worked when nacos.core.auth.system.type=nacos +### worked when nacos.core.auth.system.type=nacos or nacos.core.auth.console.enabled=true ### The token expiration in seconds: nacos.core.auth.plugin.nacos.token.cache.enable=false nacos.core.auth.plugin.nacos.token.expire.seconds=18000 -### The default token (Base64 String): +### The default token (Base64 string): +#nacos.core.auth.plugin.nacos.token.secret.key=VGhpc0lzTXlDdXN0b21TZWNyZXRLZXkwMTIzNDU2Nzg= nacos.core.auth.plugin.nacos.token.secret.key= ### worked when nacos.core.auth.system.type=ldap,{0} is Placeholder,replace login username @@ -197,101 +274,21 @@ nacos.core.auth.plugin.nacos.token.secret.key= # fileformatcheck,which validate the import file of type and content #nacos.core.config.plugin.fileformatcheck.enabled=false -#*************** Istio Related Configurations ***************# +#*************** Istio Plugin Related Configurations ***************# ### If turn on the MCP server: nacos.istio.mcp.server.enabled=false -#*************** Core Related Configurations ***************# - -### set the WorkerID manually -# nacos.core.snowflake.worker-id= +#--------------- Nacos Experimental Features Configurations ---------------# -### Member-MetaData -# nacos.core.member.meta.site= -# nacos.core.member.meta.adweight= -# nacos.core.member.meta.weight= - -### MemberLookup -### Addressing pattern category, If set, the priority is highest -# nacos.core.member.lookup.type=[file,address-server] -## Set the cluster list with a configuration file or command-line argument -# nacos.member.list=192.168.16.101:8847?raft_port=8807,192.168.16.101?raft_port=8808,192.168.16.101:8849?raft_port=8809 -## for AddressServerMemberLookup -# Maximum number of retries to query the address server upon initialization -# nacos.core.address-server.retry=5 -## Server domain name address of [address-server] mode -# address.server.domain=jmenv.tbsite.net -## Server port of [address-server] mode -# address.server.port=8080 -## Request address of [address-server] mode -# address.server.url=/nacos/serverlist +#*************** K8s Related Configurations ***************# +### If turn on the K8s sync: +nacos.k8s.sync.enabled=false -#*************** JRaft Related Configurations ***************# +### If use the Java API from an application outside a kubernetes cluster +#nacos.k8s.sync.outsideCluster=false +#nacos.k8s.sync.kubeConfig=/.kube/config -### Sets the Raft cluster election timeout, default value is 5 second -# nacos.core.protocol.raft.data.election_timeout_ms=5000 -### Sets the amount of time the Raft snapshot will execute periodically, default is 30 minute -# nacos.core.protocol.raft.data.snapshot_interval_secs=30 -### raft internal worker threads -# nacos.core.protocol.raft.data.core_thread_num=8 -### Number of threads required for raft business request processing -# nacos.core.protocol.raft.data.cli_service_thread_num=4 -### raft linear read strategy. Safe linear reads are used by default, that is, the Leader tenure is confirmed by heartbeat -# nacos.core.protocol.raft.data.read_index_type=ReadOnlySafe -### rpc request timeout, default 5 seconds -# nacos.core.protocol.raft.data.rpc_request_timeout_ms=5000 - -#*************** Distro Related Configurations ***************# - -### Distro data sync delay time, when sync task delayed, task will be merged for same data key. Default 1 second. -# nacos.core.protocol.distro.data.sync.delayMs=1000 - -### Distro data sync timeout for one sync data, default 3 seconds. -# nacos.core.protocol.distro.data.sync.timeoutMs=3000 - -### Distro data sync retry delay time when sync data failed or timeout, same behavior with delayMs, default 3 seconds. -# nacos.core.protocol.distro.data.sync.retryDelayMs=3000 - -### Distro data verify interval time, verify synced data whether expired for a interval. Default 5 seconds. -# nacos.core.protocol.distro.data.verify.intervalMs=5000 - -### Distro data verify timeout for one verify, default 3 seconds. -# nacos.core.protocol.distro.data.verify.timeoutMs=3000 - -### Distro data load retry delay when load snapshot data failed, default 30 seconds. -# nacos.core.protocol.distro.data.load.retryDelayMs=30000 - -### enable to support prometheus service discovery -#nacos.prometheus.metrics.enabled=true - -### Since 2.3 -#*************** Grpc Configurations ***************# - -## sdk grpc(between nacos server and client) configuration -## Sets the maximum message size allowed to be received on the server. -#nacos.remote.server.grpc.sdk.max-inbound-message-size=10485760 - -## Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. -#nacos.remote.server.grpc.sdk.keep-alive-time=7200000 - -## Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. -#nacos.remote.server.grpc.sdk.keep-alive-timeout=20000 - - -## Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes -#nacos.remote.server.grpc.sdk.permit-keep-alive-time=300000 - -## cluster grpc(inside the nacos server) configuration -#nacos.remote.server.grpc.cluster.max-inbound-message-size=10485760 - -## Sets the time(milliseconds) without read activity before sending a keepalive ping. The typical default is two hours. -#nacos.remote.server.grpc.cluster.keep-alive-time=7200000 - -## Sets a time(milliseconds) waiting for read activity after sending a keepalive ping. Defaults to 20 seconds. -#nacos.remote.server.grpc.cluster.keep-alive-timeout=20000 - -## Sets a time(milliseconds) that specify the most aggressive keep-alive time clients are permitted to configure. The typical default is 5 minutes -#nacos.remote.server.grpc.cluster.permit-keep-alive-time=300000 +#*************** Deployment Type Configuration ***************# -## open nacos default console ui -#nacos.console.ui.enabled=true \ No newline at end of file +### Sets the deployment type: 'merged' for joint deployment, 'server' for separate deployment server only, 'console' for separate deployment console only. +nacos.deployment.type=merged diff --git a/distribution/conf/console-guide.conf b/distribution/conf/console-guide.conf index e63b8cf46a1..9fb7f7e8fd0 100644 --- a/distribution/conf/console-guide.conf +++ b/distribution/conf/console-guide.conf @@ -1 +1 @@ -当前节点已关闭Nacos开源控制台使用,请修改application.properties中的nacos.console.ui.enabled参数为true打开开源控制台使用,详情查看文档中关于关闭默认控制台部分。 \ No newline at end of file +当前节点已关闭Nacos开源控制台使用,请修改application.properties中的nacos.console.ui.enabled参数为true打开开源控制台使用,详情查看文档中关于关闭默认控制台部分。 \ No newline at end of file diff --git a/distribution/conf/derby-schema.sql b/distribution/conf/derby-schema.sql index f4e93a3aab6..351076aa240 100644 --- a/distribution/conf/derby-schema.sql +++ b/distribution/conf/derby-schema.sql @@ -17,46 +17,49 @@ CREATE SCHEMA nacos AUTHORIZATION nacos; CREATE TABLE config_info ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128) DEFAULT NULL, - src_ip varchar(50) DEFAULT NULL, - c_desc varchar(256) DEFAULT NULL, - c_use varchar(64) DEFAULT NULL, - effect varchar(64) DEFAULT NULL, - type varchar(64) DEFAULT NULL, - c_schema LONG VARCHAR DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint configinfo_id_key PRIMARY KEY (id), - constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128) DEFAULT NULL, + src_ip varchar(50) DEFAULT NULL, + c_desc varchar(256) DEFAULT NULL, + c_use varchar(64) DEFAULT NULL, + effect varchar(64) DEFAULT NULL, + type varchar(64) DEFAULT NULL, + c_schema LONG VARCHAR DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint configinfo_id_key PRIMARY KEY (id), + constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id); CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id); CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id); CREATE TABLE his_config_info ( - id bigint NOT NULL, - nid bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - op_type char(10) DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); + id bigint NOT NULL, + nid bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + publish_type varchar(50) DEFAULT 'formal', + gray_name varchar(128) DEFAULT NULL, + ext_info CLOB, + op_type char(10) DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id); CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create); @@ -64,164 +67,175 @@ CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified); CREATE TABLE config_info_beta ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - beta_ips varchar(1024), - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint configinfobeta_id_key PRIMARY KEY (id), - constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + beta_ips varchar(1024), + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint configinfobeta_id_key PRIMARY KEY (id), + constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); CREATE TABLE config_info_tag ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - tag_id varchar(128) NOT NULL, - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - constraint configinfotag_id_key PRIMARY KEY (id), - constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); - -CREATE TABLE config_info_aggr ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - datum_id varchar(255) NOT NULL, - app_name varchar(128), - content CLOB, - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - constraint configinfoaggr_id_key PRIMARY KEY (id), - constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + tag_id varchar(128) NOT NULL, + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + constraint configinfotag_id_key PRIMARY KEY (id), + constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); + +CREATE TABLE config_info_gray ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + gray_name varchar(128) NOT NULL, + gray_rule CLOB, + app_name varchar(128), + src_ip varchar(128), + src_user varchar(128) default '', + content CLOB, + md5 varchar(32) DEFAULT NULL, + encrypted_data_key varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfogray_id_key PRIMARY KEY (id), + constraint uk_configinfogray_datagrouptenantgrayname UNIQUE (data_id,group_id,tenant_id,gray_name)); +CREATE INDEX config_info_gray_dataid_gmt_modified ON config_info_gray(data_id,gmt_modified); +CREATE INDEX config_info_gray_gmt_modified ON config_info_gray(gmt_modified); CREATE TABLE app_list ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - is_dynamic_collect_disabled smallint DEFAULT 0, - last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', - sub_info_lock_owner varchar(128), - sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', - constraint applist_id_key PRIMARY KEY (id), - constraint uk_appname UNIQUE (app_name)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + is_dynamic_collect_disabled smallint DEFAULT 0, + last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', + sub_info_lock_owner varchar(128), + sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', + constraint applist_id_key PRIMARY KEY (id), + constraint uk_appname UNIQUE (app_name)); CREATE TABLE app_configdata_relation_subs ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint configdatarelationsubs_id_key PRIMARY KEY (id), - constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationsubs_id_key PRIMARY KEY (id), + constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); CREATE TABLE app_configdata_relation_pubs ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint configdatarelationpubs_id_key PRIMARY KEY (id), - constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationpubs_id_key PRIMARY KEY (id), + constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); CREATE TABLE config_tags_relation ( - id bigint NOT NULL, - tag_name varchar(128) NOT NULL, - tag_type varchar(64) DEFAULT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) DEFAULT '', - nid bigint NOT NULL generated by default as identity, - constraint config_tags_id_key PRIMARY KEY (nid), - constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); + id bigint NOT NULL, + tag_name varchar(128) NOT NULL, + tag_type varchar(64) DEFAULT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + nid bigint NOT NULL generated by default as identity, + constraint config_tags_id_key PRIMARY KEY (nid), + constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id); CREATE TABLE group_capacity ( - id bigint NOT NULL generated by default as identity, - group_id varchar(128) DEFAULT '', - quota int DEFAULT 0, - usage int DEFAULT 0, - max_size int DEFAULT 0, - max_aggr_count int DEFAULT 0, - max_aggr_size int DEFAULT 0, - max_history_count int DEFAULT 0, - gmt_create timestamp DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint group_capacity_id_key PRIMARY KEY (id), - constraint uk_group_id UNIQUE (group_id)); + id bigint NOT NULL generated by default as identity, + group_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint group_capacity_id_key PRIMARY KEY (id), + constraint uk_group_id UNIQUE (group_id)); CREATE TABLE tenant_capacity ( - id bigint NOT NULL generated by default as identity, - tenant_id varchar(128) DEFAULT '', - quota int DEFAULT 0, - usage int DEFAULT 0, - max_size int DEFAULT 0, - max_aggr_count int DEFAULT 0, - max_aggr_size int DEFAULT 0, - max_history_count int DEFAULT 0, - gmt_create timestamp DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint tenant_capacity_id_key PRIMARY KEY (id), - constraint uk_tenant_id UNIQUE (tenant_id)); + id bigint NOT NULL generated by default as identity, + tenant_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint tenant_capacity_id_key PRIMARY KEY (id), + constraint uk_tenant_id UNIQUE (tenant_id)); CREATE TABLE tenant_info ( - id bigint NOT NULL generated by default as identity, - kp varchar(128) NOT NULL, - tenant_id varchar(128) DEFAULT '', - tenant_name varchar(128) DEFAULT '', - tenant_desc varchar(256) DEFAULT NULL, - create_source varchar(32) DEFAULT NULL, - gmt_create bigint NOT NULL, - gmt_modified bigint NOT NULL, - constraint tenant_info_id_key PRIMARY KEY (id), - constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); + id bigint NOT NULL generated by default as identity, + kp varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + tenant_name varchar(128) DEFAULT '', + tenant_desc varchar(256) DEFAULT NULL, + create_source varchar(32) DEFAULT NULL, + gmt_create bigint NOT NULL, + gmt_modified bigint NOT NULL, + constraint tenant_info_id_key PRIMARY KEY (id), + constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id); CREATE TABLE users ( - username varchar(50) NOT NULL PRIMARY KEY, - password varchar(500) NOT NULL, - enabled boolean NOT NULL DEFAULT true + username varchar(50) NOT NULL PRIMARY KEY, + password varchar(500) NOT NULL, + enabled boolean NOT NULL DEFAULT true ); CREATE TABLE roles ( - username varchar(50) NOT NULL, - role varchar(50) NOT NULL, - constraint uk_username_role UNIQUE (username,role) + username varchar(50) NOT NULL, + role varchar(50) NOT NULL, + constraint uk_username_role UNIQUE (username,role) ); CREATE TABLE permissions ( - role varchar(50) NOT NULL, - resource varchar(512) NOT NULL, - action varchar(8) NOT NULL, - constraint uk_role_permission UNIQUE (role,resource,action) + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) ); + /******************************************/ /* ipv6 support */ /******************************************/ -ALTER TABLE `config_info_tag` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `his_config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `src_user`; +ALTER TABLE config_info_tag ADD src_ip varchar(50) DEFAULT NULL; + +ALTER TABLE his_config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info_beta ADD src_ip varchar(50) DEFAULT NULL ; -ALTER TABLE `config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `config_info_beta` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; \ No newline at end of file +ALTER TABLE his_config_info ADD publish_type varchar(50) DEFAULT 'formal'; +ALTER TABLE his_config_info ADD ext_info CLOB DEFAULT NULL ; +ALTER TABLE his_config_info ADD gray_name varchar(128) DEFAULT NULL; diff --git a/distribution/conf/mysql-schema.sql b/distribution/conf/mysql-schema.sql index d3c06606e13..1f4e154a2ed 100644 --- a/distribution/conf/mysql-schema.sql +++ b/distribution/conf/mysql-schema.sql @@ -18,141 +18,151 @@ /* 表名称 = config_info */ /******************************************/ CREATE TABLE `config_info` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) DEFAULT NULL COMMENT 'group_id', - `content` longtext NOT NULL COMMENT 'content', - `md5` varchar(32) DEFAULT NULL COMMENT 'md5', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', - `src_user` text COMMENT 'source user', - `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - `c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description', - `c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage', - `effect` varchar(64) DEFAULT NULL COMMENT '配置生效的描述', - `type` varchar(64) DEFAULT NULL COMMENT '配置的类型', - `c_schema` text COMMENT '配置的模式', - `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) NOT NULL COMMENT 'data_id', + `group_id` varchar(128) DEFAULT NULL COMMENT 'group_id', + `content` longtext NOT NULL COMMENT 'content', + `md5` varchar(32) DEFAULT NULL COMMENT 'md5', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + `src_user` text COMMENT 'source user', + `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', + `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', + `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', + `c_desc` varchar(256) DEFAULT NULL COMMENT 'configuration description', + `c_use` varchar(64) DEFAULT NULL COMMENT 'configuration usage', + `effect` varchar(64) DEFAULT NULL COMMENT '配置生效的描述', + `type` varchar(64) DEFAULT NULL COMMENT '配置的类型', + `c_schema` text COMMENT '配置的模式', + `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfo_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info'; /******************************************/ -/* 表名称 = config_info_aggr */ -/******************************************/ -CREATE TABLE `config_info_aggr` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `datum_id` varchar(255) NOT NULL COMMENT 'datum_id', - `content` longtext NOT NULL COMMENT '内容', - `gmt_modified` datetime NOT NULL COMMENT '修改时间', - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfoaggr_datagrouptenantdatum` (`data_id`,`group_id`,`tenant_id`,`datum_id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='增加租户字段'; - +/* 表名称 = config_info since 2.5.0 */ +/******************************************/ +CREATE TABLE `config_info_gray` ( + `id` bigint unsigned NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) NOT NULL COMMENT 'data_id', + `group_id` varchar(128) NOT NULL COMMENT 'group_id', + `content` longtext NOT NULL COMMENT 'content', + `md5` varchar(32) DEFAULT NULL COMMENT 'md5', + `src_user` text COMMENT 'src_user', + `src_ip` varchar(100) DEFAULT NULL COMMENT 'src_ip', + `gmt_create` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_create', + `gmt_modified` datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3) COMMENT 'gmt_modified', + `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', + `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', + `gray_name` varchar(128) NOT NULL COMMENT 'gray_name', + `gray_rule` text NOT NULL COMMENT 'gray_rule', + `encrypted_data_key` varchar(256) NOT NULL DEFAULT '' COMMENT 'encrypted_data_key', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfogray_datagrouptenantgray` (`data_id`,`group_id`,`tenant_id`,`gray_name`), + KEY `idx_dataid_gmt_modified` (`data_id`,`gmt_modified`), + KEY `idx_gmt_modified` (`gmt_modified`) +) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='config_info_gray'; /******************************************/ /* 表名称 = config_info_beta */ /******************************************/ CREATE TABLE `config_info_beta` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `content` longtext NOT NULL COMMENT 'content', - `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps', - `md5` varchar(32) DEFAULT NULL COMMENT 'md5', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', - `src_user` text COMMENT 'source user', - `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) NOT NULL COMMENT 'data_id', + `group_id` varchar(128) NOT NULL COMMENT 'group_id', + `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', + `content` longtext NOT NULL COMMENT 'content', + `beta_ips` varchar(1024) DEFAULT NULL COMMENT 'betaIps', + `md5` varchar(32) DEFAULT NULL COMMENT 'md5', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + `src_user` text COMMENT 'source user', + `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', + `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', + `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfobeta_datagrouptenant` (`data_id`,`group_id`,`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_beta'; /******************************************/ /* 表名称 = config_info_tag */ /******************************************/ CREATE TABLE `config_info_tag` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', - `tag_id` varchar(128) NOT NULL COMMENT 'tag_id', - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `content` longtext NOT NULL COMMENT 'content', - `md5` varchar(32) DEFAULT NULL COMMENT 'md5', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', - `src_user` text COMMENT 'source user', - `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `data_id` varchar(255) NOT NULL COMMENT 'data_id', + `group_id` varchar(128) NOT NULL COMMENT 'group_id', + `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', + `tag_id` varchar(128) NOT NULL COMMENT 'tag_id', + `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', + `content` longtext NOT NULL COMMENT 'content', + `md5` varchar(32) DEFAULT NULL COMMENT 'md5', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + `src_user` text COMMENT 'source user', + `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_configinfotag_datagrouptenanttag` (`data_id`,`group_id`,`tenant_id`,`tag_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_info_tag'; /******************************************/ /* 表名称 = config_tags_relation */ /******************************************/ CREATE TABLE `config_tags_relation` ( - `id` bigint(20) NOT NULL COMMENT 'id', - `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', - `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', - `nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识', - PRIMARY KEY (`nid`), - UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), - KEY `idx_tenant_id` (`tenant_id`) + `id` bigint(20) NOT NULL COMMENT 'id', + `tag_name` varchar(128) NOT NULL COMMENT 'tag_name', + `tag_type` varchar(64) DEFAULT NULL COMMENT 'tag_type', + `data_id` varchar(255) NOT NULL COMMENT 'data_id', + `group_id` varchar(128) NOT NULL COMMENT 'group_id', + `tenant_id` varchar(128) DEFAULT '' COMMENT 'tenant_id', + `nid` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增长标识', + PRIMARY KEY (`nid`), + UNIQUE KEY `uk_configtagrelation_configidtag` (`id`,`tag_name`,`tag_type`), + KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='config_tag_relation'; /******************************************/ /* 表名称 = group_capacity */ /******************************************/ CREATE TABLE `group_capacity` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群', - `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', - `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', - `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', - `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值', - `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', - `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_group_id` (`group_id`) + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `group_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Group ID,空字符表示整个集群', + `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', + `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', + `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', + `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数,,0表示使用默认值', + `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', + `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_group_id` (`group_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='集群、各Group容量信息表'; /******************************************/ /* 表名称 = his_config_info */ /******************************************/ CREATE TABLE `his_config_info` ( - `id` bigint(20) unsigned NOT NULL COMMENT 'id', - `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识', - `data_id` varchar(255) NOT NULL COMMENT 'data_id', - `group_id` varchar(128) NOT NULL COMMENT 'group_id', - `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', - `content` longtext NOT NULL COMMENT 'content', - `md5` varchar(32) DEFAULT NULL COMMENT 'md5', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', - `src_user` text COMMENT 'source user', - `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', - `op_type` char(10) DEFAULT NULL COMMENT 'operation type', - `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', - `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥', - PRIMARY KEY (`nid`), - KEY `idx_gmt_create` (`gmt_create`), - KEY `idx_gmt_modified` (`gmt_modified`), - KEY `idx_did` (`data_id`) + `id` bigint(20) unsigned NOT NULL COMMENT 'id', + `nid` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'nid, 自增标识', + `data_id` varchar(255) NOT NULL COMMENT 'data_id', + `group_id` varchar(128) NOT NULL COMMENT 'group_id', + `app_name` varchar(128) DEFAULT NULL COMMENT 'app_name', + `content` longtext NOT NULL COMMENT 'content', + `md5` varchar(32) DEFAULT NULL COMMENT 'md5', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + `src_user` text COMMENT 'source user', + `src_ip` varchar(50) DEFAULT NULL COMMENT 'source ip', + `op_type` char(10) DEFAULT NULL COMMENT 'operation type', + `tenant_id` varchar(128) DEFAULT '' COMMENT '租户字段', + `encrypted_data_key` varchar(1024) NOT NULL DEFAULT '' COMMENT '密钥', + `publish_type` varchar(50) DEFAULT 'formal' COMMENT 'publish type gray or formal', + `gray_name` varchar(50) DEFAULT NULL COMMENT 'gray name', + `ext_info` longtext DEFAULT NULL COMMENT 'ext info', + PRIMARY KEY (`nid`), + KEY `idx_gmt_create` (`gmt_create`), + KEY `idx_gmt_modified` (`gmt_modified`), + KEY `idx_did` (`data_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='多租户改造'; @@ -160,51 +170,51 @@ CREATE TABLE `his_config_info` ( /* 表名称 = tenant_capacity */ /******************************************/ CREATE TABLE `tenant_capacity` ( - `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', - `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID', - `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', - `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', - `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', - `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', - `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', - `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', - `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', - `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tenant_id` (`tenant_id`) + `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID', + `tenant_id` varchar(128) NOT NULL DEFAULT '' COMMENT 'Tenant ID', + `quota` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '配额,0表示使用默认值', + `usage` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '使用量', + `max_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个配置大小上限,单位为字节,0表示使用默认值', + `max_aggr_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '聚合子配置最大个数', + `max_aggr_size` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '单个聚合数据的子配置大小上限,单位为字节,0表示使用默认值', + `max_history_count` int(10) unsigned NOT NULL DEFAULT '0' COMMENT '最大变更历史数量', + `gmt_create` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', + `gmt_modified` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='租户容量信息表'; CREATE TABLE `tenant_info` ( - `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', - `kp` varchar(128) NOT NULL COMMENT 'kp', - `tenant_id` varchar(128) default '' COMMENT 'tenant_id', - `tenant_name` varchar(128) default '' COMMENT 'tenant_name', - `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc', - `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source', - `gmt_create` bigint(20) NOT NULL COMMENT '创建时间', - `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间', - PRIMARY KEY (`id`), - UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), - KEY `idx_tenant_id` (`tenant_id`) + `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'id', + `kp` varchar(128) NOT NULL COMMENT 'kp', + `tenant_id` varchar(128) default '' COMMENT 'tenant_id', + `tenant_name` varchar(128) default '' COMMENT 'tenant_name', + `tenant_desc` varchar(256) DEFAULT NULL COMMENT 'tenant_desc', + `create_source` varchar(32) DEFAULT NULL COMMENT 'create_source', + `gmt_create` bigint(20) NOT NULL COMMENT '创建时间', + `gmt_modified` bigint(20) NOT NULL COMMENT '修改时间', + PRIMARY KEY (`id`), + UNIQUE KEY `uk_tenant_info_kptenantid` (`kp`,`tenant_id`), + KEY `idx_tenant_id` (`tenant_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin COMMENT='tenant_info'; CREATE TABLE `users` ( - `username` varchar(50) NOT NULL PRIMARY KEY COMMENT 'username', - `password` varchar(500) NOT NULL COMMENT 'password', - `enabled` boolean NOT NULL COMMENT 'enabled' + `username` varchar(50) NOT NULL PRIMARY KEY COMMENT 'username', + `password` varchar(500) NOT NULL COMMENT 'password', + `enabled` boolean NOT NULL COMMENT 'enabled' ); CREATE TABLE `roles` ( - `username` varchar(50) NOT NULL COMMENT 'username', - `role` varchar(50) NOT NULL COMMENT 'role', - UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE + `username` varchar(50) NOT NULL COMMENT 'username', + `role` varchar(50) NOT NULL COMMENT 'role', + UNIQUE INDEX `idx_user_role` (`username` ASC, `role` ASC) USING BTREE ); CREATE TABLE `permissions` ( - `role` varchar(50) NOT NULL COMMENT 'role', - `resource` varchar(128) NOT NULL COMMENT 'resource', - `action` varchar(8) NOT NULL COMMENT 'action', - UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE + `role` varchar(50) NOT NULL COMMENT 'role', + `resource` varchar(128) NOT NULL COMMENT 'resource', + `action` varchar(8) NOT NULL COMMENT 'action', + UNIQUE INDEX `uk_role_permission` (`role`,`resource`,`action`) USING BTREE ); diff --git a/distribution/conf/nacos-logback.xml b/distribution/conf/nacos-logback.xml index 784ec4c81fc..576fb6d084a 100644 --- a/distribution/conf/nacos-logback.xml +++ b/distribution/conf/nacos-logback.xml @@ -598,6 +598,23 @@ + + ${LOG_HOME}/k8s-sync-main.log + true + + ${LOG_HOME}/k8s-sync-main.log.%d{yyyy-MM-dd}.%i + 2GB + 7 + 7GB + true + + + %date %level %msg%n%n + UTF-8 + + + @@ -690,6 +707,11 @@ + + + + + diff --git a/distribution/pom.xml b/distribution/pom.xml index 0d54be0a38f..8a43818e604 100644 --- a/distribution/pom.xml +++ b/distribution/pom.xml @@ -34,77 +34,11 @@ ${project.groupId} - nacos-console + nacos-bootstrap - - release-config - - - ${project.groupId} - nacos-config - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - release-config - - single - - package - - - release-config.xml - - false - - - - - - acm - - - - release-naming - - - ${project.groupId} - nacos-naming - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - release-naming - - single - - package - - - release-naming.xml - - false - - - - - - ans - - release-address @@ -164,39 +98,6 @@ nacos-client - - release-core - - - ${project.groupId} - nacos-core - - - - - - org.apache.maven.plugins - maven-assembly-plugin - - - release-core - - single - - package - - - release-core.xml - - false - - - - - - nacos-core - - release-nacos diff --git a/distribution/release-config.xml b/distribution/release-config.xml deleted file mode 100644 index 69dd60a691e..00000000000 --- a/distribution/release-config.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - cfg - false - - dir - tar.gz - zip - - - - ../ - - README.md - - - - - - conf/** - benchmark/* - - - - - - bin/* - - 0755 - - - - - - LICENSE-BIN - LICENSE - - - NOTICE-BIN - NOTICE - - - - - - true - - com.alibaba.nacos:nacos-config - - - lib/ - false - - - lib/ - - - - - - diff --git a/distribution/release-core.xml b/distribution/release-core.xml deleted file mode 100644 index 87d0a568d43..00000000000 --- a/distribution/release-core.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - core - false - - dir - tar.gz - zip - - - - ../ - - README.md - - - - - - conf/** - - - - - - bin/* - - 0755 - - - - - - LICENSE-BIN - LICENSE - - - NOTICE-BIN - NOTICE - - - - - - true - - com.alibaba.nacos:nacos-core - - - lib/ - false - - - lib/ - - - - - - diff --git a/distribution/release-nacos.xml b/distribution/release-nacos.xml index 1c7b2a10d7c..8112b228dac 100644 --- a/distribution/release-nacos.xml +++ b/distribution/release-nacos.xml @@ -54,7 +54,7 @@ - ../console/target/nacos-server.jar + ../bootstrap/target/nacos-server.jar target/ @@ -63,7 +63,7 @@ true - com.alibaba.nacos:nacos-console + com.alibaba.nacos:nacos-bootstrap diff --git a/distribution/release-naming.xml b/distribution/release-naming.xml deleted file mode 100644 index 14558c04226..00000000000 --- a/distribution/release-naming.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - naming - false - - dir - tar.gz - zip - - - - ../ - - README.md - - - - - - conf/** - benchmark/* - - - - - - bin/* - - 0755 - - - - - - LICENSE-BIN - LICENSE - - - NOTICE-BIN - NOTICE - - - - - - true - - com.alibaba.nacos:nacos-naming - - - lib/ - false - - - lib/ - - - - - - diff --git a/istio/src/main/java/com/alibaba/nacos/istio/api/ApiConstants.java b/istio/src/main/java/com/alibaba/nacos/istio/api/ApiConstants.java index edafe3fbbfe..e1c8d4fecc3 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/api/ApiConstants.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/api/ApiConstants.java @@ -51,4 +51,9 @@ public class ApiConstants { */ public static final String CLUSTER_TYPE = API_TYPE_PREFIX + "envoy.config.cluster.v3.Cluster"; public static final String ENDPOINT_TYPE = API_TYPE_PREFIX + "envoy.config.endpoint.v3.ClusterLoadAssignment"; + + public static final String LISTENER_TYPE = API_TYPE_PREFIX + "envoy.config.listener.v3.Listener"; + + public static final String ROUTE_TYPE = API_TYPE_PREFIX + "envoy.config.route.v3.RouteConfiguration"; + } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGenerator.java index 568ee349d42..8be7aaccacc 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGenerator.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGenerator.java @@ -16,7 +16,8 @@ package com.alibaba.nacos.istio.api; -import com.alibaba.nacos.istio.common.ResourceSnapshot; +import com.alibaba.nacos.istio.model.PushRequest; +import io.envoyproxy.envoy.service.discovery.v3.Resource; import java.util.List; @@ -30,8 +31,16 @@ public interface ApiGenerator { /** * Generate data based on resource snapshot. * - * @param resourceSnapshot Resource snapshot + * @param pushRequest Push Request * @return data */ - List generate(ResourceSnapshot resourceSnapshot); + List generate(PushRequest pushRequest); + + /** + * Delta generate data based on resource snapshot. + * + * @param pushRequest Push Request + * @return data + */ + List deltaGenerate(PushRequest pushRequest); } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGeneratorFactory.java b/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGeneratorFactory.java index d54dd78a8de..7a02f74b382 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGeneratorFactory.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/api/ApiGeneratorFactory.java @@ -18,7 +18,11 @@ import com.alibaba.nacos.istio.mcp.EmptyMcpGenerator; import com.alibaba.nacos.istio.mcp.ServiceEntryMcpGenerator; +import com.alibaba.nacos.istio.xds.CdsGenerator; +import com.alibaba.nacos.istio.xds.EdsGenerator; import com.alibaba.nacos.istio.xds.EmptyXdsGenerator; +import com.alibaba.nacos.istio.xds.LdsGenerator; +import com.alibaba.nacos.istio.xds.RdsGenerator; import com.alibaba.nacos.istio.xds.ServiceEntryXdsGenerator; import org.springframework.stereotype.Component; @@ -41,6 +45,12 @@ public ApiGeneratorFactory() { apiGeneratorMap.put(SERVICE_ENTRY_PROTO_PACKAGE, ServiceEntryXdsGenerator.getInstance()); // TODO Support other api generator + //xds + apiGeneratorMap.put(CLUSTER_TYPE, CdsGenerator.getInstance()); + apiGeneratorMap.put(ENDPOINT_TYPE, EdsGenerator.getInstance()); + apiGeneratorMap.put(LISTENER_TYPE, LdsGenerator.getInstance()); + apiGeneratorMap.put(ROUTE_TYPE, RdsGenerator.getInstance()); + // mcp apiGeneratorMap.put(SERVICE_ENTRY_COLLECTION, ServiceEntryMcpGenerator.getInstance()); } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/Debounce.java b/istio/src/main/java/com/alibaba/nacos/istio/common/Debounce.java new file mode 100644 index 00000000000..a65ebf12895 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/common/Debounce.java @@ -0,0 +1,119 @@ +/* + * Copyright 1999-2022 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.istio.common; + +import com.alibaba.nacos.istio.misc.IstioConfig; +import com.alibaba.nacos.istio.model.PushRequest; + +import java.util.Date; +import java.util.Queue; +import java.util.Timer; +import java.util.TimerTask; +import java.util.concurrent.Callable; + +/**. + * @author RocketEngine26 + * @date 2022/8/20 9:05 + */ +public class Debounce implements Callable { + private Date startDebounce; + + private Date lastConfigUpdateTime; + + private final IstioConfig istioConfig; + + private final Queue pushRequestQueue; + + private PushRequest pushRequest; + + private int debouncedEvents = 0; + + private boolean free = true; + + private boolean flag = false; + + public Debounce(Queue pushRequestQueue, IstioConfig istioConfig) { + this.pushRequestQueue = pushRequestQueue; + this.istioConfig = istioConfig; + } + + @Override + public PushRequest call() throws Exception { + while (true) { + if (flag) { + return pushRequest; + } + + PushRequest otherRequest = pushRequestQueue.poll(); + + if (otherRequest != null) { + lastConfigUpdateTime = new Date(); + if (debouncedEvents == 0) { + startDebounce = lastConfigUpdateTime; + pushRequest = otherRequest; + new Timer().schedule(new TimerTask() { + @Override + public void run() { + if (free) { + try { + pushWorker(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + }, istioConfig.getDebounceAfter()); + } else { + merge(otherRequest); + } + debouncedEvents++; + } + } + } + + private void pushWorker() { + long eventDelay = System.currentTimeMillis() - startDebounce.getTime(); + long quietTime = System.currentTimeMillis() - lastConfigUpdateTime.getTime(); + + if (eventDelay > istioConfig.getDebounceMax() || quietTime > istioConfig.getDebounceAfter()) { + if (pushRequest != null) { + free = false; + flag = true; + debouncedEvents = 0; + } + } else { + new Timer().schedule(new TimerTask() { + @Override + public void run() { + if (free) { + try { + pushWorker(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + } + }, istioConfig.getDebounceAfter() - quietTime); + } + } + + private void merge(PushRequest otherRequest) { + pushRequest.getReason().addAll(otherRequest.getReason()); + pushRequest.setFull(pushRequest.isFull() || otherRequest.isFull()); + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/EventProcessor.java b/istio/src/main/java/com/alibaba/nacos/istio/common/EventProcessor.java index 5f75abdd56f..64c4e215710 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/common/EventProcessor.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/common/EventProcessor.java @@ -18,6 +18,7 @@ import com.alibaba.nacos.istio.mcp.NacosMcpService; import com.alibaba.nacos.istio.misc.Loggers; +import com.alibaba.nacos.istio.model.PushRequest; import com.alibaba.nacos.istio.util.IstioExecutor; import com.alibaba.nacos.istio.xds.NacosXdsService; import com.alibaba.nacos.sys.utils.ApplicationUtils; @@ -48,22 +49,22 @@ public class EventProcessor implements ApplicationListener events; + private final BlockingQueue requests; public EventProcessor() { - events = new ArrayBlockingQueue<>(20); + requests = new ArrayBlockingQueue<>(20); } /** * notify. * - * @param event event + * @param pushRequest push request */ - public void notify(Event event) { + public void notify(PushRequest pushRequest) { try { - events.put(event); + requests.put(pushRequest); } catch (InterruptedException e) { - Loggers.MAIN.warn("There are too many events, this event {} will be ignored.", event.getType()); + Loggers.MAIN.warn("There are too many events, this event {} will be ignored.", pushRequest.getReason()); // set the interrupted flag Thread.currentThread().interrupt(); } @@ -94,15 +95,15 @@ private class Consumer extends Thread { public void run() { Future task = null; boolean hasNewEvent = false; - Event lastEvent = null; + PushRequest lastEvent = null; while (true) { try { // Today we only care about service event, // so we simply ignore event until the last task has been completed. - Event event = events.poll(MAX_WAIT_EVENT_TIME, TimeUnit.MILLISECONDS); - if (event != null) { + PushRequest pushRequest = requests.poll(MAX_WAIT_EVENT_TIME, TimeUnit.MILLISECONDS); + if (pushRequest != null) { hasNewEvent = true; - lastEvent = event; + lastEvent = pushRequest; } if (hasClientConnection() && needNewTask(hasNewEvent, task)) { task = IstioExecutor.asyncHandleEvent(new EventHandleTask(lastEvent)); @@ -128,17 +129,19 @@ private boolean needNewTask(boolean hasNewEvent, Future task) { private class EventHandleTask implements Callable { - private final Event event; + private final PushRequest pushRequest; - EventHandleTask(Event event) { - this.event = event; + EventHandleTask(PushRequest pushRequest) { + this.pushRequest = pushRequest; } @Override public Void call() throws Exception { ResourceSnapshot snapshot = resourceManager.createResourceSnapshot(); - nacosXdsService.handleEvent(snapshot, event); - nacosMcpService.handleEvent(snapshot, event); + pushRequest.setResourceSnapshot(snapshot); + nacosXdsService.handleEvent(pushRequest); + nacosXdsService.handleDeltaEvent(pushRequest); + nacosMcpService.handleEvent(pushRequest); return null; } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/IstioConfigProcessor.java b/istio/src/main/java/com/alibaba/nacos/istio/common/IstioConfigProcessor.java new file mode 100644 index 00000000000..2c0d1e685b7 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/common/IstioConfigProcessor.java @@ -0,0 +1,137 @@ + +/* + * 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.istio.common; + +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.Subscriber; +import com.alibaba.nacos.config.server.model.event.IstioConfigChangeEvent; +import com.alibaba.nacos.istio.misc.Loggers; +import com.alibaba.nacos.istio.model.DestinationRule; +import com.alibaba.nacos.istio.model.PushRequest; +import com.alibaba.nacos.istio.model.VirtualService; +import com.alibaba.nacos.istio.xds.NacosXdsService; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; +import org.yaml.snakeyaml.Yaml; +import java.util.Map; + +/** + * Listener for IstioConfig. + * + * @author junwei + */ +@Service +@Component +public class IstioConfigProcessor { + + private NacosXdsService nacosXdsService; + + private NacosResourceManager resourceManager; + + public static final String CONFIG_REASON = "config"; + + private static final String VIRTUAL_SERVICE = "VirtualService"; + + private static final String DESTINATION_RULE = "DestinationRule"; + + private static final String API_VERSION = "networking.istio.io/v1alpha3"; + + Yaml yaml = new Yaml(); + + public IstioConfigProcessor() { + NotifyCenter.registerSubscriber(new Subscriber() { + @Override + public void onEvent(Event event) { + if (event instanceof IstioConfigChangeEvent) { + IstioConfigChangeEvent istioConfigChangeEvent = (IstioConfigChangeEvent) event; + String content = istioConfigChangeEvent.content; + if (isContentValid(content) && tryParseContent(content)) { + PushRequest pushRequest = new PushRequest(content, true); + if (null == nacosXdsService) { + nacosXdsService = ApplicationUtils.getBean(NacosXdsService.class); + } + if (null == resourceManager) { + resourceManager = ApplicationUtils.getBean(NacosResourceManager.class); + } + pushRequest.addReason(CONFIG_REASON); + ResourceSnapshot snapshot = resourceManager.createResourceSnapshot(); + pushRequest.setResourceSnapshot(snapshot); + nacosXdsService.handleConfigEvent(pushRequest); + } + } + + } + + @Override + public Class subscribeType() { + return IstioConfigChangeEvent.class; + } + }); + } + + public boolean isContentValid(String content) { + if (content == null || content.trim().isEmpty()) { + Loggers.MAIN.warn("Configuration content is null or empty."); + return false; + } + + Map obj; + try { + obj = yaml.load(content); + } catch (Exception e) { + Loggers.MAIN.error("Invalid YAML content.", e); + return false; + } + + String apiVersion = obj.containsKey("apiVersion") ? (String) obj.get("apiVersion") : ""; + String kind = obj.containsKey("kind") ? (String) obj.get("kind") : ""; + + return API_VERSION.equals(apiVersion) && (VIRTUAL_SERVICE.equals(kind) + || DESTINATION_RULE.equals(kind)) && obj.containsKey("metadata") && obj.containsKey("spec"); + } + + public boolean tryParseContent(String content) { + + if (content == null || content.trim().isEmpty()) { + Loggers.MAIN.warn("Configuration content is null or empty."); + return false; + } + + try { + Map obj = yaml.load(content); + String kind = (String) obj.get("kind"); + if (VIRTUAL_SERVICE.equals(kind)) { + VirtualService virtualService = yaml.loadAs(content, VirtualService.class); + Loggers.MAIN.info("Configuration Content was successfully parsed as VirtualService."); + } else if (DESTINATION_RULE.equals(kind)) { + DestinationRule destinationRule = yaml.loadAs(content, DestinationRule.class); + Loggers.MAIN.info("Configuration Content was successfully parsed as DestinationRule."); + } else { + Loggers.MAIN.warn("Unknown Config : Unknown 'kind' field in content: {}", kind); + return false; + } + return true; + } catch (Exception e) { + Loggers.MAIN.error("Error parsing configuration content: {}", e.getMessage(), e); + return false; + } + } + +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/NacosResourceManager.java b/istio/src/main/java/com/alibaba/nacos/istio/common/NacosResourceManager.java index ef3f2a1928d..f3127b318d8 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/common/NacosResourceManager.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/common/NacosResourceManager.java @@ -18,7 +18,6 @@ import com.alibaba.nacos.istio.misc.IstioConfig; import com.alibaba.nacos.istio.model.IstioService; -import com.alibaba.nacos.istio.util.IstioExecutor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; @@ -39,12 +38,7 @@ public class NacosResourceManager { private IstioConfig istioConfig; public NacosResourceManager() { - resourceSnapshot = new ResourceSnapshot(); - } - - public void start() { - IstioExecutor.registerNacosResourceWatcher(serviceInfoResourceWatcher, istioConfig.getMcpPushInterval() * 2L, - istioConfig.getMcpPushInterval()); + resourceSnapshot = new ResourceSnapshot(istioConfig); } public Map services() { @@ -67,9 +61,9 @@ public void initResourceSnapshot() { ResourceSnapshot resourceSnapshot = getResourceSnapshot(); resourceSnapshot.initResourceSnapshot(this); } - + public ResourceSnapshot createResourceSnapshot() { - ResourceSnapshot resourceSnapshot = new ResourceSnapshot(); + ResourceSnapshot resourceSnapshot = new ResourceSnapshot(istioConfig); resourceSnapshot.initResourceSnapshot(this); setResourceSnapshot(resourceSnapshot); return resourceSnapshot; diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/NacosServiceInfoResourceWatcher.java b/istio/src/main/java/com/alibaba/nacos/istio/common/NacosServiceInfoResourceWatcher.java index 296586d7d6b..121cebcd814 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/common/NacosServiceInfoResourceWatcher.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/common/NacosServiceInfoResourceWatcher.java @@ -17,85 +17,185 @@ package com.alibaba.nacos.istio.common; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; +import com.alibaba.nacos.common.notify.NotifyCenter; +import com.alibaba.nacos.common.notify.listener.SmartSubscriber; +import com.alibaba.nacos.istio.misc.IstioConfig; import com.alibaba.nacos.istio.model.IstioService; +import com.alibaba.nacos.istio.model.PushRequest; import com.alibaba.nacos.istio.util.IstioCrdUtil; import com.alibaba.nacos.naming.core.v2.ServiceManager; +import com.alibaba.nacos.naming.core.v2.event.client.ClientOperationEvent; +import com.alibaba.nacos.naming.core.v2.event.metadata.InfoChangeEvent; +import com.alibaba.nacos.naming.core.v2.event.publisher.NamingEventPublisherFactory; import com.alibaba.nacos.naming.core.v2.index.ServiceStorage; import com.alibaba.nacos.naming.core.v2.pojo.Service; import org.springframework.beans.factory.annotation.Autowired; import java.util.HashMap; -import java.util.HashSet; +import java.util.LinkedList; +import java.util.List; import java.util.Map; +import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; + +import static com.alibaba.nacos.istio.util.IstioExecutor.cycleDebounce; +import static com.alibaba.nacos.istio.util.IstioExecutor.debouncePushChange; /** * @author special.fy */ @org.springframework.stereotype.Service -public class NacosServiceInfoResourceWatcher implements Runnable { +public class NacosServiceInfoResourceWatcher extends SmartSubscriber { private final Map serviceInfoMap = new ConcurrentHashMap<>(16); - + + private final Queue pushRequestQueue = new ConcurrentLinkedQueue<>(); + + private boolean isInitial = true; + + @Autowired + private IstioConfig istioConfig; + @Autowired private ServiceStorage serviceStorage; @Autowired private EventProcessor eventProcessor; - + + public NacosServiceInfoResourceWatcher() { + NotifyCenter.registerSubscriber(this, NamingEventPublisherFactory.getInstance()); + } + + public Map snapshot() { + return new HashMap<>(serviceInfoMap); + } + @Override - public void run() { - boolean changed = false; - - // Query all services to see if any of them have changes. + public List> subscribeTypes() { + List> result = new LinkedList<>(); + result.add(ClientOperationEvent.ClientRegisterServiceEvent.class); + result.add(ClientOperationEvent.ClientDeregisterServiceEvent.class); + result.add(InfoChangeEvent.ServiceInfoChangeEvent.class); + result.add(InfoChangeEvent.InstanceInfoChangeEvent.class); + return result; + } + + public void onEvent(com.alibaba.nacos.common.notify.Event event) { + if (isInitial) { + init(); + isInitial = false; + cycleDebounce(new ToNotify()); + } + + if (event instanceof ClientOperationEvent.ClientRegisterServiceEvent) { + // If service changed, push to all subscribers. + ClientOperationEvent.ClientRegisterServiceEvent clientRegisterServiceEvent = (ClientOperationEvent.ClientRegisterServiceEvent) event; + Service service = clientRegisterServiceEvent.getService(); + String serviceName = IstioCrdUtil.buildServiceName(service); + + IstioService old = serviceInfoMap.get(serviceName); + PushRequest pushRequest; + + boolean full = update(serviceName, service); + if (old != null) { + pushRequest = new PushRequest(serviceName, full); + } else { + pushRequest = new PushRequest(serviceName, true); + } + pushRequestQueue.add(pushRequest); + } else if (event instanceof ClientOperationEvent.ClientDeregisterServiceEvent) { + ClientOperationEvent.ClientDeregisterServiceEvent clientDeregisterServiceEvent = (ClientOperationEvent + .ClientDeregisterServiceEvent) event; + Service service = clientDeregisterServiceEvent.getService(); + String serviceName = IstioCrdUtil.buildServiceName(service); + PushRequest pushRequest; + + boolean full = update(serviceName, service); + if (serviceStorage.getPushData(service).ipCount() <= 0) { + pushRequest = new PushRequest(serviceName, true); + serviceInfoMap.remove(serviceName); + } else { + pushRequest = new PushRequest(serviceName, full); + } + pushRequestQueue.add(pushRequest); + } else if (event instanceof InfoChangeEvent.ServiceInfoChangeEvent) { + InfoChangeEvent.ServiceInfoChangeEvent serviceInfoChangeEvent = (InfoChangeEvent.ServiceInfoChangeEvent) event; + Service service = serviceInfoChangeEvent.getService(); + String serviceName = IstioCrdUtil.buildServiceName(service); + PushRequest pushRequest = new PushRequest(serviceName, true); + + update(serviceName, service); + pushRequestQueue.add(pushRequest); + + } else if (event instanceof InfoChangeEvent.InstanceInfoChangeEvent) { + InfoChangeEvent.InstanceInfoChangeEvent instanceInfoChangeEvent = (InfoChangeEvent.InstanceInfoChangeEvent) event; + Service service = instanceInfoChangeEvent.getService(); + String serviceName = IstioCrdUtil.buildServiceName(service); + + boolean full = update(serviceName, service); + PushRequest pushRequest = new PushRequest(serviceName, full); + pushRequestQueue.add(pushRequest); + } + } + + private void init() { Set namespaces = ServiceManager.getInstance().getAllNamespaces(); - Set allServices = new HashSet<>(); for (String namespace : namespaces) { - Set services = ServiceManager.getInstance().getSingletons(namespace); + Set services = ServiceManager.getInstance().getSingletons(namespace); if (services.isEmpty()) { continue; } - + for (Service service : services) { - String serviceName = IstioCrdUtil.buildServiceNameForServiceEntry(service); - allServices.add(serviceName); - - IstioService old = serviceInfoMap.get(serviceName); - // Service not changed - if (old != null && old.getRevision().equals(service.getRevision())) { - continue; - } - - // Update the resource - changed = true; + String serviceName = IstioCrdUtil.buildServiceName(service); ServiceInfo serviceInfo = serviceStorage.getPushData(service); if (!serviceInfo.isValid()) { - serviceInfoMap.remove(serviceName); continue; } - - if (old != null) { - serviceInfoMap.put(serviceName, new IstioService(service, serviceInfo, old)); - } else { - serviceInfoMap.put(serviceName, new IstioService(service, serviceInfo)); - } + serviceInfoMap.put(serviceName, new IstioService(service, serviceInfo)); + pushRequestQueue.add(new PushRequest(serviceName, true)); } } - - for (String key : serviceInfoMap.keySet()) { - if (!allServices.contains(key)) { - changed = true; - serviceInfoMap.remove(key); - } + } + + private boolean update(String serviceName, Service service) { + ServiceInfo serviceInfo = serviceStorage.getPushData(service); + if (!serviceInfo.isValid()) { + serviceInfoMap.remove(serviceName); + return true; } - - if (changed) { - eventProcessor.notify(Event.SERVICE_UPDATE_EVENT); + + IstioService old = serviceInfoMap.get(serviceName); + if (old != null) { + serviceInfoMap.put(serviceName, new IstioService(service, serviceInfo, old)); + } else { + serviceInfoMap.put(serviceName, new IstioService(service, serviceInfo)); } + return false; } - - public Map snapshot() { - return new HashMap<>(serviceInfoMap); + + private class ToNotify implements Runnable { + @Override + public void run() { + while (true) { + if (pushRequestQueue.size() > 0) { + PushRequest updatePush; + Future futureUpdate = debouncePushChange(new Debounce(pushRequestQueue, istioConfig)); + + try { + updatePush = futureUpdate.get(); + if (updatePush != null) { + eventProcessor.notify(updatePush); + } + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + } + } + } } -} +} \ No newline at end of file diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/ResourceSnapshot.java b/istio/src/main/java/com/alibaba/nacos/istio/common/ResourceSnapshot.java index 49e8c28bcd4..70cb6b61930 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/common/ResourceSnapshot.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/common/ResourceSnapshot.java @@ -16,15 +16,13 @@ package com.alibaba.nacos.istio.common; +import com.alibaba.nacos.istio.misc.IstioConfig; +import com.alibaba.nacos.istio.model.IstioResources; import com.alibaba.nacos.istio.model.IstioService; -import com.alibaba.nacos.istio.model.ServiceEntryWrapper; -import com.alibaba.nacos.istio.util.IstioCrdUtil; import java.text.SimpleDateFormat; -import java.util.ArrayList; import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicLong; /** @@ -32,16 +30,19 @@ */ public class ResourceSnapshot { private static AtomicLong versionSuffix = new AtomicLong(0); - - private final List serviceEntries; + + private final IstioResources istioResources; + + private IstioConfig istioConfig; private boolean isCompleted; private String version; - - public ResourceSnapshot() { + + public ResourceSnapshot(IstioConfig istioConfig) { isCompleted = false; - serviceEntries = new ArrayList<>(); + istioResources = new IstioResources(new ConcurrentHashMap(16)); + this.istioConfig = istioConfig; } public synchronized void initResourceSnapshot(NacosResourceManager manager) { @@ -49,7 +50,7 @@ public synchronized void initResourceSnapshot(NacosResourceManager manager) { return; } - initServiceEntry(manager); + initIstioResources(manager); generateVersion(); @@ -60,22 +61,19 @@ private void generateVersion() { String time = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX").format(new Date()); version = time + "/" + versionSuffix.getAndIncrement(); } - - private void initServiceEntry(NacosResourceManager manager) { - Map serviceInfoMap = manager.services(); - for (String serviceName : serviceInfoMap.keySet()) { - ServiceEntryWrapper serviceEntryWrapper = IstioCrdUtil.buildServiceEntry(serviceName, manager.getIstioConfig().getDomainSuffix(), serviceInfoMap.get(serviceName)); - if (serviceEntryWrapper != null) { - serviceEntries.add(serviceEntryWrapper); - } - } - + + private void initIstioResources(NacosResourceManager manager) { + istioResources.setIstioServiceMap(manager.services()); } - - public List getServiceEntries() { - return serviceEntries; + + public IstioResources getIstioResources() { + return istioResources; } - + + public IstioConfig getIstioConfig() { + return istioConfig; + } + public boolean isCompleted() { return isCompleted; } @@ -83,4 +81,4 @@ public boolean isCompleted() { public String getVersion() { return version; } -} +} \ No newline at end of file diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/WatchedStatus.java b/istio/src/main/java/com/alibaba/nacos/istio/common/WatchedStatus.java index 44a472a6e5a..02b5ed84303 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/common/WatchedStatus.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/common/WatchedStatus.java @@ -16,12 +16,19 @@ package com.alibaba.nacos.istio.common; +import java.util.HashSet; +import java.util.Set; + /** * @author special.fy */ public class WatchedStatus { private String type; + + private boolean lastAckOrNack; + + private Set lastSubscribe; private String latestVersion; @@ -70,4 +77,20 @@ public String getAckedNonce() { public void setAckedNonce(String ackedNonce) { this.ackedNonce = ackedNonce; } + + public boolean isLastAckOrNack() { + return lastAckOrNack; + } + + public void setLastAckOrNack(boolean lastAckOrNack) { + this.lastAckOrNack = lastAckOrNack; + } + + public Set getLastSubscribe() { + return lastSubscribe; + } + + public void setLastSubscribe(Set lastSubscribe) { + this.lastSubscribe = new HashSet<>(lastSubscribe); + } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/mcp/EmptyMcpGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/mcp/EmptyMcpGenerator.java index b9cd664ac5d..4c90e6e6b0a 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/mcp/EmptyMcpGenerator.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/mcp/EmptyMcpGenerator.java @@ -17,7 +17,7 @@ package com.alibaba.nacos.istio.mcp; import com.alibaba.nacos.istio.api.ApiGenerator; -import com.alibaba.nacos.istio.common.ResourceSnapshot; +import com.alibaba.nacos.istio.model.PushRequest; import istio.mcp.v1alpha1.ResourceOuterClass.Resource; import java.util.ArrayList; @@ -42,7 +42,12 @@ public static EmptyMcpGenerator getInstance() { } @Override - public List generate(ResourceSnapshot resourceSnapshot) { + public List generate(PushRequest pushRequest) { + return new ArrayList<>(); + } + + @Override + public List deltaGenerate(PushRequest pushRequest) { return new ArrayList<>(); } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/mcp/NacosMcpService.java b/istio/src/main/java/com/alibaba/nacos/istio/mcp/NacosMcpService.java index afa6cfeba4f..4f718ef7b44 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/mcp/NacosMcpService.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/mcp/NacosMcpService.java @@ -19,11 +19,10 @@ import com.alibaba.nacos.istio.api.ApiGenerator; import com.alibaba.nacos.istio.api.ApiGeneratorFactory; import com.alibaba.nacos.istio.common.AbstractConnection; -import com.alibaba.nacos.istio.common.Event; import com.alibaba.nacos.istio.common.NacosResourceManager; -import com.alibaba.nacos.istio.common.ResourceSnapshot; import com.alibaba.nacos.istio.common.WatchedStatus; import com.alibaba.nacos.istio.misc.Loggers; +import com.alibaba.nacos.istio.model.PushRequest; import com.alibaba.nacos.istio.util.NonceGenerator; import io.grpc.stub.StreamObserver; import istio.mcp.v1alpha1.Mcp; @@ -105,8 +104,10 @@ private void process(Mcp.RequestResources requestResources, AbstractConnection connection : connections.values()) { - WatchedStatus watchedStatus = connection.getWatchedStatusByType(SERVICE_ENTRY_COLLECTION); - if (watchedStatus != null) { - connection.push(serviceEntryMcpResponse, watchedStatus); - } - } - break; - default: - Loggers.MAIN.warn("Invalid event {}, ignore it.", event.getType()); + public void handleEvent(PushRequest pushRequest) { + if (connections.size() == 0) { + return; + } + + Loggers.MAIN.info("mcp: event {} trigger push.", pushRequest.getReason()); + + Mcp.Resources serviceEntryMcpResponse = buildMcpResourcesResponse(SERVICE_ENTRY_COLLECTION, pushRequest); + + for (AbstractConnection connection : connections.values()) { + WatchedStatus watchedStatus = connection.getWatchedStatusByType(SERVICE_ENTRY_COLLECTION); + if (watchedStatus != null) { + connection.push(serviceEntryMcpResponse, watchedStatus); + } } } - private Mcp.Resources buildMcpResourcesResponse(String type, ResourceSnapshot resourceSnapshot) { + private Mcp.Resources buildMcpResourcesResponse(String type, PushRequest pushRequest) { @SuppressWarnings("unchecked") ApiGenerator serviceEntryGenerator = (ApiGenerator) apiGeneratorFactory.getApiGenerator(type); - List rawResources = serviceEntryGenerator.generate(resourceSnapshot); + List rawResources = serviceEntryGenerator.generate(pushRequest); String nonce = NonceGenerator.generateNonce(); return Mcp.Resources.newBuilder() .setCollection(type) .addAllResources(rawResources) - .setSystemVersionInfo(resourceSnapshot.getVersion()) + .setSystemVersionInfo(pushRequest.getResourceSnapshot().getVersion()) .setNonce(nonce).build(); } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/mcp/ServiceEntryMcpGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/mcp/ServiceEntryMcpGenerator.java index 3cb4cca9767..160a0f5ac79 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/mcp/ServiceEntryMcpGenerator.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/mcp/ServiceEntryMcpGenerator.java @@ -18,6 +18,9 @@ import com.alibaba.nacos.istio.api.ApiGenerator; import com.alibaba.nacos.istio.common.ResourceSnapshot; +import com.alibaba.nacos.istio.misc.IstioConfig; +import com.alibaba.nacos.istio.model.IstioService; +import com.alibaba.nacos.istio.model.PushRequest; import com.alibaba.nacos.istio.model.ServiceEntryWrapper; import com.google.protobuf.Any; import istio.mcp.v1alpha1.MetadataOuterClass; @@ -26,15 +29,19 @@ import java.util.ArrayList; import java.util.List; +import java.util.Map; import static com.alibaba.nacos.istio.api.ApiConstants.SERVICE_ENTRY_PROTO; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.buildServiceEntry; /** * @author special.fy */ public class ServiceEntryMcpGenerator implements ApiGenerator { - - private volatile static ServiceEntryMcpGenerator singleton = null; + + private List serviceEntries; + + private static volatile ServiceEntryMcpGenerator singleton = null; public static ServiceEntryMcpGenerator getInstance() { if (singleton == null) { @@ -48,10 +55,23 @@ public static ServiceEntryMcpGenerator getInstance() { } @Override - public List generate(ResourceSnapshot resourceSnapshot) { + public List generate(PushRequest pushRequest) { List result = new ArrayList<>(); - - List serviceEntries = resourceSnapshot.getServiceEntries(); + serviceEntries = new ArrayList<>(16); + ResourceSnapshot resourceSnapshot = pushRequest.getResourceSnapshot(); + + IstioConfig istioConfig = resourceSnapshot.getIstioConfig(); + Map serviceInfoMap = resourceSnapshot.getIstioResources().getIstioServiceMap(); + + for (Map.Entry entry : serviceInfoMap.entrySet()) { + String serviceName = entry.getKey(); + + ServiceEntryWrapper serviceEntryWrapper = buildServiceEntry(serviceName, serviceName + istioConfig.getDomainSuffix(), serviceInfoMap.get(serviceName)); + if (serviceEntryWrapper != null) { + serviceEntries.add(serviceEntryWrapper); + } + } + for (ServiceEntryWrapper serviceEntryWrapper : serviceEntries) { MetadataOuterClass.Metadata metadata = serviceEntryWrapper.getMetadata(); ServiceEntryOuterClass.ServiceEntry serviceEntry = serviceEntryWrapper.getServiceEntry(); @@ -63,4 +83,9 @@ public List generate(ResourceSnapshot resourceSnapshot) { return result; } + + @Override + public List deltaGenerate(PushRequest pushRequest) { + return new ArrayList<>(); + } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/misc/IstioConfig.java b/istio/src/main/java/com/alibaba/nacos/istio/misc/IstioConfig.java index 433018d8eb5..68030c01a73 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/misc/IstioConfig.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/misc/IstioConfig.java @@ -32,8 +32,16 @@ public class IstioConfig { private boolean serverEnabled = false; @Value("${nacos.istio.mcp.server.port:18848}") private int serverPort = 18848; - @Value("${nacos.istio.mcp.push.interval:3000}") - private int mcpPushInterval; + + @Value("${nacos.istio.server.full:true}") + private boolean fullEnabled = true; + + @Value("${nacos.istio.debounce.max:5000}") + private long debounceMax; + + @Value("${nacos.istio.debounce.after:100}") + private long debounceAfter; + @Value("${nacos.istio.domain.suffix:nacos}") private String domainSuffix; @@ -48,9 +56,16 @@ public int getServerPort() { public String getDomainSuffix() { return domainSuffix; } - - public int getMcpPushInterval() { - return mcpPushInterval; + + public boolean isFullEnabled() { + return fullEnabled; + } + + public long getDebounceMax() { + return debounceMax; } + public long getDebounceAfter() { + return debounceAfter; + } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/model/DestinationRule.java b/istio/src/main/java/com/alibaba/nacos/istio/model/DestinationRule.java new file mode 100644 index 00000000000..30271aaef1d --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/model/DestinationRule.java @@ -0,0 +1,144 @@ +/* + * 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.istio.model; + +import java.util.List; + +public class DestinationRule { + + private String apiVersion; + + private String kind; + + private Metadata metadata; + + private Spec spec; + + public static class Metadata { + + private String name; + + private String namespace; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + } + + public static class Spec { + + private String host; + + private List subsets; + + public static class Subset { + + private String name; + + private Labels labels; + + public static class Labels { + + private String version; + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public Labels getLabels() { + return labels; + } + + public void setLabels(Labels labels) { + this.labels = labels; + } + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public List getSubsets() { + return subsets; + } + + public void setSubsets(List subsets) { + this.subsets = subsets; + } + } + + public String getApiVersion() { + return apiVersion; + } + + public void setApiVersion(String apiVersion) { + this.apiVersion = apiVersion; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + public Spec getSpec() { + return spec; + } + + public void setSpec(Spec spec) { + this.spec = spec; + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/model/IstioEndpoint.java b/istio/src/main/java/com/alibaba/nacos/istio/model/IstioEndpoint.java new file mode 100644 index 00000000000..d7ba0bcc393 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/model/IstioEndpoint.java @@ -0,0 +1,133 @@ +/* + * + * Copyright 1999-2022 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.istio.model; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.google.protobuf.UInt32Value; +import io.envoyproxy.envoy.config.core.v3.Address; +import io.envoyproxy.envoy.config.core.v3.Locality; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.endpoint.v3.Endpoint; +import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; +import org.apache.commons.lang.StringUtils; + +import java.util.Map; + +import static com.alibaba.nacos.istio.util.IstioCrdUtil.ISTIO_HOSTNAME; + +/**. + * @author RocketEngine26 + * @date 2022/8/9 10:29 + */ +public class IstioEndpoint { + private LbEndpoint lbEndpoint; + + private Instance instance; + + private Locality locality; + + private String protocol; + + private String hostName; + + private String clusterName; + + public IstioEndpoint(Instance instance) { + this.instance = instance; + this.hostName = StringUtils.isNotEmpty(instance.getMetadata().get(ISTIO_HOSTNAME)) ? instance.getMetadata().get(ISTIO_HOSTNAME) : ""; + this.clusterName = StringUtils.isNotEmpty(instance.getClusterName()) ? instance.getClusterName() : ""; + + if (StringUtils.isNotEmpty(instance.getMetadata().get("protocol"))) { + this.protocol = instance.getMetadata().get("protocol"); + + if ("triple".equals(this.protocol) || "tri".equals(this.protocol)) { + this.protocol = "grpc"; + } + } else { + this.protocol = "http"; + } + + buildLocality(); + } + + private void buildLocality() { + String region = instance.getMetadata().getOrDefault("region", ""); + String zone = instance.getMetadata().getOrDefault("zone", ""); + String subzone = instance.getMetadata().getOrDefault("subzone", ""); + + this.locality = Locality.newBuilder().setRegion(region).setZone(zone).setSubZone(subzone).build(); + } + + private LbEndpoint buildLbEndpoint() { + Address adder = Address.newBuilder().setSocketAddress(SocketAddress.newBuilder().setAddress(instance.getIp()) + .setPortValue(this.instance.getPort()).setProtocol(SocketAddress.Protocol.TCP).build()).build(); + this.lbEndpoint = LbEndpoint.newBuilder().setLoadBalancingWeight(UInt32Value.newBuilder().setValue( + (int) this.instance.getWeight())).setEndpoint(Endpoint.newBuilder().setAddress(adder).build()).build(); + + return this.lbEndpoint; + } + + public Map getLabels() { + return instance.getMetadata(); + } + + public String getAdder() { + return instance.getIp(); + } + + public LbEndpoint getLbEndpoint() { + return buildLbEndpoint(); + } + + public String getStringLocality() { + return locality.getRegion() + "." + locality.getZone() + "." + locality.getSubZone(); + } + + public Locality getLocality() { + return locality; + } + + public int getPort() { + return instance.getPort(); + } + + public String getProtocol() { + return protocol; + } + + public int getWeight() { + return (int) instance.getWeight(); + } + + public String getHostName() { + return hostName; + } + + public String getClusterName() { + return clusterName; + } + + public boolean isHealthy() { + return instance.isHealthy(); + } + + public boolean isEnabled() { + return instance.isEnabled(); + } +} \ No newline at end of file diff --git a/istio/src/main/java/com/alibaba/nacos/istio/model/IstioResources.java b/istio/src/main/java/com/alibaba/nacos/istio/model/IstioResources.java new file mode 100644 index 00000000000..d6343ccc434 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/model/IstioResources.java @@ -0,0 +1,42 @@ +/* + * + * Copyright 1999-2022 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.istio.model; + +import java.util.Map; + +/**. + * @author RocketEngine26 + * @date 2022/8/9 16:26 + */ +public class IstioResources { + //TODO: crd + private Map istioServiceMap; + + public IstioResources(Map istioServiceMap) { + this.istioServiceMap = istioServiceMap; + } + + public Map getIstioServiceMap() { + return istioServiceMap; + } + + public void setIstioServiceMap(Map istioServiceMap) { + this.istioServiceMap = istioServiceMap; + } +} \ No newline at end of file diff --git a/istio/src/main/java/com/alibaba/nacos/istio/model/IstioService.java b/istio/src/main/java/com/alibaba/nacos/istio/model/IstioService.java index be2a67d3ec2..cc10e670caf 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/model/IstioService.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/model/IstioService.java @@ -28,19 +28,23 @@ * @author special.fy */ public class IstioService { - - private String name; - - private String groupName; - - private String namespace; - - private Long revision; - - private List hosts; - - private Date createTimeStamp; - + + private final String name; + + private final String groupName; + + private final String namespace; + + private final Long revision; + + private int port = 0; + + private String protocol; + + private final List hosts; + + private final Date createTimeStamp; + public IstioService(Service service, ServiceInfo serviceInfo) { this.name = serviceInfo.getName(); this.groupName = serviceInfo.getGroupName(); @@ -49,7 +53,7 @@ public IstioService(Service service, ServiceInfo serviceInfo) { // Record the create time of service to avoid trigger istio pull push. // See https://github.com/istio/istio/pull/30684 createTimeStamp = new Date(); - + this.hosts = sanitizeServiceInfo(serviceInfo); } @@ -61,35 +65,35 @@ public IstioService(Service service, ServiceInfo serviceInfo, IstioService old) // set the create time of service as old time to avoid trigger istio pull push. // See https://github.com/istio/istio/pull/30684 createTimeStamp = old.getCreateTimeStamp(); - + this.hosts = sanitizeServiceInfo(serviceInfo); } - private List sanitizeServiceInfo(ServiceInfo serviceInfo) { - List hosts = new ArrayList<>(); + private List sanitizeServiceInfo(ServiceInfo serviceInfo) { + List hosts = new ArrayList<>(); for (Instance instance : serviceInfo.getHosts()) { if (instance.isHealthy() && instance.isEnabled()) { - hosts.add(instance); + IstioEndpoint istioEndpoint = new IstioEndpoint(instance); + if (port == 0) { + port = istioEndpoint.getPort(); + protocol = istioEndpoint.getProtocol(); + } + hosts.add(istioEndpoint); } } // Panic mode, all instances are invalid, to push all instances to istio. if (hosts.isEmpty()) { - hosts = serviceInfo.getHosts(); + for (Instance instance : serviceInfo.getHosts()) { + IstioEndpoint istioEndpoint = new IstioEndpoint(instance); + hosts.add(istioEndpoint); + } } return hosts; } - - public String getName() { - return name; - } - - public String getGroupName() { - return groupName; - } - + public String getNamespace() { return namespace; } @@ -97,12 +101,20 @@ public String getNamespace() { public Long getRevision() { return revision; } - - public List getHosts() { + + public int getPort() { + return port; + } + + public String getProtocol() { + return protocol; + } + + public List getHosts() { return hosts; } public Date getCreateTimeStamp() { return createTimeStamp; } -} +} \ No newline at end of file diff --git a/istio/src/main/java/com/alibaba/nacos/istio/model/PushRequest.java b/istio/src/main/java/com/alibaba/nacos/istio/model/PushRequest.java new file mode 100644 index 00000000000..cf26c196a94 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/model/PushRequest.java @@ -0,0 +1,89 @@ +/* + * Copyright 1999-2022 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.istio.model; + +import com.alibaba.nacos.istio.common.ResourceSnapshot; + +import java.util.HashSet; +import java.util.Set; + +/** + * @author RocketEngine26 + * @date 2022/8/21 下午1:09 + */ +public class PushRequest { + private ResourceSnapshot resourceSnapshot; + + private final Set reason = new HashSet<>(); + + private Set subscribe; + + private final Set removed = new HashSet<>(); + + private boolean full; + + public PushRequest(ResourceSnapshot snapshot, boolean full) { + this.resourceSnapshot = snapshot; + this.full = full; + } + + public PushRequest(String reason, boolean full) { + this.full = full; + this.reason.add(reason); + } + + public ResourceSnapshot getResourceSnapshot() { + return resourceSnapshot; + } + + public boolean isFull() { + return full; + } + + public void setFull(boolean full) { + this.full = full; + } + + public void setResourceSnapshot(ResourceSnapshot resourceSnapshot) { + this.resourceSnapshot = resourceSnapshot; + } + + public Set getReason() { + return reason; + } + + public void addReason(String reason) { + this.reason.add(reason); + } + + public Set getRemoved() { + return removed; + } + + public void addRemoved(String remove) { + this.removed.add(remove); + } + + public Set getSubscribe() { + return subscribe; + } + + public void setSubscribe(Set subscribe) { + this.subscribe = subscribe; + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/model/VirtualService.java b/istio/src/main/java/com/alibaba/nacos/istio/model/VirtualService.java new file mode 100644 index 00000000000..8c3855078aa --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/model/VirtualService.java @@ -0,0 +1,303 @@ +package com.alibaba.nacos.istio.model; +/* + * 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. + */ + +import java.util.List; + +public class VirtualService { + + private String apiVersion; + + private String kind; + + private Metadata metadata; + + private Spec spec; + + public VirtualService() {} + + public static class Metadata { + + private String name; + + private String namespace; + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + } + + public static class Spec { + + private List hosts; + + private List http; + + public static class Http { + + private String name; + + private List match; + + private Rewrite rewrite; + + private List route; + + private Redirect redirect; + + public static class Match { + + private Uri uri; + + public static class Uri { + + private String prefix; + + private String exact; + + private String regex; + + public String getPrefix() { + return prefix; + } + + public void setPrefix(String prefix) { + this.prefix = prefix; + } + + public String getExact() { + return exact; + } + + public void setExact(String exact) { + this.exact = exact; + } + + public String getRegex() { + return regex; + } + + public void setRegex(String regex) { + this.regex = regex; + } + } + + public Uri getUri() { + return uri; + } + + public void setUri(Uri uri) { + this.uri = uri; + } + } + + public static class Rewrite { + + private String uri; + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + } + + public static class Route { + + private Destination destination; + + public static class Destination { + + private String host; + + private String subset; + + private Port port; + + public static class Port { + + private int number; + + public int getNumber() { + return number; + } + + public void setNumber(int number) { + this.number = number; + } + } + + public Port getPort() { + return port; + } + + public void setPort(Port port) { + this.port = port; + } + + public String getHost() { + return host; + } + + public void setHost(String host) { + this.host = host; + } + + public String getSubset() { + return subset; + } + + public void setSubset(String subset) { + this.subset = subset; + } + } + + public Destination getDestination() { + return destination; + } + + public void setDestination(Destination destination) { + this.destination = destination; + } + } + + public static class Redirect { + + private String uri; + + private String authority; + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getAuthority() { + return authority; + } + + public void setAuthority(String authority) { + this.authority = authority; + } + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public List getMatch() { + return match; + } + + public void setMatch(List match) { + this.match = match; + } + + public Rewrite getRewrite() { + return rewrite; + } + + public void setRewrite(Rewrite rewrite) { + this.rewrite = rewrite; + } + + public List getRoute() { + return route; + } + + public void setRoute(List route) { + this.route = route; + } + + public Redirect getRedirect() { + return redirect; + } + + public void setRedirect(Redirect redirect) { + this.redirect = redirect; + } + } + + public List getHosts() { + return hosts; + } + + public void setHosts(List hosts) { + this.hosts = hosts; + } + + public List getHttp() { + return http; + } + + public void setHttp(List http) { + this.http = http; + } + } + + public String getApiVersion() { + return apiVersion; + } + + public void setApiVersion(String apiVersion) { + this.apiVersion = apiVersion; + } + + public String getKind() { + return kind; + } + + public void setKind(String kind) { + this.kind = kind; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } + + public Spec getSpec() { + return spec; + } + + public void setSpec(Spec spec) { + this.spec = spec; + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/server/IstioServer.java b/istio/src/main/java/com/alibaba/nacos/istio/server/IstioServer.java index 82a707c66e8..ba3a808da64 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/server/IstioServer.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/server/IstioServer.java @@ -16,7 +16,6 @@ package com.alibaba.nacos.istio.server; -import com.alibaba.nacos.istio.common.NacosResourceManager; import com.alibaba.nacos.istio.mcp.NacosMcpService; import com.alibaba.nacos.istio.misc.IstioConfig; import com.alibaba.nacos.istio.misc.Loggers; @@ -49,10 +48,7 @@ public class IstioServer { @Autowired private NacosXdsService nacosXdsService; - - @Autowired - private NacosResourceManager nacosResourceManager; - + /** * Start. * @@ -65,10 +61,9 @@ public void start() throws IOException { Loggers.MAIN.info("The Nacos Istio server is disabled."); return; } - nacosResourceManager.start(); - + Loggers.MAIN.info("Nacos Istio server, starting Nacos Istio server..."); - + server = ServerBuilder.forPort(istioConfig.getServerPort()).addService(ServerInterceptors.intercept(nacosMcpService, serverInterceptor)) .addService(ServerInterceptors.intercept(nacosXdsService, serverInterceptor)).build(); server.start(); @@ -77,9 +72,7 @@ public void start() throws IOException { @Override public void run() { - System.out.println("Stopping Nacos Istio server..."); IstioServer.this.stop(); - System.out.println("Nacos Istio server stopped..."); } }); } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/util/IstioCrdUtil.java b/istio/src/main/java/com/alibaba/nacos/istio/util/IstioCrdUtil.java index 5b809054cf4..bb2864efc72 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/util/IstioCrdUtil.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/util/IstioCrdUtil.java @@ -17,19 +17,23 @@ package com.alibaba.nacos.istio.util; import com.alibaba.nacos.api.common.Constants; -import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.istio.model.IstioEndpoint; import com.alibaba.nacos.istio.model.IstioService; import com.alibaba.nacos.istio.model.ServiceEntryWrapper; import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.google.protobuf.Timestamp; -import istio.mcp.v1alpha1.MetadataOuterClass.Metadata; +import io.envoyproxy.envoy.config.core.v3.TrafficDirection; +import istio.mcp.v1alpha1.MetadataOuterClass; import istio.networking.v1alpha3.GatewayOuterClass; -import istio.networking.v1alpha3.ServiceEntryOuterClass.ServiceEntry; +import istio.networking.v1alpha3.ServiceEntryOuterClass; +import istio.networking.v1alpha3.WorkloadEntryOuterClass; import istio.networking.v1alpha3.WorkloadEntryOuterClass.WorkloadEntry; -import org.apache.commons.lang.StringUtils; +import java.util.ArrayList; import java.util.Date; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.regex.Pattern; @@ -40,86 +44,89 @@ public class IstioCrdUtil { public static final String VALID_DEFAULT_GROUP_NAME = "DEFAULT-GROUP"; - private static final String ISTIO_HOSTNAME = "istio.hostname"; + public static final String ISTIO_HOSTNAME = "istio.hostname"; public static final String VALID_LABEL_KEY_FORMAT = "^([a-zA-Z0-9](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[-a-zA-Z0-9]*[a-zA-Z0-9])?)*/)?((?:[A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$"; + public static final String VALID_LABEL_VALUE_FORMAT = "^((?:[A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])?$"; - - public static String buildServiceNameForServiceEntry(Service service) { + + public static String buildClusterName(TrafficDirection direction, String subset, String hostName, int port) { + return direction.toString().toLowerCase() + "|" + port + "|" + subset + "|" + hostName; + } + + public static String buildServiceName(Service service) { String group = !Constants.DEFAULT_GROUP.equals(service.getGroup()) ? service.getGroup() : VALID_DEFAULT_GROUP_NAME; // DEFAULT_GROUP is invalid for istio,because the istio host only supports: [0-9],[A-Z],[a-z],-,* return service.getName() + "." + group + "." + service.getNamespace(); } - - public static ServiceEntryWrapper buildServiceEntry(String serviceName, String domainSuffix,IstioService istioService) { + + public static String parseServiceEntryNameToServiceName(String serviceEntryName, String domain) { + return serviceEntryName.substring(0, serviceEntryName.length() - domain.length() - 1); + } + + public static String parseClusterNameToServiceName(String clusterName, String domain) { + String str = clusterName.split("\\|", 4)[3]; + return str.substring(0, str.length() - domain.length() - 1); + } + + public static ServiceEntryWrapper buildServiceEntry(String serviceName, String hostName, IstioService istioService) { if (istioService.getHosts().isEmpty()) { return null; } - ServiceEntry.Builder serviceEntryBuilder = ServiceEntry - .newBuilder().setResolution(ServiceEntry.Resolution.STATIC) - .setLocation(ServiceEntry.Location.MESH_INTERNAL); + ServiceEntryOuterClass.ServiceEntry.Builder serviceEntryBuilder = ServiceEntryOuterClass.ServiceEntry + .newBuilder().setResolution(ServiceEntryOuterClass.ServiceEntry.Resolution.STATIC) + .setLocation(ServiceEntryOuterClass.ServiceEntry.Location.MESH_INTERNAL); int port = 0; String protocol = "http"; - String hostname = serviceName; - - for (Instance instance : istioService.getHosts()) { - if (port == 0) { - port = instance.getPort(); - } - - if (StringUtils.isNotEmpty(instance.getMetadata().get("protocol"))) { - protocol = instance.getMetadata().get("protocol"); - - if ("triple".equals(protocol) || "tri".equals(protocol)){ - protocol = "grpc"; - } - } - - String metaHostname = instance.getMetadata().get(ISTIO_HOSTNAME); - if (StringUtils.isNotEmpty(metaHostname)) { - hostname = metaHostname; - } - - if (!instance.isHealthy() || !instance.isEnabled()) { + List endpoints = buildWorkloadEntry(istioService.getHosts()); + + serviceEntryBuilder.addHosts(hostName).addPorts(GatewayOuterClass.Port.newBuilder().setNumber(port) + .setName(protocol).setProtocol(protocol.toUpperCase()).build()).addAllEndpoints(endpoints); + ServiceEntryOuterClass.ServiceEntry serviceEntry = serviceEntryBuilder.build(); + + Date createTimestamp = istioService.getCreateTimeStamp(); + MetadataOuterClass.Metadata metadata = MetadataOuterClass.Metadata.newBuilder() + .setName(istioService.getNamespace() + "/" + serviceName) + .putAnnotations("virtual", "1") + .putLabels("registryType", "nacos") + .setCreateTime(Timestamp.newBuilder().setSeconds(createTimestamp.getTime() / 1000).build()) + .setVersion(String.valueOf(istioService.getRevision())).build(); + + return new ServiceEntryWrapper(metadata, serviceEntry); + } + + public static List buildWorkloadEntry(List istioEndpointList) { + List result = new ArrayList<>(); + + for (IstioEndpoint istioEndpoint : istioEndpointList) { + if (!istioEndpoint.isHealthy() || !istioEndpoint.isEnabled()) { continue; } Map metadata = new HashMap<>(1 << 3); - if (StringUtils.isNotEmpty(instance.getClusterName())) { - metadata.put("cluster", instance.getClusterName()); + if (StringUtils.isNotEmpty(istioEndpoint.getClusterName())) { + metadata.put("cluster", istioEndpoint.getClusterName()); } - for (Map.Entry entry : instance.getMetadata().entrySet()){ - if (!Pattern.matches(VALID_LABEL_KEY_FORMAT, entry.getKey())){ + for (Map.Entry entry : istioEndpoint.getLabels().entrySet()) { + if (!Pattern.matches(VALID_LABEL_KEY_FORMAT, entry.getKey())) { continue; } - if (!Pattern.matches(VALID_LABEL_VALUE_FORMAT, entry.getValue())){ + if (!Pattern.matches(VALID_LABEL_VALUE_FORMAT, entry.getValue())) { continue; } - metadata.put(entry.getKey(), entry.getValue()); + metadata.put(entry.getKey().toLowerCase(), entry.getValue()); } - - WorkloadEntry workloadEntry = WorkloadEntry.newBuilder() - .setAddress(instance.getIp()).setWeight((int) instance.getWeight()) - .putAllLabels(metadata).putPorts(protocol, instance.getPort()).build(); - serviceEntryBuilder.addEndpoints(workloadEntry); + + WorkloadEntryOuterClass.WorkloadEntry workloadEntry = WorkloadEntryOuterClass.WorkloadEntry.newBuilder() + .setAddress(istioEndpoint.getAdder()).setWeight(istioEndpoint.getWeight()) + .putAllLabels(metadata).putPorts(istioEndpoint.getProtocol(), istioEndpoint.getPort()).build(); + + result.add(workloadEntry); } - - serviceEntryBuilder.addHosts(hostname + "." + domainSuffix).addPorts( - GatewayOuterClass.Port.newBuilder().setNumber(port).setName(protocol).setProtocol(protocol.toUpperCase()).build()); - ServiceEntry serviceEntry = serviceEntryBuilder.build(); - - Date createTimestamp = istioService.getCreateTimeStamp(); - Metadata metadata = Metadata.newBuilder() - .setName(istioService.getNamespace() + "/" + serviceName) - .putAnnotations("virtual", "1") - .putLabels("registryType", "nacos") - .setCreateTime(Timestamp.newBuilder().setSeconds(createTimestamp.getTime() / 1000).build()) - .setVersion(String.valueOf(istioService.getRevision())).build(); - - return new ServiceEntryWrapper(metadata, serviceEntry); + return result; } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/util/IstioExecutor.java b/istio/src/main/java/com/alibaba/nacos/istio/util/IstioExecutor.java index ee703d70fc4..b91b3c94b8a 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/util/IstioExecutor.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/util/IstioExecutor.java @@ -20,35 +20,36 @@ import com.alibaba.nacos.common.executor.NameThreadFactory; import com.alibaba.nacos.core.utils.ClassUtils; import com.alibaba.nacos.istio.IstioApp; -import com.alibaba.nacos.sys.env.EnvUtil; import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Future; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - /** * @author special.fy */ public class IstioExecutor { - - private static final ScheduledExecutorService NACOS_RESOURCE_WATCHER = ExecutorFactory.Managed - .newScheduledExecutorService(ClassUtils.getCanonicalName(IstioApp.class), - EnvUtil.getAvailableProcessors(2), - new NameThreadFactory("com.alibaba.nacos.istio.resource.watcher")); - private static final ExecutorService EVENT_HANDLE_EXECUTOR = ExecutorFactory.Managed .newSingleExecutorService(ClassUtils.getCanonicalName(IstioApp.class), - new NameThreadFactory("com.alibaba.nacos.istio.event.handle")); - - - public static void registerNacosResourceWatcher(Runnable watcher, long initialDelay, long period) { - NACOS_RESOURCE_WATCHER.scheduleAtFixedRate(watcher, initialDelay, period, TimeUnit.MILLISECONDS); - } + new NameThreadFactory("com.alibaba.nacos.istio.event.handle")); + + private static final ExecutorService PUSH_CHANGE_EXECUTOR = ExecutorFactory.Managed + .newSingleExecutorService(ClassUtils.getCanonicalName(IstioApp.class), + new NameThreadFactory("com.alibaba.nacos.istio.pushchange.debounce")); + + private static final ExecutorService CYCLE_DEBOUNCE_EXECUTOR = ExecutorFactory.Managed + .newSingleExecutorService(ClassUtils.getCanonicalName(IstioApp.class), + new NameThreadFactory("com.alibaba.nacos.istio.cycle.debounce")); public static Future asyncHandleEvent(Callable task) { return EVENT_HANDLE_EXECUTOR.submit(task); } + + public static Future debouncePushChange(Callable debounce) { + return PUSH_CHANGE_EXECUTOR.submit(debounce); + } + + public static void cycleDebounce(Runnable toNotify) { + CYCLE_DEBOUNCE_EXECUTOR.submit(toNotify); + } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/CdsGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/CdsGenerator.java new file mode 100644 index 00000000000..758fdd23a80 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/CdsGenerator.java @@ -0,0 +1,94 @@ +/* + * Copyright 1999-2022 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.istio.xds; + +import com.alibaba.nacos.istio.api.ApiGenerator; +import com.alibaba.nacos.istio.misc.IstioConfig; +import com.alibaba.nacos.istio.misc.Loggers; +import com.alibaba.nacos.istio.model.IstioService; +import com.alibaba.nacos.istio.model.PushRequest; +import com.google.protobuf.Any; +import io.envoyproxy.envoy.config.cluster.v3.Cluster; +import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource; +import io.envoyproxy.envoy.config.core.v3.ConfigSource; +import io.envoyproxy.envoy.config.core.v3.Http1ProtocolOptions; +import io.envoyproxy.envoy.config.core.v3.Http2ProtocolOptions; +import io.envoyproxy.envoy.config.core.v3.TrafficDirection; +import io.envoyproxy.envoy.service.discovery.v3.Resource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.alibaba.nacos.istio.api.ApiConstants.CLUSTER_TYPE; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.buildClusterName; +import static io.envoyproxy.envoy.config.core.v3.ApiVersion.V2_VALUE; + +/** + * @author RocketEngine26 + * @date 2022/8/17 + */ +public final class CdsGenerator implements ApiGenerator { + + private static volatile CdsGenerator singleton = null; + + public static CdsGenerator getInstance() { + if (singleton == null) { + synchronized (CdsGenerator.class) { + if (singleton == null) { + singleton = new CdsGenerator(); + } + } + } + return singleton; + } + + @Override + public List generate(PushRequest pushRequest) { + if (!pushRequest.isFull()) { + return null; + } + List result = new ArrayList<>(); + IstioConfig istioConfig = pushRequest.getResourceSnapshot().getIstioConfig(); + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources().getIstioServiceMap(); + for (Map.Entry entry : istioServiceMap.entrySet()) { + String name = buildClusterName(TrafficDirection.OUTBOUND, "", + entry.getKey() + '.' + istioConfig.getDomainSuffix(), entry.getValue().getPort()); + + Cluster.Builder cluster = Cluster.newBuilder().setName(name).setType(Cluster.DiscoveryType.EDS) + .setEdsClusterConfig(Cluster.EdsClusterConfig.newBuilder().setServiceName(name).setEdsConfig( + ConfigSource.newBuilder().setAds(AggregatedConfigSource.newBuilder()) + .setResourceApiVersionValue(V2_VALUE).build()).build()); + if ("grpc".equals(entry.getValue().getProtocol())) { + cluster.setHttp2ProtocolOptions(Http2ProtocolOptions.newBuilder().build()); + } else { + cluster.setHttpProtocolOptions(Http1ProtocolOptions.newBuilder().build()); + } + + result.add(Any.newBuilder().setValue(cluster.build().toByteString()).setTypeUrl(CLUSTER_TYPE).build()); + } + + return result; + } + + @Override + public List deltaGenerate(PushRequest pushRequest) { + Loggers.MAIN.info("Delta Cds Not supported"); + return null; + } +} \ No newline at end of file diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/DeltaConnection.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/DeltaConnection.java new file mode 100644 index 00000000000..fbf91168837 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/DeltaConnection.java @@ -0,0 +1,58 @@ +/* + * Copyright 1999-2022 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.istio.xds; + +import com.alibaba.nacos.istio.common.AbstractConnection; +import com.alibaba.nacos.istio.common.WatchedStatus; +import com.alibaba.nacos.istio.misc.Loggers; +import io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryResponse; +import io.grpc.stub.StreamObserver; + +/** + * @author RocketEngine26 + * @date 2022/8/20 下午10:46 + */ +public class DeltaConnection extends AbstractConnection { + + public DeltaConnection(StreamObserver streamObserver) { + super(streamObserver); + } + + @Override + public void push(DeltaDiscoveryResponse response, WatchedStatus watchedStatus) { + if (Loggers.MAIN.isDebugEnabled()) { + Loggers.MAIN.debug("DeltaDiscoveryResponse: {}", response.toString()); + } + + Loggers.MAIN.info("DeltaDiscoveryResponse: {}", response.toString()); + + this.streamObserver.onNext(response); + + // Update watched status + watchedStatus.setLatestVersion(response.getSystemVersionInfo()); + watchedStatus.setLatestNonce(response.getNonce()); + + Loggers.MAIN.info("delta: push, type: {}, connection-id {}, version {}, nonce {}, resource size {}.", + watchedStatus.getType(), + getConnectionId(), + response.getSystemVersionInfo(), + response.getNonce(), + response.getResourcesCount()); + } +} + diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/EdsGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/EdsGenerator.java new file mode 100644 index 00000000000..17eb9413708 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/EdsGenerator.java @@ -0,0 +1,171 @@ +/* + * Copyright 1999-2022 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.istio.xds; + +import com.alibaba.nacos.istio.api.ApiGenerator; +import com.alibaba.nacos.istio.misc.IstioConfig; +import com.alibaba.nacos.istio.model.IstioEndpoint; +import com.alibaba.nacos.istio.model.IstioService; +import com.alibaba.nacos.istio.model.PushRequest; +import com.google.protobuf.Any; +import com.google.protobuf.UInt32Value; +import io.envoyproxy.envoy.config.core.v3.TrafficDirection; +import io.envoyproxy.envoy.config.endpoint.v3.ClusterLoadAssignment; +import io.envoyproxy.envoy.config.endpoint.v3.LbEndpoint; +import io.envoyproxy.envoy.config.endpoint.v3.LocalityLbEndpoints; +import io.envoyproxy.envoy.service.discovery.v3.Resource; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.alibaba.nacos.istio.api.ApiConstants.ENDPOINT_TYPE; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.buildClusterName; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.parseClusterNameToServiceName; + +/**. + * @author RocketEngine26 + * @date 2022/7/24 15:28 + */ +public final class EdsGenerator implements ApiGenerator { + + private static volatile EdsGenerator singleton = null; + + public static EdsGenerator getInstance() { + if (singleton == null) { + synchronized (EdsGenerator.class) { + if (singleton == null) { + singleton = new EdsGenerator(); + } + } + } + return singleton; + } + + @Override + public List generate(PushRequest pushRequest) { + List result = new ArrayList<>(); + IstioConfig istioConfig = pushRequest.getResourceSnapshot().getIstioConfig(); + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources().getIstioServiceMap(); + if (pushRequest.getReason().size() != 0) { + for (String reason : pushRequest.getReason()) { + IstioService istioService = istioServiceMap.get(reason); + String name = buildClusterName(TrafficDirection.OUTBOUND, "", + reason + '.' + istioConfig.getDomainSuffix(), istioService.getPort()); + Any any = buildEndpoint(name, istioService); + if (any != null) { + result.add(any); + } + } + } else { + for (Map.Entry entry : istioServiceMap.entrySet()) { + String name = buildClusterName(TrafficDirection.OUTBOUND, "", + entry.getKey() + '.' + istioConfig.getDomainSuffix(), entry.getValue().getPort()); + Any any = buildEndpoint(name, entry.getValue()); + if (any != null) { + result.add(any); + } + } + } + return result; + } + + @Override + public List deltaGenerate(PushRequest pushRequest) { + if (pushRequest.isFull()) { + return null; + } + + List result = new ArrayList<>(); + Set reason = pushRequest.getReason(); + IstioConfig istioConfig = pushRequest.getResourceSnapshot().getIstioConfig(); + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources().getIstioServiceMap(); + + if (pushRequest.getSubscribe().size() != 0) { + for (String subscribe : pushRequest.getSubscribe()) { + String serviceName = parseClusterNameToServiceName(subscribe, istioConfig.getDomainSuffix()); + if (reason.contains(serviceName)) { + if (istioServiceMap.containsKey(serviceName)) { + Any any = buildEndpoint(subscribe, istioServiceMap.get(serviceName)); + if (any != null) { + result.add(Resource.newBuilder().setResource(any).setVersion(pushRequest.getResourceSnapshot().getVersion()).build()); + } else { + pushRequest.addRemoved(subscribe); + } + } else { + pushRequest.addRemoved(subscribe); + } + } + } + } else { + for (Map.Entry entry : istioServiceMap.entrySet()) { + String name = buildClusterName(TrafficDirection.OUTBOUND, "", + entry.getKey() + '.' + istioConfig.getDomainSuffix(), entry.getValue().getPort()); + Any any = buildEndpoint(name, entry.getValue()); + if (any != null) { + result.add(Resource.newBuilder().setResource(any).setVersion(pushRequest.getResourceSnapshot().getVersion()).build()); + } else { + pushRequest.addRemoved(name); + } + } + } + + return result; + } + + private static Any buildEndpoint(String name, IstioService istioService) { + if (istioService.getHosts().isEmpty()) { + return null; + } + + List istioEndpoints = istioService.getHosts(); + Map llbEndpointsBuilder = new HashMap<>(istioEndpoints.size()); + + for (IstioEndpoint istioEndpoint : istioEndpoints) { + String label = istioEndpoint.getStringLocality(); + LbEndpoint lbEndpoint = istioEndpoint.getLbEndpoint(); + + if (!llbEndpointsBuilder.containsKey(label)) { + LocalityLbEndpoints.Builder llbEndpointBuilder = LocalityLbEndpoints.newBuilder() + .setLocality(istioEndpoint.getLocality()).addLbEndpoints(lbEndpoint); + llbEndpointsBuilder.put(label, llbEndpointBuilder); + } else { + llbEndpointsBuilder.get(label).addLbEndpoints(lbEndpoint); + } + } + + List listlle = new ArrayList<>(); + for (LocalityLbEndpoints.Builder builder : llbEndpointsBuilder.values()) { + int weight = 0; + for (LbEndpoint lbEndpoint : builder.getLbEndpointsList()) { + weight += lbEndpoint.getLoadBalancingWeight().getValue(); + } + LocalityLbEndpoints lle = builder.setLoadBalancingWeight(UInt32Value.newBuilder().setValue(weight)).build(); + listlle.add(lle); + } + + if (listlle.size() == 0) { + return null; + } + + ClusterLoadAssignment cla = ClusterLoadAssignment.newBuilder().setClusterName(name).addAllEndpoints(listlle).build(); + return Any.newBuilder().setValue(cla.toByteString()).setTypeUrl(ENDPOINT_TYPE).build(); + } +} \ No newline at end of file diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/EmptyXdsGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/EmptyXdsGenerator.java index 275a1ea4d97..99e288c3462 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/xds/EmptyXdsGenerator.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/EmptyXdsGenerator.java @@ -17,8 +17,9 @@ package com.alibaba.nacos.istio.xds; import com.alibaba.nacos.istio.api.ApiGenerator; -import com.alibaba.nacos.istio.common.ResourceSnapshot; +import com.alibaba.nacos.istio.model.PushRequest; import com.google.protobuf.Any; +import io.envoyproxy.envoy.service.discovery.v3.Resource; import java.util.ArrayList; import java.util.List; @@ -28,7 +29,7 @@ */ public class EmptyXdsGenerator implements ApiGenerator { - private volatile static EmptyXdsGenerator singleton = null; + private static volatile EmptyXdsGenerator singleton = null; public static EmptyXdsGenerator getInstance() { if (singleton == null) { @@ -40,9 +41,14 @@ public static EmptyXdsGenerator getInstance() { } return singleton; } - + + @Override + public List generate(PushRequest pushRequest) { + return new ArrayList<>(); + } + @Override - public List generate(ResourceSnapshot resourceSnapshot) { + public List deltaGenerate(PushRequest pushRequest) { return new ArrayList<>(); } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/LdsGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/LdsGenerator.java new file mode 100644 index 00000000000..d54883e8b44 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/LdsGenerator.java @@ -0,0 +1,250 @@ +/* + * 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.istio.xds; + +import com.alibaba.nacos.istio.api.ApiGenerator; +import com.alibaba.nacos.istio.misc.Loggers; +import com.alibaba.nacos.istio.model.IstioService; +import com.alibaba.nacos.istio.model.PushRequest; +import com.google.protobuf.Any; +import com.google.protobuf.ByteString; +import io.envoyproxy.envoy.config.accesslog.v3.AccessLog; +import io.envoyproxy.envoy.config.core.v3.Address; +import io.envoyproxy.envoy.config.core.v3.AggregatedConfigSource; +import io.envoyproxy.envoy.config.core.v3.ConfigSource; +import io.envoyproxy.envoy.config.core.v3.SocketAddress; +import io.envoyproxy.envoy.config.listener.v3.Filter; +import io.envoyproxy.envoy.config.listener.v3.FilterChain; +import io.envoyproxy.envoy.config.listener.v3.Listener; +import io.envoyproxy.envoy.config.route.v3.Route; +import io.envoyproxy.envoy.config.route.v3.RouteAction; +import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; +import io.envoyproxy.envoy.config.route.v3.RouteMatch; +import io.envoyproxy.envoy.config.route.v3.VirtualHost; +import io.envoyproxy.envoy.extensions.filters.http.router.v3.Router; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.HttpFilter; +import io.envoyproxy.envoy.extensions.filters.network.http_connection_manager.v3.Rds; +import io.envoyproxy.envoy.service.discovery.v3.Resource; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import static com.alibaba.nacos.istio.api.ApiConstants.LISTENER_TYPE; +import static io.envoyproxy.envoy.config.core.v3.ApiVersion.V2_VALUE; + +/** + * LDS of XDS protocol generator. + * + * @author PoisonGravity + */ +public class LdsGenerator implements ApiGenerator { + + public static final String INIT_LISTENER = "bootstrap_listener"; + + private static final String INIT_LISTENER_NAME = "listener_0"; + + private static final String INIT_LISTENER_ADDRESS = "0.0.0.0"; + + private static final int INIT_LISTENER_PORT = 80; + + private static final String ACCESS_LOGGER_NAME = "envoy.access_loggers.stdout"; + + private static final String TYPE_URL_ACCESS_LOG = "type.googleapis.com/envoy.extensions.access_loggers.stream.v3.StdoutAccessLog"; + + private static final int DEFAULT_PORT_INCREMENT = 1; + + public static final String ROUTE_CONFIGURATION_SUFFIX = "_route_config"; + + public static final String BOOTSTRAP_UPSTREAM_CLUSTER = "nacos_xds"; + + public static final String DEFAULT_FILTER_CHAIN_NAME = "filter_chain_0"; + + public static final String DEFAULT_FILTER_TYPE = "envoy.filters.network.http_connection_manager"; + + public static final String DEFAULT_HTTPMANAGER_PREFIX = "ingress_http"; + + public static final String DEFAULT_HTTP_ROUTER_TYPE = "envoy.filters.http.router"; + + private static volatile LdsGenerator singleton = null; + + public static LdsGenerator getInstance() { + if (singleton == null) { + synchronized (LdsGenerator.class) { + if (singleton == null) { + singleton = new LdsGenerator(); + } + } + } + return singleton; + } + + @Override + public List generate(PushRequest pushRequest) { + if (!pushRequest.isFull()) { + return null; + } + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources() + .getIstioServiceMap(); + List result = new ArrayList<>(); + result.add(buildBootstrapListener()); + + if (pushRequest.isFull()) { + for (Map.Entry entry : istioServiceMap.entrySet()) { + IstioService istioService = entry.getValue(); + if (istioService != null) { + String rdsName = entry.getKey() + ROUTE_CONFIGURATION_SUFFIX; + result.add(buildDynamicListener(entry.getKey(), INIT_LISTENER_ADDRESS, istioService.getPort(), + rdsName)); + } else { + Loggers.MAIN.error("Attempt to create listener for non-existent service"); + } + } + } + return result; + } + + @Override + public List deltaGenerate(PushRequest pushRequest) { + return null; + } + + /** + * Constructs a default Envoy listener configuration with specified parameters. + * This method prepares the necessary configurations for both bootstrap and non-bootstrap scenarios. + */ + private static Any buildDefaultListener(String listenerName, String listenerAddress, int listenerPort, + String rdsName, boolean isBootstrap) { + if (isValid(listenerName, listenerPort, rdsName, isBootstrap)) { + Loggers.MAIN.error("Listener name, Listener port and RDS name cannot be null."); + return null; + } + + Listener.Builder listenerBuilder = Listener.newBuilder().setName(listenerName); + + listenerAddress = (listenerAddress == null) ? INIT_LISTENER_ADDRESS : listenerAddress; + int portValue = isBootstrap ? INIT_LISTENER_PORT : listenerPort + DEFAULT_PORT_INCREMENT; + listenerBuilder.setAddress(Address.newBuilder() + .setSocketAddress(SocketAddress.newBuilder().setAddress(listenerAddress).setPortValue(portValue))); + + String routeConfigName = + isBootstrap ? INIT_LISTENER + ROUTE_CONFIGURATION_SUFFIX : listenerName + ROUTE_CONFIGURATION_SUFFIX; + String virtualHostName = isBootstrap ? INIT_LISTENER : listenerName; + + RouteConfiguration routeConfiguration = RouteConfiguration.newBuilder().setName(routeConfigName) + .addVirtualHosts(VirtualHost.newBuilder().setName(virtualHostName).addDomains("*").addRoutes( + Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/").build()) + .setRoute(RouteAction.newBuilder().setCluster(BOOTSTRAP_UPSTREAM_CLUSTER).build()) + .build()).build()).build(); + + HttpConnectionManager httpConnectionManager = HttpConnectionManager.newBuilder() + .setStatPrefix(DEFAULT_HTTPMANAGER_PREFIX).addAccessLog(buildAccessLog()) + .setCodecType(HttpConnectionManager.CodecType.AUTO).setRouteConfig(routeConfiguration) + .addHttpFilters(createHttpFilter()).build(); + + listenerBuilder.addFilterChains(createFilterChain(httpConnectionManager)); + + Listener listener = listenerBuilder.build(); + + return Any.newBuilder().setValue(listener.toByteString()).setTypeUrl(LISTENER_TYPE).build(); + } + + /** + * Creates the initial bootstrap listener configuration. + * This is used during the startup phase of the Envoy server to construct the base configuration. + */ + private static Any buildBootstrapListener() { + return buildDefaultListener(INIT_LISTENER_NAME, INIT_LISTENER_ADDRESS, INIT_LISTENER_PORT, null, true); + } + + /** + * Constructs a default listener configuration for static environments. + * This method is designed to provide configurations for scenarios where dynamic discovery services might not be used. + */ + private static Any buildDefaultStaticListener(String listenerName, String listenerAddress, int listenerPort, + String rdsName) { + if (INIT_LISTENER.equals(listenerName)) { + return buildBootstrapListener(); + } + return buildDefaultListener(listenerName, listenerAddress, listenerPort, rdsName, false); + } + + /** + * Constructs a listener configuration for environments using dynamic service discovery. + * This method prepares the listener to utilize dynamic routing and service discovery services with rds. + */ + private static Any buildDynamicListener(String listenerName, String listenerAddress, int listenerPort, + String rdsName) { + if (INIT_LISTENER.equals(listenerName)) { + return buildBootstrapListener(); + } + + if ((listenerName == null) || (listenerPort == 0) || (rdsName == null)) { + Loggers.MAIN.error("Listener name, Listener port and RDS name cannot be null."); + return null; + } + + listenerAddress = (listenerAddress == null) ? INIT_LISTENER_ADDRESS : listenerAddress; + Listener.Builder listenerBuilder = Listener.newBuilder().setName(listenerName); + + listenerBuilder.setAddress(Address.newBuilder().setSocketAddress( + SocketAddress.newBuilder().setAddress(listenerAddress) + .setPortValue(listenerPort + DEFAULT_PORT_INCREMENT))); + ConfigSource configSource = createConfigSource(); + + Rds rds = Rds.newBuilder().setConfigSource(configSource).setRouteConfigName(rdsName).build(); + + HttpConnectionManager httpConnectionManager = HttpConnectionManager.newBuilder() + .setStatPrefix(DEFAULT_HTTPMANAGER_PREFIX).addAccessLog(buildAccessLog()) + .setCodecType(HttpConnectionManager.CodecType.AUTO).setRds(rds).addHttpFilters(createHttpFilter()) + .build(); + + listenerBuilder.addFilterChains(createFilterChain(httpConnectionManager)); + + Listener listener = listenerBuilder.build(); + + return Any.newBuilder().setValue(listener.toByteString()).setTypeUrl(LISTENER_TYPE).build(); + + } + + private static AccessLog buildAccessLog() { + return AccessLog.newBuilder().setName(ACCESS_LOGGER_NAME) + .setTypedConfig(Any.newBuilder().setTypeUrl(TYPE_URL_ACCESS_LOG).setValue(ByteString.EMPTY).build()) + .build(); + } + + private static ConfigSource createConfigSource() { + return ConfigSource.newBuilder().setAds(AggregatedConfigSource.newBuilder()) + .setResourceApiVersionValue(V2_VALUE).build(); + } + + private static HttpFilter createHttpFilter() { + return HttpFilter.newBuilder().setName(DEFAULT_HTTP_ROUTER_TYPE) + .setTypedConfig(Any.pack(Router.newBuilder().build())).build(); + } + + private static FilterChain createFilterChain(HttpConnectionManager httpConnectionManager) { + return FilterChain.newBuilder().setName(DEFAULT_FILTER_CHAIN_NAME).addFilters( + Filter.newBuilder().setName(DEFAULT_FILTER_TYPE).setTypedConfig(Any.pack(httpConnectionManager))) + .build(); + } + + private static boolean isValid(String listenerName, int listenerPort, String rdsName, boolean isBootstrap) { + return !isBootstrap && ((listenerName == null) || (listenerPort == 0) || (rdsName == null)); + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/NacosXdsService.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/NacosXdsService.java index 8df95908459..1180089c0db 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/xds/NacosXdsService.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/NacosXdsService.java @@ -19,22 +19,36 @@ import com.alibaba.nacos.istio.api.ApiGenerator; import com.alibaba.nacos.istio.api.ApiGeneratorFactory; import com.alibaba.nacos.istio.common.*; +import com.alibaba.nacos.istio.misc.IstioConfig; import com.alibaba.nacos.istio.misc.Loggers; +import com.alibaba.nacos.istio.model.PushRequest; import com.alibaba.nacos.istio.util.NonceGenerator; import com.google.protobuf.Any; import io.envoyproxy.envoy.service.discovery.v3.AggregatedDiscoveryServiceGrpc; +import io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryRequest; +import io.envoyproxy.envoy.service.discovery.v3.DeltaDiscoveryResponse; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryRequest; import io.envoyproxy.envoy.service.discovery.v3.DiscoveryResponse; +import io.envoyproxy.envoy.service.discovery.v3.Resource; import io.grpc.stub.StreamObserver; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import static com.alibaba.nacos.istio.api.ApiConstants.CLUSTER_TYPE; +import static com.alibaba.nacos.istio.api.ApiConstants.ENDPOINT_TYPE; +import static com.alibaba.nacos.istio.api.ApiConstants.LISTENER_TYPE; import static com.alibaba.nacos.istio.api.ApiConstants.MESH_CONFIG_PROTO_PACKAGE; +import static com.alibaba.nacos.istio.api.ApiConstants.ROUTE_TYPE; import static com.alibaba.nacos.istio.api.ApiConstants.SERVICE_ENTRY_PROTO_PACKAGE; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.parseClusterNameToServiceName; +import static com.alibaba.nacos.istio.xds.LdsGenerator.INIT_LISTENER; +import static com.alibaba.nacos.istio.xds.RdsGenerator.DEFAULT_ROUTE_CONFIGURATION; /** * @author special.fy @@ -43,11 +57,13 @@ public class NacosXdsService extends AggregatedDiscoveryServiceGrpc.AggregatedDiscoveryServiceImplBase { private final Map> connections = new ConcurrentHashMap<>(16); + + private final Map> deltaConnections = new ConcurrentHashMap<>(16); public boolean hasClientConnection() { - return connections.size() != 0; + return connections.size() != 0 || deltaConnections.size() != 0; } - + @Autowired ApiGeneratorFactory apiGeneratorFactory; @@ -100,8 +116,32 @@ public void process(DiscoveryRequest discoveryRequest, AbstractConnection resourceNames = new HashSet<>(discoveryRequest.getResourceNamesList()); + PushRequest pushRequest = new PushRequest(resourceManager.getResourceSnapshot(), true); + IstioConfig istioConfig = pushRequest.getResourceSnapshot().getIstioConfig(); + + if (discoveryRequest.getTypeUrl().equals(CLUSTER_TYPE)) { + for (String resourceName : resourceNames) { + String reason = parseClusterNameToServiceName(resourceName, istioConfig.getDomainSuffix()); + pushRequest.addReason(reason); + } + } + + if (discoveryRequest.getTypeUrl().equals(LISTENER_TYPE) && discoveryRequest.getResponseNonce().isEmpty()) { + String reason = INIT_LISTENER; + pushRequest.addReason(reason); + } + + if (discoveryRequest.getTypeUrl().equals(ROUTE_TYPE) && discoveryRequest.getResponseNonce().isEmpty()) { + String reason = DEFAULT_ROUTE_CONFIGURATION; + pushRequest.addReason(reason); + for (String resourceName : resourceNames) { + pushRequest.addReason(resourceName); + } + } + + DiscoveryResponse response = buildDiscoveryResponse(discoveryRequest.getTypeUrl(), pushRequest); connection.push(response, connection.getWatchedStatusByType(discoveryRequest.getTypeUrl())); } @@ -128,8 +168,13 @@ private boolean shouldPush(DiscoveryRequest discoveryRequest, AbstractConnection if (discoveryRequest.getResponseNonce().isEmpty()) { Loggers.MAIN.info("xds: init request, type {}, connection-id {}, version {}", type, connectionId, discoveryRequest.getVersionInfo()); + Loggers.MAIN.info("xds: content {},{}", + discoveryRequest.getTypeUrl(), discoveryRequest.getResourceNamesList()); watchedStatus = new WatchedStatus(); + watchedStatus.setType(discoveryRequest.getTypeUrl()); + Loggers.MAIN.info("watchedStatus: {}", + watchedStatus); connection.addWatchedResource(discoveryRequest.getTypeUrl(), watchedStatus); return true; @@ -139,8 +184,13 @@ private boolean shouldPush(DiscoveryRequest discoveryRequest, AbstractConnection if (watchedStatus == null) { Loggers.MAIN.info("xds: reconnect, type {}, connection-id {}, version {}, nonce {}.", type, connectionId, discoveryRequest.getVersionInfo(), discoveryRequest.getResponseNonce()); + Loggers.MAIN.info("xds: content {},{}", + discoveryRequest.getTypeUrl(), discoveryRequest.getResourceNamesList()); + watchedStatus = new WatchedStatus(); watchedStatus.setType(discoveryRequest.getTypeUrl()); + Loggers.MAIN.info("watchedStatus: {}", + watchedStatus); connection.addWatchedResource(discoveryRequest.getTypeUrl(), watchedStatus); return true; @@ -161,46 +211,236 @@ private boolean shouldPush(DiscoveryRequest discoveryRequest, AbstractConnection return false; } - public void handleEvent(ResourceSnapshot resourceSnapshot, Event event) { - switch (event.getType()) { - case Service: - if (connections.size() == 0) { - return; + public void handleEvent(PushRequest pushRequest) { + if (connections.size() == 0) { + return; + } + + for (AbstractConnection connection : connections.values()) { + //mcp + WatchedStatus watchedStatus = connection.getWatchedStatusByType(SERVICE_ENTRY_PROTO_PACKAGE); + if (watchedStatus != null) { + DiscoveryResponse serviceEntryResponse = buildDiscoveryResponse(SERVICE_ENTRY_PROTO_PACKAGE, pushRequest); + connection.push(serviceEntryResponse, watchedStatus); + } + //CDS + WatchedStatus cdsWatchedStatus = connection.getWatchedStatusByType(CLUSTER_TYPE); + if (cdsWatchedStatus != null) { + DiscoveryResponse cdsResponse = buildDiscoveryResponse(CLUSTER_TYPE, pushRequest); + if (cdsResponse != null) { + connection.push(cdsResponse, cdsWatchedStatus); } + } + //EDS + WatchedStatus edsWatchedStatus = connection.getWatchedStatusByType(ENDPOINT_TYPE); + if (edsWatchedStatus != null) { + DiscoveryResponse edsResponse = buildDiscoveryResponse(ENDPOINT_TYPE, pushRequest); + connection.push(edsResponse, edsWatchedStatus); + } + //LDS + WatchedStatus ldsWatchedStatus = connection.getWatchedStatusByType(LISTENER_TYPE); + if (ldsWatchedStatus != null) { + DiscoveryResponse ldsResponse = buildDiscoveryResponse(LISTENER_TYPE, pushRequest); + connection.push(ldsResponse, ldsWatchedStatus); + } + + } + } + + /** + * Handles events from Istio configuration changes and propagates updates to all connected clients. + * @param pushRequest pushRequest + */ + public void handleConfigEvent(PushRequest pushRequest) { + if (connections.size() == 0) { + return; + } - Loggers.MAIN.info("xds: event {} trigger push.", event.getType()); - - // Service Entry via MCP - DiscoveryResponse serviceEntryResponse = buildDiscoveryResponse(SERVICE_ENTRY_PROTO_PACKAGE, resourceSnapshot); - // TODO CDS, EDS - - for (AbstractConnection connection : connections.values()) { - // Service Entry via MCP - WatchedStatus watchedStatus = connection.getWatchedStatusByType(SERVICE_ENTRY_PROTO_PACKAGE); - if (watchedStatus != null) { - connection.push(serviceEntryResponse, watchedStatus); - } - // TODO CDS, EDS - } - break; - case Endpoint: - Loggers.MAIN.warn("Currently, endpoint event is not supported."); - break; - default: - Loggers.MAIN.warn("Invalid event {}, ignore it.", event.getType()); + for (AbstractConnection connection : connections.values()) { + //RDS + WatchedStatus watchedStatus; + watchedStatus = connection.getWatchedStatusByType(ROUTE_TYPE); + if (watchedStatus == null) { + watchedStatus = new WatchedStatus(); + watchedStatus.setType(ROUTE_TYPE); + connection.addWatchedResource(ROUTE_TYPE, watchedStatus); + } + WatchedStatus rdsWatchedStatus = connection.getWatchedStatusByType(ROUTE_TYPE); + if (rdsWatchedStatus != null) { + DiscoveryResponse rdsResponse = buildDiscoveryResponse(ROUTE_TYPE, pushRequest); + connection.push(rdsResponse, rdsWatchedStatus); + } } } - private DiscoveryResponse buildDiscoveryResponse(String type, ResourceSnapshot resourceSnapshot) { + private DiscoveryResponse buildDiscoveryResponse(String type, PushRequest pushRequest) { @SuppressWarnings("unchecked") - ApiGenerator serviceEntryGenerator = (ApiGenerator) apiGeneratorFactory.getApiGenerator(type); - List rawResources = serviceEntryGenerator.generate(resourceSnapshot); - + ApiGenerator generator = (ApiGenerator) apiGeneratorFactory.getApiGenerator(type); + List rawResources = generator.generate(pushRequest); + if (rawResources == null) { + return null; + } + String nonce = NonceGenerator.generateNonce(); return DiscoveryResponse.newBuilder() .setTypeUrl(type) .addAllResources(rawResources) - .setVersionInfo(resourceSnapshot.getVersion()) + .setVersionInfo(pushRequest.getResourceSnapshot().getVersion()) + .setNonce(nonce).build(); + } + + @Override + public StreamObserver deltaAggregatedResources(StreamObserver responseObserver) { + // Init snapshot of nacos service info. + resourceManager.initResourceSnapshot(); + AbstractConnection newConnection = new DeltaConnection(responseObserver); + + return new StreamObserver() { + private boolean initRequest = true; + + @Override + public void onNext(DeltaDiscoveryRequest deltaDiscoveryRequest) { + // init connection + if (initRequest) { + newConnection.setConnectionId(deltaDiscoveryRequest.getNode().getId()); + deltaConnections.put(newConnection.getConnectionId(), newConnection); + initRequest = false; + } + + deltaProcess(deltaDiscoveryRequest, newConnection); + } + + @Override + public void onError(Throwable throwable) { + Loggers.MAIN.error("delta xds: {} stream error.", newConnection.getConnectionId(), throwable); + clear(); + } + + @Override + public void onCompleted() { + Loggers.MAIN.info("delta xds: {} stream close.", newConnection.getConnectionId()); + responseObserver.onCompleted(); + clear(); + } + + private void clear() { + deltaConnections.remove(newConnection.getConnectionId()); + } + }; + } + + public void deltaProcess(DeltaDiscoveryRequest deltaDiscoveryRequest, AbstractConnection connection) { + if (!deltaShouldPush(deltaDiscoveryRequest, connection)) { + return; + } + ResourceSnapshot resourceSnapshot = resourceManager.getResourceSnapshot(); + PushRequest pushRequest = new PushRequest(resourceSnapshot, true); + + Set subscribe = new HashSet<>(deltaDiscoveryRequest.getResourceNamesSubscribeList()); + pushRequest.setSubscribe(subscribe); + connection.getWatchedStatusByType(deltaDiscoveryRequest.getTypeUrl()).setLastSubscribe(subscribe); + + DeltaDiscoveryResponse response = buildDeltaDiscoveryResponse(deltaDiscoveryRequest.getTypeUrl(), pushRequest); + connection.push(response, connection.getWatchedStatusByType(deltaDiscoveryRequest.getTypeUrl())); + } + + private boolean deltaShouldPush(DeltaDiscoveryRequest deltaDiscoveryRequest, AbstractConnection connection) { + String type = deltaDiscoveryRequest.getTypeUrl(); + String connectionId = connection.getConnectionId(); + + // Suitable for bug of istio + // See https://github.com/istio/istio/pull/34633 + if (type.equals(MESH_CONFIG_PROTO_PACKAGE)) { + Loggers.MAIN.info("delta xds: type {} should be ignored.", type); + return false; + } + + WatchedStatus watchedStatus; + if (deltaDiscoveryRequest.getResponseNonce().isEmpty()) { + Loggers.MAIN.info("delta xds: init request, type {}, connection-id {}", + type, connectionId); + watchedStatus = new WatchedStatus(); + watchedStatus.setType(deltaDiscoveryRequest.getTypeUrl()); + connection.addWatchedResource(deltaDiscoveryRequest.getTypeUrl(), watchedStatus); + + return true; + } + + watchedStatus = connection.getWatchedStatusByType(deltaDiscoveryRequest.getTypeUrl()); + if (watchedStatus == null) { + Loggers.MAIN.info("delta xds: reconnect, type {}, connection-id {}, nonce {}.", + type, connectionId, deltaDiscoveryRequest.getResponseNonce()); + watchedStatus = new WatchedStatus(); + watchedStatus.setType(deltaDiscoveryRequest.getTypeUrl()); + connection.addWatchedResource(deltaDiscoveryRequest.getTypeUrl(), watchedStatus); + + return true; + } + + if (deltaDiscoveryRequest.getErrorDetail().getCode() != 0) { + Loggers.MAIN.error("delta xds: ACK error, connection-id: {}, code: {}, message: {}", + connectionId, + deltaDiscoveryRequest.getErrorDetail().getCode(), + deltaDiscoveryRequest.getErrorDetail().getMessage()); + watchedStatus.setLastAckOrNack(true); + return false; + } + + if (!watchedStatus.getLatestNonce().equals(deltaDiscoveryRequest.getResponseNonce())) { + Loggers.MAIN.warn("delta xds: request dis match, type {}, connection-id {}", + deltaDiscoveryRequest.getTypeUrl(), + connection.getConnectionId()); + return false; + } + + // This request is ack, we should record version and nonce. + //TODO: setAckedVersion + watchedStatus.setAckedNonce(deltaDiscoveryRequest.getResponseNonce()); + watchedStatus.setLastSubscribe(new HashSet<>(deltaDiscoveryRequest.getResourceNamesSubscribeList())); + Loggers.MAIN.info("delta xds: ack, type {}, connection-id {}, nonce {}", type, connectionId, deltaDiscoveryRequest.getResponseNonce()); + return false; + } + + public void handleDeltaEvent(PushRequest pushRequest) { + if (deltaConnections.size() == 0) { + return; + } + pushRequest.setFull(pushRequest.getResourceSnapshot().getIstioConfig().isFullEnabled()); + for (AbstractConnection connection : deltaConnections.values()) { + WatchedStatus watchedStatus = connection.getWatchedStatusByType(SERVICE_ENTRY_PROTO_PACKAGE); + if (watchedStatus != null && watchedStatus.isLastAckOrNack()) { + pushRequest.setSubscribe(watchedStatus.getLastSubscribe()); + DeltaDiscoveryResponse serviceEntryResponse = buildDeltaDiscoveryResponse(SERVICE_ENTRY_PROTO_PACKAGE, pushRequest); + if (serviceEntryResponse != null) { + connection.push(serviceEntryResponse, watchedStatus); + } + } + + WatchedStatus edsWatchedStatus = connection.getWatchedStatusByType(ENDPOINT_TYPE); + if (edsWatchedStatus != null && edsWatchedStatus.isLastAckOrNack()) { + pushRequest.setSubscribe(edsWatchedStatus.getLastSubscribe()); + DeltaDiscoveryResponse edsResponse = buildDeltaDiscoveryResponse(ENDPOINT_TYPE, pushRequest); + if (edsResponse != null) { + connection.push(edsResponse, edsWatchedStatus); + } + } + } + } + + private DeltaDiscoveryResponse buildDeltaDiscoveryResponse(String type, PushRequest pushRequest) { + @SuppressWarnings("unchecked") + ApiGenerator generator = (ApiGenerator) apiGeneratorFactory.getApiGenerator(type); + List rawResources = generator.deltaGenerate(pushRequest); + if (rawResources == null) { + return null; + } + + String nonce = NonceGenerator.generateNonce(); + return DeltaDiscoveryResponse.newBuilder() + .setTypeUrl(type) + .addAllResources(rawResources) + .addAllRemovedResources(pushRequest.getRemoved()) + .setSystemVersionInfo(pushRequest.getResourceSnapshot().getVersion()) .setNonce(nonce).build(); } } diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/RdsGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/RdsGenerator.java new file mode 100644 index 00000000000..cfd321da526 --- /dev/null +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/RdsGenerator.java @@ -0,0 +1,241 @@ +/* + * 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.istio.xds; + +import com.alibaba.nacos.istio.api.ApiGenerator; +import com.alibaba.nacos.istio.misc.Loggers; +import com.alibaba.nacos.istio.model.IstioService; +import com.alibaba.nacos.istio.model.PushRequest; +import com.alibaba.nacos.istio.model.VirtualService; +import com.google.protobuf.Any; +import io.envoyproxy.envoy.config.core.v3.TrafficDirection; +import io.envoyproxy.envoy.config.route.v3.RedirectAction; +import io.envoyproxy.envoy.config.route.v3.Route; +import io.envoyproxy.envoy.config.route.v3.RouteAction; +import io.envoyproxy.envoy.config.route.v3.RouteConfiguration; +import io.envoyproxy.envoy.config.route.v3.RouteMatch; +import io.envoyproxy.envoy.config.route.v3.VirtualHost; +import io.envoyproxy.envoy.service.discovery.v3.Resource; +import io.envoyproxy.envoy.type.matcher.v3.RegexMatcher; +import org.yaml.snakeyaml.Yaml; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import static com.alibaba.nacos.istio.api.ApiConstants.ROUTE_TYPE; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.buildClusterName; + +/** + * Rds of Xds Generator. + * + * @author PoisonGravity + */ +public class RdsGenerator implements ApiGenerator { + + public static final String DEFAULT_ROUTE_CONFIGURATION = "default_route_configuration"; + + public static final String CONFIG_REASON = "config"; + + public static final String DOMAIN_SUFFIX = ".nacos"; + + public static final String ROUTE_CONFIGURATION_SUFFIX = "_route_config"; + + public static final String BOOTSTRAP_UPSTREAM_CLUSTER = "nacos_xds"; + + private final Yaml yaml = new Yaml(); + + private static volatile RdsGenerator singleton = null; + + public static RdsGenerator getInstance() { + if (singleton == null) { + synchronized (RdsGenerator.class) { + if (singleton == null) { + singleton = new RdsGenerator(); + } + } + } + return singleton; + } + + @Override + public List generate(PushRequest pushRequest) { + + List result = new ArrayList<>(); + Set reasons = pushRequest.getReason(); + if (reasons.contains(DEFAULT_ROUTE_CONFIGURATION)) { + reasons.stream().filter(reason -> !DEFAULT_ROUTE_CONFIGURATION.equals(reason)).forEach(reason -> { + result.add(buildDefaultRouteConfiguration(reason)); + }); + } else if (reasons.contains(CONFIG_REASON)) { + reasons.stream().filter(reason -> !CONFIG_REASON.equals(reason)).forEach(reason -> { + VirtualService vs = parseContent(reason, VirtualService.class); + result.add(generateRdsFromVirtualService(vs, pushRequest)); + }); + } else { + reasons.forEach(reason -> { + result.add(buildDefaultRouteConfiguration(reason)); + }); + } + + return result; + } + + @Override + public List deltaGenerate(PushRequest pushRequest) { + return null; + } + + private static Any buildDefaultRouteConfiguration(String routeConfigurationName) { + if (routeConfigurationName == null) { + throw new IllegalArgumentException("routeConfigurationName cannot be null"); + } + String virtualHostName = routeConfigurationName; + if (routeConfigurationName.endsWith(ROUTE_CONFIGURATION_SUFFIX)) { + virtualHostName = routeConfigurationName.substring(0, + routeConfigurationName.length() - ROUTE_CONFIGURATION_SUFFIX.length()); + } + RouteConfiguration routeConfiguration = RouteConfiguration.newBuilder().setName(routeConfigurationName) + .addVirtualHosts(VirtualHost.newBuilder().setName(virtualHostName).addDomains("*").addRoutes( + Route.newBuilder().setMatch(RouteMatch.newBuilder().setPrefix("/").build()) + .setRoute(RouteAction.newBuilder().setCluster(BOOTSTRAP_UPSTREAM_CLUSTER).build()) + .build()).build()).build(); + + return Any.newBuilder().setValue(routeConfiguration.toByteString()).setTypeUrl(ROUTE_TYPE).build(); + } + + /*** + *

generate Rds From VirtualService.

+ * @param virtualService VirtualService Parsed + * @param pushRequest PushRequest + * @return + */ + public static Any generateRdsFromVirtualService(VirtualService virtualService, PushRequest pushRequest) { + List httpRoutes = virtualService.getSpec().getHttp(); + List hosts = virtualService.getSpec().getHosts(); + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources() + .getIstioServiceMap(); + List hostnames = getMatchingHostnames(hosts, pushRequest); + String virtualHostName = virtualService.getMetadata().getName(); + for (Map.Entry entry : istioServiceMap.entrySet()) { + if (entry.getKey().contains(virtualHostName)) { + virtualHostName = buildClusterName(TrafficDirection.OUTBOUND, "", entry.getKey() + DOMAIN_SUFFIX, + entry.getValue().getPort()); + Loggers.MAIN.info("Setting virtualHostName: {}", virtualHostName); + } + } + VirtualHost.Builder virtualHostBuilder = VirtualHost.newBuilder().setName(virtualHostName) + .addAllDomains(hostnames); + + for (VirtualService.Spec.Http httpRoute : httpRoutes) { + processHttpRoute(httpRoute, virtualHostBuilder, pushRequest); + } + + RouteConfiguration routeConfiguration = RouteConfiguration.newBuilder() + .setName(virtualService.getMetadata().getName() + ROUTE_CONFIGURATION_SUFFIX) + .addVirtualHosts(virtualHostBuilder).build(); + return Any.newBuilder().setValue(routeConfiguration.toByteString()).setTypeUrl(ROUTE_TYPE).build(); + + } + + private T parseContent(String content, Class valueType) { + return yaml.loadAs(content, valueType); + } + + private static List getMatchingHostnames(List hosts, PushRequest pushRequest) { + List hostnames = new ArrayList<>(); + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources() + .getIstioServiceMap(); + for (String host : hosts) { + if ("*".equals(host)) { + hostnames.add("*"); + break; + } + for (Map.Entry entry : istioServiceMap.entrySet()) { + if (entry.getKey().contains(host)) { + String hostname = buildClusterName(TrafficDirection.OUTBOUND, "", host + DOMAIN_SUFFIX, + entry.getValue().getPort()); + Loggers.MAIN.info("Matching hostname: {}", hostname); + hostnames.add(hostname); + } + } + } + return hostnames; + } + + private static void processHttpRoute(VirtualService.Spec.Http httpRoute, VirtualHost.Builder virtualHostBuilder, + PushRequest pushRequest) { + Route.Builder routeBuilder = Route.newBuilder(); + + if (httpRoute.getName() != null) { + routeBuilder.setName(httpRoute.getName()); + } + + for (VirtualService.Spec.Http.Match match : httpRoute.getMatch()) { + RouteMatch.Builder routeMatchBuilder = RouteMatch.newBuilder(); + if (match.getUri().getPrefix() != null) { + routeMatchBuilder.setPrefix(match.getUri().getPrefix()); + } else if (match.getUri().getExact() != null) { + routeMatchBuilder.setPath(match.getUri().getExact()); + } else if (match.getUri().getRegex() != null) { + // 检查是否定义了正则表达式 + RegexMatcher regexMatcher = RegexMatcher.newBuilder().setRegex(match.getUri().getRegex()).build(); + routeMatchBuilder.setSafeRegex(regexMatcher); + } + routeBuilder.setMatch(routeMatchBuilder); + } + + if (httpRoute.getRedirect() != null) { + setRedirectAction(httpRoute.getRedirect(), routeBuilder); + } else { + setRouteAction(httpRoute, routeBuilder, pushRequest); + } + virtualHostBuilder.addRoutes(routeBuilder); + } + + private static void setRouteAction(VirtualService.Spec.Http httpRoute, Route.Builder routeBuilder, + PushRequest pushRequest) { + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources() + .getIstioServiceMap(); + RouteAction.Builder routeActionBuilder = RouteAction.newBuilder(); + String hostName = httpRoute.getRoute().get(0).getDestination().getHost(); + if (httpRoute.getRewrite() != null) { + routeActionBuilder.setPrefixRewrite(httpRoute.getRewrite().getUri()); + } + String destName = hostName; + for (Map.Entry entry : istioServiceMap.entrySet()) { + if (entry.getKey().contains(hostName)) { + destName = buildClusterName(TrafficDirection.OUTBOUND, "", entry.getKey() + DOMAIN_SUFFIX, + entry.getValue().getPort()); + Loggers.MAIN.info("Setting route action to destination: {}", destName); + } + } + routeBuilder.setRoute(routeActionBuilder.setCluster(destName)); + } + + private static void setRedirectAction(VirtualService.Spec.Http.Redirect redirect, Route.Builder routeBuilder) { + RedirectAction.Builder redirectBuilder = RedirectAction.newBuilder(); + if (redirect.getUri() != null) { + redirectBuilder.setPathRedirect(redirect.getUri()); + } + if (redirect.getAuthority() != null) { + redirectBuilder.setHostRedirect(redirect.getAuthority()); + } + routeBuilder.setRedirect(redirectBuilder); + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/xds/ServiceEntryXdsGenerator.java b/istio/src/main/java/com/alibaba/nacos/istio/xds/ServiceEntryXdsGenerator.java index cd7e0495e24..b12741dd72b 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/xds/ServiceEntryXdsGenerator.java +++ b/istio/src/main/java/com/alibaba/nacos/istio/xds/ServiceEntryXdsGenerator.java @@ -17,24 +17,33 @@ package com.alibaba.nacos.istio.xds; import com.alibaba.nacos.istio.api.ApiGenerator; -import com.alibaba.nacos.istio.common.ResourceSnapshot; +import com.alibaba.nacos.istio.misc.IstioConfig; +import com.alibaba.nacos.istio.model.IstioService; +import com.alibaba.nacos.istio.model.PushRequest; import com.alibaba.nacos.istio.model.ServiceEntryWrapper; import com.google.protobuf.Any; import istio.mcp.v1alpha1.MetadataOuterClass.Metadata; import istio.mcp.v1alpha1.ResourceOuterClass.Resource; +import istio.networking.v1alpha3.ServiceEntryOuterClass; import istio.networking.v1alpha3.ServiceEntryOuterClass.ServiceEntry; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.Set; import static com.alibaba.nacos.istio.api.ApiConstants.*; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.buildServiceEntry; +import static com.alibaba.nacos.istio.util.IstioCrdUtil.parseServiceEntryNameToServiceName; /** * @author special.fy */ public final class ServiceEntryXdsGenerator implements ApiGenerator { - private volatile static ServiceEntryXdsGenerator singleton = null; + private static volatile ServiceEntryXdsGenerator singleton = null; + + private List serviceEntries; public static ServiceEntryXdsGenerator getInstance() { if (singleton == null) { @@ -48,10 +57,20 @@ public static ServiceEntryXdsGenerator getInstance() { } @Override - public List generate(ResourceSnapshot resourceSnapshot) { + public List generate(PushRequest pushRequest) { List resources = new ArrayList<>(); - - List serviceEntries = resourceSnapshot.getServiceEntries(); + serviceEntries = new ArrayList<>(16); + IstioConfig istioConfig = pushRequest.getResourceSnapshot().getIstioConfig(); + Map serviceInfoMap = pushRequest.getResourceSnapshot().getIstioResources().getIstioServiceMap(); + + for (Map.Entry entry : serviceInfoMap.entrySet()) { + String serviceName = entry.getKey(); + + ServiceEntryWrapper serviceEntryWrapper = buildServiceEntry(serviceName, serviceName + istioConfig.getDomainSuffix(), serviceInfoMap.get(serviceName)); + if (serviceEntryWrapper != null) { + serviceEntries.add(serviceEntryWrapper); + } + } for (ServiceEntryWrapper serviceEntryWrapper : serviceEntries) { Metadata metadata = serviceEntryWrapper.getMetadata(); ServiceEntry serviceEntry = serviceEntryWrapper.getServiceEntry(); @@ -65,7 +84,60 @@ public List generate(ResourceSnapshot resourceSnapshot) { for (Resource resource : resources) { result.add(Any.newBuilder().setValue(resource.toByteString()).setTypeUrl(MCP_RESOURCE_PROTO).build()); } + + return result; + } + + @Override + public List deltaGenerate(PushRequest pushRequest) { + if (pushRequest.isFull()) { + return null; + } + + List result = new ArrayList<>(); + serviceEntries = new ArrayList<>(); + Set reason = pushRequest.getReason(); + IstioConfig istioConfig = pushRequest.getResourceSnapshot().getIstioConfig(); + Map istioServiceMap = pushRequest.getResourceSnapshot().getIstioResources().getIstioServiceMap(); + + if (pushRequest.getSubscribe().size() != 0) { + for (String subscribe : pushRequest.getSubscribe()) { + String serviceName = parseServiceEntryNameToServiceName(subscribe, istioConfig.getDomainSuffix()); + if (reason.contains(serviceName)) { + if (istioServiceMap.containsKey(serviceName)) { + ServiceEntryWrapper serviceEntryWrapper = buildServiceEntry(serviceName, subscribe, istioServiceMap.get(serviceName)); + if (serviceEntryWrapper != null) { + serviceEntries.add(serviceEntryWrapper); + } else { + pushRequest.addRemoved(subscribe); + } + } else { + pushRequest.addRemoved(subscribe); + } + } + } + } else { + for (Map.Entry entry : istioServiceMap.entrySet()) { + String hostName = entry.getKey() + "." + istioConfig.getDomainSuffix(); + ServiceEntryWrapper serviceEntryWrapper = buildServiceEntry(entry.getKey(), hostName, entry.getValue()); + if (serviceEntryWrapper != null) { + serviceEntries.add(serviceEntryWrapper); + } else { + pushRequest.addRemoved(hostName); + } + } + } + + + for (ServiceEntryWrapper serviceEntryWrapper : serviceEntries) { + ServiceEntryOuterClass.ServiceEntry serviceEntry = serviceEntryWrapper.getServiceEntry(); + + Any any = Any.newBuilder().setValue(serviceEntry.toByteString()).setTypeUrl(SERVICE_ENTRY_PROTO).build(); + result.add(io.envoyproxy.envoy.service.discovery.v3.Resource.newBuilder().setResource(any).setVersion( + pushRequest.getResourceSnapshot().getVersion()).build()); + } + return result; } } diff --git a/k8s-sync/pom.xml b/k8s-sync/pom.xml new file mode 100644 index 00000000000..10fe2050bfc --- /dev/null +++ b/k8s-sync/pom.xml @@ -0,0 +1,59 @@ + + + + + + nacos-all + com.alibaba.nacos + ${revision} + ../pom.xml + + + 4.0.0 + nacos-k8s-sync + jar + + nacos-k8s-sync ${project.version} + http://nacos.io + + + + ${project.groupId} + nacos-core + + + ${project.groupId} + nacos-naming + + + + io.kubernetes + client-java-api + + + io.kubernetes + client-java + + + org.springframework + spring-beans + 5.3.20 + compile + + + \ No newline at end of file diff --git a/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/K8sSyncConfig.java b/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/K8sSyncConfig.java new file mode 100644 index 00000000000..95fe9fe36f8 --- /dev/null +++ b/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/K8sSyncConfig.java @@ -0,0 +1,49 @@ +/* + * 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.k8s.sync; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +/** + * Configurations for k8s integration. + * + * @author EmanuelGi + */ +@Component +public class K8sSyncConfig { + @Value("${nacos.k8s.sync.enabled:false}") + private boolean enabled = false; + + @Value("${nacos.k8s.sync.outsideCluster:false}") + private boolean outsideCluster = false; + + @Value("${nacos.k8s.sync.kubeConfig:}") + private String kubeConfig; + + public boolean isEnabled() { + return enabled; + } + + public boolean isOutsideCluster() { + return outsideCluster; + } + + public String getKubeConfig() { + return kubeConfig; + } +} diff --git a/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/K8sSyncServer.java b/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/K8sSyncServer.java new file mode 100644 index 00000000000..65c40ceec45 --- /dev/null +++ b/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/K8sSyncServer.java @@ -0,0 +1,438 @@ +/* + * Copyright 1999-2022 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.k8s.sync; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.naming.core.InstanceOperatorClientImpl; +import com.alibaba.nacos.naming.core.ServiceOperatorV2Impl; +import com.alibaba.nacos.naming.core.v2.ServiceManager; +import com.alibaba.nacos.naming.core.v2.pojo.Service; +import io.kubernetes.client.informer.ResourceEventHandler; +import io.kubernetes.client.informer.SharedIndexInformer; +import io.kubernetes.client.informer.SharedInformerFactory; +import io.kubernetes.client.informer.cache.Lister; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1EndpointAddress; +import io.kubernetes.client.openapi.models.V1EndpointSubset; +import io.kubernetes.client.openapi.models.V1Endpoints; +import io.kubernetes.client.openapi.models.V1EndpointsList; +import io.kubernetes.client.openapi.models.V1Service; +import io.kubernetes.client.openapi.models.V1ServiceList; +import io.kubernetes.client.openapi.models.V1ServicePort; +import io.kubernetes.client.util.CallGeneratorParams; +import io.kubernetes.client.util.ClientBuilder; +import io.kubernetes.client.util.KubeConfig; +import okhttp3.OkHttpClient; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import java.io.FileReader; +import java.io.IOException; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +/** + * Start and stop k8s-sync. + * + * @author EmanuelGi + */ +@Component +public class K8sSyncServer { + + @Autowired + private K8sSyncConfig k8sSyncConfig; + + @Autowired + private ServiceOperatorV2Impl serviceOperatorV2; + + @Autowired + private InstanceOperatorClientImpl instanceOperatorClient; + + private SharedInformerFactory factory; + + /** + * start. + * + * @throws IOException io exception + */ + @PostConstruct + public void start() throws IOException { + if (!k8sSyncConfig.isEnabled()) { + Loggers.MAIN.info("The Nacos k8s-sync is disabled."); + return; + } + Loggers.MAIN.info("Starting Nacos k8s-sync ..."); + startInformer(); + Runtime.getRuntime().addShutdownHook(new Thread() { + @Override + public void run() { + Loggers.MAIN.info("Stopping Nacos k8s-sync ..."); + K8sSyncServer.this.stop(); + Loggers.MAIN.info("Nacos k8s-sync stopped..."); + } + }); + } + + /** + * start informer. + * + * @throws IOException io exception + */ + public void startInformer() throws IOException { + ApiClient apiClient; + CoreV1Api coreV1Api; + + if (k8sSyncConfig.isOutsideCluster()) { + apiClient = getOutsideApiClient(); + coreV1Api = new CoreV1Api(); + } else { + coreV1Api = new CoreV1Api(); + apiClient = coreV1Api.getApiClient(); + } + + OkHttpClient httpClient = apiClient.getHttpClient().newBuilder().build(); + apiClient.setHttpClient(httpClient); + + factory = new SharedInformerFactory(apiClient); + SharedIndexInformer serviceInformer = + factory.sharedIndexInformerFor( + (CallGeneratorParams params) -> { + return coreV1Api.listServiceForAllNamespacesCall( + null, + null, + null, + null, + null, + null, + params.resourceVersion, + null, + params.timeoutSeconds, + params.watch, + null); + }, + V1Service.class, + V1ServiceList.class); + + SharedIndexInformer endpointInformer = + factory.sharedIndexInformerFor( + (CallGeneratorParams params) -> { + return coreV1Api.listEndpointsForAllNamespacesCall( + null, + null, + null, + null, + null, + null, + params.resourceVersion, + null, + params.timeoutSeconds, + params.watch, + null); + }, + V1Endpoints.class, + V1EndpointsList.class); + + serviceInformer.addEventHandler( + new ResourceEventHandler() { + @Override + public void onAdd(V1Service service) { + if (service.getMetadata() == null || service.getSpec() == null) { + return; + } + String serviceName = service.getMetadata().getName(); + String namespace = service.getMetadata().getNamespace(); + List servicePorts = service.getSpec().getPorts(); + try { + registerService(namespace, serviceName, servicePorts, false, endpointInformer); + Loggers.MAIN.info("add service, namespace:" + namespace + " serviceName: " + serviceName); + } catch (Exception e) { + Loggers.MAIN.warn("add service fail, message:" + e.getMessage() + " namespace:" + + namespace + " serviceName: " + serviceName); + } + } + + @Override + public void onUpdate(V1Service oldService, V1Service newService) { + if (oldService.getMetadata() == null || oldService.getSpec() == null + || newService.getMetadata() == null || newService.getSpec() == null) { + return; + } + List oldServicePorts = oldService.getSpec().getPorts(); + String serviceName = newService.getMetadata().getName(); + String namespace = newService.getMetadata().getNamespace(); + List newServicePorts = newService.getSpec().getPorts(); + boolean portChanged = compareServicePorts(oldServicePorts, newServicePorts); + try { + registerService(namespace, serviceName, newServicePorts, portChanged, endpointInformer); + Loggers.MAIN.info("update service, namespace: " + namespace + " serviceName: " + serviceName); + } catch (Exception e) { + Loggers.MAIN.warn("update service fail, message: " + e.getMessage() + " namespace: " + + namespace + " serviceName: " + serviceName); + } + } + + @Override + public void onDelete(V1Service service, boolean deletedFinalStateUnknown) { + if (service.getMetadata() == null) { + return; + } + String serviceName = service.getMetadata().getName(); + String namespace = service.getMetadata().getNamespace(); + try { + unregisterService(namespace, serviceName); + Loggers.MAIN.info("delete service, namespace:" + namespace + " serviceName:" + serviceName); + } catch (Exception e) { + Loggers.MAIN.warn("delete service fail, message: " + e.getMessage() + + " namespace:" + namespace + " serviceName:" + serviceName); + } + } + }); + + endpointInformer.addEventHandler(new ResourceEventHandler() { + @Override + public void onAdd(V1Endpoints obj) { + if (obj.getMetadata() == null) { + return; + } + String serviceName = obj.getMetadata().getName(); + String namespace = obj.getMetadata().getNamespace(); + Set addIpSet = getIpFromEndpoints(obj); + + //TODO 因为需要指定namespace,这里servicelister需要重新new,是否可以优化,比如说作为单例的放到map中 + Lister serviceLister = new Lister<>(serviceInformer.getIndexer(), namespace); + V1Service service = serviceLister.get(serviceName); + List servicePorts = service.getSpec().getPorts(); + try { + registerInstances(addIpSet, namespace, serviceName, servicePorts); + Loggers.MAIN.info("add instances, namespace:" + namespace + " serviceName: " + serviceName); + } catch (NacosException e) { + Loggers.MAIN.warn("add instances fail, message:" + e.getMessage() + " namespace:" + namespace + ", serviceName: " + serviceName); + } + } + + @Override + public void onUpdate(V1Endpoints oldObj, V1Endpoints newObj) { + if (newObj.getMetadata() == null) { + return; + } + String serviceName = newObj.getMetadata().getName(); + String namespace = newObj.getMetadata().getNamespace(); + Lister serviceLister = new Lister<>(serviceInformer.getIndexer(), namespace); + V1Service service = serviceLister.get(serviceName); + List servicePorts = service.getSpec().getPorts(); + try { + registerService(namespace, serviceName, servicePorts, false, endpointInformer); + Loggers.MAIN.info("update instances, namespace:" + namespace + " serviceName: " + serviceName); + } catch (NacosException e) { + Loggers.MAIN.warn("update instances fail, message:" + e.getMessage() + " namespace:" + + namespace + ", serviceName: " + serviceName); + } + } + + @Override + public void onDelete(V1Endpoints obj, boolean deletedFinalStateUnknown) { + if (obj.getMetadata() == null) { + return; + } + String serviceName = obj.getMetadata().getName(); + String namespace = obj.getMetadata().getNamespace(); + Set deleteIpSet = getIpFromEndpoints(obj); + try { + List oldInstanceList = instanceOperatorClient.listAllInstances(namespace, serviceName); + unregisterInstances(deleteIpSet, namespace, serviceName, oldInstanceList); + Loggers.MAIN.info("delete instances, namespace:" + namespace + ", serviceName: " + serviceName); + } catch (NacosException e) { + Loggers.MAIN.info("delete instances fail, namespace:" + namespace + ", serviceName: " + serviceName); + } + } + }); + factory.startAllRegisteredInformers(); + } + + /** + * create instance. + * + * @param ip instance ip + * @param targetPort instance port + * @param serviceName service name + * @param port service port + * @return instance + */ + public Instance createInstance(String ip, int targetPort, String serviceName, int port) { + Instance instance = new Instance(); + instance.setIp(ip); + instance.setPort(targetPort); + instance.setClusterName(serviceName); + instance.setEphemeral(false); + instance.setHealthy(true); + instance.addMetadata("servicePort", String.valueOf(port)); + return instance; + } + + /** + * register service. + * + * @param namespace service namespace + * @param serviceName service name + * @param servicePorts service ports + * @param portChanged port is changed or not + * @throws NacosException nacos exception during registering + */ + public void registerService(String namespace, String serviceName, List servicePorts, boolean portChanged, + SharedIndexInformer endpointInformer) throws NacosException { + //TODO defaultnamespace 常量 + + Service service = Service.newService(namespace, Constants.DEFAULT_GROUP, serviceName, false); + ServiceManager.getInstance().getSingleton(service); + + //NotifyCenter.publishEvent(new NamingTraceEvent.RegisterServiceTraceEvent(System.currentTimeMillis(), + // namespace, Constants.DEFAULT_GROUP, serviceName)); + + Set oldIpSet = new HashSet<>(); + List oldInstanceList = instanceOperatorClient.listAllInstances(namespace, serviceName); + for (Instance instance:oldInstanceList) { + oldIpSet.add(instance.getIp()); + } + Lister endpointLister = new Lister<>(endpointInformer.getIndexer(), namespace); + V1Endpoints endpoints = endpointLister.get(serviceName); + Set newIpSet = getIpFromEndpoints(endpoints); + + //unregister deleted instance + Set deleteIpSet = new HashSet<>(); + deleteIpSet.addAll(oldIpSet); + deleteIpSet.removeAll(newIpSet); + unregisterInstances(deleteIpSet, namespace, serviceName, oldInstanceList); + //register added instance + Set addIpSet = new HashSet<>(); + addIpSet.addAll(newIpSet); + if (!portChanged) { + addIpSet.removeAll(oldIpSet); + } + registerInstances(addIpSet, namespace, serviceName, servicePorts); + } + + /** + * unregister service. + * + * @param namespace service namespace + * @param serviceName service name + * @throws NacosException nacos exception during unregistering + */ + public void unregisterService(String namespace, String serviceName) throws NacosException { + List instancelist = instanceOperatorClient.listAllInstances(namespace, serviceName); + for (Instance instance:instancelist) { + instanceOperatorClient.removeInstance(namespace, serviceName, instance); + } + serviceOperatorV2.delete(namespace, serviceName); + } + + /** + * register instances. + * + * @param addIpSet add ip set + * @param namespace service namespace + * @param serviceName service name + * @param servicePorts servie ports + * @throws NacosException nacos exception during registering instances + */ + public void registerInstances(Set addIpSet, String namespace, String serviceName, + List servicePorts) throws NacosException { + for (V1ServicePort servicePort:servicePorts) { + int port = servicePort.getPort(); + if (!servicePort.getTargetPort().isInteger()) { + continue; + } + int targetPort = servicePort.getTargetPort().getIntValue(); + for (String ip:addIpSet) { + Instance instance = createInstance(ip, targetPort, serviceName, port); + instanceOperatorClient.registerInstance(namespace, serviceName, instance); + } + } + //TODO:register instance后是否需要发布事件 + } + + /** + * unregister instances. + * + * @param deleteIpSet delete ip set + * @param namespace service namespace + * @param serviceName service name + * @param oldInstanceList old instance list from nacos service + */ + public void unregisterInstances(Set deleteIpSet, String namespace, String serviceName, + List oldInstanceList) { + for (Instance instance:oldInstanceList) { + if (deleteIpSet.contains(instance.getIp())) { + instanceOperatorClient.removeInstance(namespace, serviceName, instance); + } + } + } + + public Set getIpFromEndpoints(V1Endpoints endpoints) { + Set ipSet = new HashSet<>(); + List endpointSubsetList = endpoints.getSubsets(); + for (V1EndpointSubset endpointSubset:endpointSubsetList) { + for (V1EndpointAddress endpointAddress:endpointSubset.getAddresses()) { + ipSet.add(endpointAddress.getIp()); + } + } + return ipSet; + } + + /** + * compare oldServicePorts and newServicePorts. + * + * @param oldServicePorts old service ports list + * @param newServicePorts new service ports list + */ + public boolean compareServicePorts(List oldServicePorts, List newServicePorts) { + if (oldServicePorts.size() != newServicePorts.size()) { + return false; + } + return oldServicePorts.containsAll(newServicePorts) && newServicePorts.containsAll(oldServicePorts); + } + + /** + * use the Java API from an application outside a kubernetes cluster. + * you should load a kubeConfig to generate apiClient instead of getting it from coreV1api. + */ + public ApiClient getOutsideApiClient() throws IOException { + String kubeConfigPath = k8sSyncConfig.getKubeConfig(); + + // loading the out-of-cluster config, a kubeconfig from file-system + ApiClient apiClient = ClientBuilder.kubeconfig(KubeConfig.loadKubeConfig(new FileReader(kubeConfigPath))).build(); + + // set the global default api-client to the in-cluster one from above + Configuration.setDefaultApiClient(apiClient); + return apiClient; + } + + /** + * stop. + */ + public void stop() { + if (factory != null) { + factory.stopAllRegisteredInformers(); + } + } +} diff --git a/istio/src/main/java/com/alibaba/nacos/istio/common/EventType.java b/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/Loggers.java similarity index 70% rename from istio/src/main/java/com/alibaba/nacos/istio/common/EventType.java rename to k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/Loggers.java index 0062b8e72f5..b4c028d2f09 100644 --- a/istio/src/main/java/com/alibaba/nacos/istio/common/EventType.java +++ b/k8s-sync/src/main/java/com/alibaba/nacos/k8s/sync/Loggers.java @@ -14,20 +14,17 @@ * limitations under the License. */ -package com.alibaba.nacos.istio.common; +package com.alibaba.nacos.k8s.sync; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * @author special.fy + * Loggers Holder. + * + * @author EmanuelGi */ -public enum EventType { - - /** - * The service info of nacos changes. - */ - Service, - - /** - * The endpoints of service change. - */ - Endpoint; +public class Loggers { + + public static final Logger MAIN = LoggerFactory.getLogger("com.alibaba.nacos.k8s.sync.main"); } diff --git a/lock/pom.xml b/lock/pom.xml new file mode 100644 index 00000000000..edff7bdb230 --- /dev/null +++ b/lock/pom.xml @@ -0,0 +1,58 @@ + + + + + com.alibaba.nacos + nacos-all + ${revision} + ../pom.xml + + 4.0.0 + + nacos-lock + nacos-lock ${project.version} + https://nacos.io + + + + ${project.groupId} + nacos-core + + + + ${project.groupId} + nacos-api + + + org.springframework.boot + spring-boot + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-test + test + + + + diff --git a/lock/src/main/java/com/alibaba/nacos/lock/LockManager.java b/lock/src/main/java/com/alibaba/nacos/lock/LockManager.java new file mode 100644 index 00000000000..7b2e34529e1 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/LockManager.java @@ -0,0 +1,55 @@ +/* + * 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.lock; + +import com.alibaba.nacos.lock.core.reentrant.AtomicLockService; +import com.alibaba.nacos.lock.model.LockKey; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * lock manager. + * + * @author 985492783@qq.com + * @description LockManager + * @date 2023/7/10 15:10 + */ +public interface LockManager { + + /** + * get mutex lock. + * + * @param lockKey lock key + * @return AbstractAtomicLock + */ + AtomicLockService getMutexLock(LockKey lockKey); + + /** + * show all atomicLock entity to snapshot save. + * + * @return Map + */ + ConcurrentHashMap showLocks(); + + /** + * remove mutex lock. + * + * @param lockKey lock key + * @return AbstractAtomicLock + */ + AtomicLockService removeMutexLock(LockKey lockKey); +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/NacosLockManager.java b/lock/src/main/java/com/alibaba/nacos/lock/NacosLockManager.java new file mode 100644 index 00000000000..27e81f41e3a --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/NacosLockManager.java @@ -0,0 +1,80 @@ +/* + * 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.lock; + +import com.alibaba.nacos.common.spi.NacosServiceLoader; +import com.alibaba.nacos.lock.core.reentrant.AtomicLockService; +import com.alibaba.nacos.lock.exception.NacosLockException; +import com.alibaba.nacos.lock.factory.LockFactory; +import com.alibaba.nacos.lock.model.LockKey; +import org.springframework.stereotype.Service; + +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Collectors; + +/** + * nacos lock manager. + * + * @author 985492783@qq.com + * @date 2023/8/22 21:01 + */ +@Service +public class NacosLockManager implements LockManager { + + private final Map factoryMap; + + private final ConcurrentHashMap atomicLockMap = new ConcurrentHashMap<>(); + + public NacosLockManager() { + Collection factories = NacosServiceLoader.load(LockFactory.class); + factoryMap = factories.stream() + .collect(Collectors.toConcurrentMap(LockFactory::getLockType, lockFactory -> lockFactory)); + } + + @Override + public AtomicLockService getMutexLock(LockKey lockKey) { + if (lockKey == null || lockKey.getLockType() == null || lockKey.getKey() == null) { + throw new NacosLockException("lockType or lockKey is null."); + } + if (!factoryMap.containsKey(lockKey.getLockType())) { + throw new NacosLockException("lockType: " + lockKey.getLockType() + " is not exist."); + } + return atomicLockMap.computeIfAbsent(lockKey, lock -> { + LockFactory lockFactory = factoryMap.get(lock.getLockType()); + return lockFactory.createLock(lock.getKey()); + }); + } + + @Override + public ConcurrentHashMap showLocks() { + return atomicLockMap; + } + + @Override + public AtomicLockService removeMutexLock(LockKey lockKey) { + if (lockKey == null || lockKey.getLockType() == null || lockKey.getKey() == null) { + throw new NacosLockException("lockType or lockKey is null."); + } + if (!factoryMap.containsKey(lockKey.getLockType())) { + throw new NacosLockException("lockType: " + lockKey.getLockType() + " is not exist."); + } + return atomicLockMap.remove(lockKey); + } + +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/aspect/RequestLockAspect.java b/lock/src/main/java/com/alibaba/nacos/lock/aspect/RequestLockAspect.java new file mode 100644 index 00000000000..9d9a09d72e4 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/aspect/RequestLockAspect.java @@ -0,0 +1,59 @@ +/* + * 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.lock.aspect; + +import com.alibaba.nacos.api.lock.remote.request.LockOperationRequest; +import com.alibaba.nacos.api.lock.remote.response.LockOperationResponse; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.lock.monitor.LockMetricsMonitor; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.springframework.stereotype.Component; + +import java.util.concurrent.TimeUnit; + +/** + * RequestLockAspect. + * @author goumang.zh@alibaba-inc.com + */ +@Aspect +@Component +public class RequestLockAspect { + + + /** + * count metrics and get handler time. + */ + @SuppressWarnings("checkstyle:linelength") + @Around(value = "execution(* com.alibaba.nacos.core.remote.RequestHandler.handleRequest(..)) && target(com.alibaba.nacos.lock.remote.rpc.handler.LockRequestHandler) && args(request, meta)", argNames = "pjp,request,meta") + public Object lockMeterPoint(ProceedingJoinPoint pjp, LockOperationRequest request, RequestMeta meta) + throws Throwable { + long st = System.currentTimeMillis(); + try { + LockMetricsMonitor.getTotalMeter(request.getLockOperationEnum()).incrementAndGet(); + LockOperationResponse result = (LockOperationResponse) pjp.proceed(); + if (result.isSuccess()) { + LockMetricsMonitor.getSuccessMeter(request.getLockOperationEnum()).incrementAndGet(); + } + return result; + } finally { + long rt = System.currentTimeMillis() - st; + LockMetricsMonitor.getLockHandlerTimer().record(rt, TimeUnit.MILLISECONDS); + } + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/constant/Constants.java b/lock/src/main/java/com/alibaba/nacos/lock/constant/Constants.java new file mode 100644 index 00000000000..025d0b9103d --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/constant/Constants.java @@ -0,0 +1,28 @@ +/* + * 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.lock.constant; + +/** + * constants. + * @author 985492783@qq.com + * @description Constants + * @date 2023/7/10 15:54 + */ +public class Constants { + + public static final String LOCK_ACQUIRE_SERVICE_GROUP_V2 = "lock_acquire_service_v2"; +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/constant/PropertiesConstant.java b/lock/src/main/java/com/alibaba/nacos/lock/constant/PropertiesConstant.java new file mode 100644 index 00000000000..30fe91f6aef --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/constant/PropertiesConstant.java @@ -0,0 +1,34 @@ +/* + * 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.lock.constant; + +/** + * properties constant. + * + * @author 985492783@qq.com + * @date 2023/8/25 0:50 + */ +public class PropertiesConstant { + + public static final String DEFAULT_AUTO_EXPIRE = "nacos.lock.default_expire_time"; + + public static final String MAX_AUTO_EXPIRE = "nacos.lock.max_expire_time"; + + public static final Long DEFAULT_AUTO_EXPIRE_TIME = 30_000L; + + public static final Long MAX_AUTO_EXPIRE_TIME = 1800_000L; +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/AbstractAtomicLock.java b/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/AbstractAtomicLock.java new file mode 100644 index 00000000000..c271ed609ac --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/AbstractAtomicLock.java @@ -0,0 +1,42 @@ +/* + * 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.lock.core.reentrant; + +import java.io.Serializable; + +/** + * abstract atomic lock. + * + * @author 985492783@qq.com + * @description AtomicLock + * @date 2023/7/10 14:50 + */ +public abstract class AbstractAtomicLock implements AtomicLockService, Serializable { + + private static final long serialVersionUID = -3460985546856855524L; + + private final String key; + + public AbstractAtomicLock(String key) { + this.key = key; + } + + @Override + public String getKey() { + return key; + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/AtomicLockService.java b/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/AtomicLockService.java new file mode 100644 index 00000000000..9d4b1988f5e --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/AtomicLockService.java @@ -0,0 +1,63 @@ +/* + * 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.lock.core.reentrant; + +import com.alibaba.nacos.lock.model.LockInfo; + +/** + * Atomic Lock Service. + * + * @author 985492783@qq.com + * @description AtomicLockService + * @date 2023/7/10 15:34 + */ +public interface AtomicLockService { + + /** + * try lock with expireTime. + * + * @param lockInfo request Lock + * @return boolean + */ + Boolean tryLock(LockInfo lockInfo); + + /** + * release lock. + * + * @param lockInfo instance + * @return boolean + */ + Boolean unLock(LockInfo lockInfo); + + /** + * return is auto expire. + * @return Boolean + */ + Boolean autoExpire(); + + /** + * get key. + * @return key + */ + String getKey(); + + /** + * judge lock is clear to gc. + * @return boolean + */ + Boolean isClear(); +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/mutex/MutexAtomicLock.java b/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/mutex/MutexAtomicLock.java new file mode 100644 index 00000000000..a1b8d5a1098 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/core/reentrant/mutex/MutexAtomicLock.java @@ -0,0 +1,71 @@ +/* + * 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.lock.core.reentrant.mutex; + +import com.alibaba.nacos.lock.core.reentrant.AbstractAtomicLock; +import com.alibaba.nacos.lock.model.LockInfo; + +import java.util.concurrent.atomic.AtomicInteger; + +/** + * MutexAtomicLock. + * + * @author 985492783@qq.com + * @description MutexAtomicLock + * @date 2023/7/10 15:33 + */ +public class MutexAtomicLock extends AbstractAtomicLock { + + private static final Integer EMPTY = 0; + + private static final Integer FULL = 1; + + private final AtomicInteger state; + + private Long expiredTimestamp; + + public MutexAtomicLock(String key) { + super(key); + this.state = new AtomicInteger(EMPTY); + } + + @Override + public Boolean tryLock(LockInfo lockInfo) { + Long endTime = lockInfo.getEndTime(); + if (state.compareAndSet(EMPTY, FULL) || autoExpire()) { + this.expiredTimestamp = endTime; + return true; + } + return false; + } + + @Override + public Boolean unLock(LockInfo lockInfo) { + return state.compareAndSet(FULL, EMPTY); + } + + @Override + public Boolean autoExpire() { + return System.currentTimeMillis() >= this.expiredTimestamp; + } + + @Override + public Boolean isClear() { + return EMPTY.equals(state.get()) || autoExpire(); + } + +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/exception/NacosLockException.java b/lock/src/main/java/com/alibaba/nacos/lock/exception/NacosLockException.java new file mode 100644 index 00000000000..8669c7e18f1 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/exception/NacosLockException.java @@ -0,0 +1,45 @@ +/* + * 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.lock.exception; + +/** + * NacosLockException. + * + * @author 985492783@qq.com + * @date 2023/11/18 18:57 + */ +public class NacosLockException extends RuntimeException { + + public NacosLockException() { + } + + public NacosLockException(String message) { + super(message); + } + + public NacosLockException(String message, Throwable cause) { + super(message, cause); + } + + public NacosLockException(Throwable cause) { + super(cause); + } + + public NacosLockException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) { + super(message, cause, enableSuppression, writableStackTrace); + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/factory/LockFactory.java b/lock/src/main/java/com/alibaba/nacos/lock/factory/LockFactory.java new file mode 100644 index 00000000000..a87c63d0bae --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/factory/LockFactory.java @@ -0,0 +1,41 @@ +/* + * 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.lock.factory; + +import com.alibaba.nacos.lock.core.reentrant.AbstractAtomicLock; + +/** + * lock factory. + * + * @author 985492783@qq.com + * @date 2023/8/22 20:57 + */ +public interface LockFactory { + + /** + * spi lock factory type. + * @return type + */ + String getLockType(); + + /** + * create spi lock entity. + * @param key key + * @return AbstractAtomicLock + */ + AbstractAtomicLock createLock(String key); +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/factory/SimpleLockFactory.java b/lock/src/main/java/com/alibaba/nacos/lock/factory/SimpleLockFactory.java new file mode 100644 index 00000000000..87ab3cb3751 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/factory/SimpleLockFactory.java @@ -0,0 +1,40 @@ +/* + * 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.lock.factory; + +import com.alibaba.nacos.api.lock.common.LockConstants; +import com.alibaba.nacos.lock.core.reentrant.AbstractAtomicLock; +import com.alibaba.nacos.lock.core.reentrant.mutex.MutexAtomicLock; + +/** + * lock factory. + * + * @author 985492783@qq.com + * @date 2023/8/22 21:16 + */ +public class SimpleLockFactory implements LockFactory { + + @Override + public String getLockType() { + return LockConstants.NACOS_LOCK_TYPE; + } + + @Override + public AbstractAtomicLock createLock(String key) { + return new MutexAtomicLock(key); + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/model/LockInfo.java b/lock/src/main/java/com/alibaba/nacos/lock/model/LockInfo.java new file mode 100644 index 00000000000..1e72f3f3314 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/model/LockInfo.java @@ -0,0 +1,64 @@ +/* + * 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.lock.model; + +import java.io.Serializable; +import java.util.Map; + +/** + * lock info DTO. + * + * @author 985492783@qq.com + * @date 2023/9/17 14:20 + */ +public class LockInfo implements Serializable { + + private static final long serialVersionUID = -3460985546826875524L; + + private LockKey key; + + private Long endTime; + + private Map params; + + public LockInfo() { + } + + public LockKey getKey() { + return key; + } + + public void setKey(LockKey key) { + this.key = key; + } + + public Long getEndTime() { + return endTime; + } + + public void setEndTime(Long endTime) { + this.endTime = endTime; + } + + public Map getParams() { + return params; + } + + public void setParams(Map params) { + this.params = params; + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/model/LockKey.java b/lock/src/main/java/com/alibaba/nacos/lock/model/LockKey.java new file mode 100644 index 00000000000..d2b40f0df28 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/model/LockKey.java @@ -0,0 +1,73 @@ +/* + * 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.lock.model; + +import java.io.Serializable; +import java.util.Objects; + +/** + * lock key type and key name. + * + * @author 985492783@qq.com + * @date 2023/9/7 21:31 + */ +public class LockKey implements Serializable { + + private static final long serialVersionUID = -3460548121526875524L; + + public LockKey(String lockType, String key) { + this.lockType = lockType; + this.key = key; + } + + private String lockType; + + private String key; + + public String getLockType() { + return lockType; + } + + public void setLockType(String lockType) { + this.lockType = lockType; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + LockKey lockKey = (LockKey) o; + return Objects.equals(lockType, lockKey.lockType) && Objects.equals(key, lockKey.key); + } + + @Override + public int hashCode() { + return Objects.hash(lockType, key); + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/monitor/LockMemoryMonitor.java b/lock/src/main/java/com/alibaba/nacos/lock/monitor/LockMemoryMonitor.java new file mode 100644 index 00000000000..64e4587548a --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/monitor/LockMemoryMonitor.java @@ -0,0 +1,40 @@ +/* + * 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.lock.monitor; + +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; + +/** + * memory monitor. + * + * @author goumang.zh@alibaba-inc.com + */ +@Service +public class LockMemoryMonitor { + + /** + * auto clean metrics per-day. + */ + @Scheduled(cron = "0 0 0 * * ?") + public void clear() { + LockMetricsMonitor.getGrpcLockTotal().set(0); + LockMetricsMonitor.getGrpcLockSuccess().set(0); + LockMetricsMonitor.getGrpcUnLockTotal().set(0); + LockMetricsMonitor.getGrpcUnLockSuccess().set(0); + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/monitor/LockMetricsMonitor.java b/lock/src/main/java/com/alibaba/nacos/lock/monitor/LockMetricsMonitor.java new file mode 100644 index 00000000000..507b2802e05 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/monitor/LockMetricsMonitor.java @@ -0,0 +1,111 @@ +/* + * 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.lock.monitor; + +import com.alibaba.nacos.api.lock.remote.LockOperationEnum; +import com.alibaba.nacos.core.monitor.NacosMeterRegistryCenter; +import io.micrometer.core.instrument.ImmutableTag; +import io.micrometer.core.instrument.Tag; +import io.micrometer.core.instrument.Timer; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; + +/** + * MetricsMonitor. + * @author goumang.zh@alibaba-inc.com + */ +public class LockMetricsMonitor { + + private static final String METER_REGISTRY = NacosMeterRegistryCenter.LOCK_STABLE_REGISTRY; + + private static AtomicInteger grpcLockSuccess = new AtomicInteger(); + + private static AtomicInteger grpcUnLockSuccess = new AtomicInteger(); + + private static AtomicInteger grpcLockTotal = new AtomicInteger(); + + private static AtomicInteger grpcUnLockTotal = new AtomicInteger(); + + private static AtomicInteger aliveLockCount = new AtomicInteger(); + + static { + ImmutableTag immutableTag = new ImmutableTag("module", "lock"); + List tags = new ArrayList<>(); + tags.add(immutableTag); + tags.add(new ImmutableTag("name", "grpcLockTotal")); + NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_monitor", tags, grpcLockTotal); + + tags = new ArrayList<>(); + tags.add(immutableTag); + tags.add(new ImmutableTag("name", "grpcLockSuccess")); + NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_monitor", tags, grpcLockSuccess); + + tags = new ArrayList<>(); + tags.add(immutableTag); + tags.add(new ImmutableTag("name", "grpcUnLockTotal")); + NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_monitor", tags, grpcUnLockTotal); + + tags = new ArrayList<>(); + tags.add(immutableTag); + tags.add(new ImmutableTag("name", "grpcUnLockSuccess")); + NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_monitor", tags, grpcUnLockSuccess); + + tags = new ArrayList<>(); + tags.add(immutableTag); + tags.add(new ImmutableTag("name", "aliveLockCount")); + NacosMeterRegistryCenter.gauge(METER_REGISTRY, "nacos_monitor", tags, aliveLockCount); + } + + public static AtomicInteger getGrpcLockSuccess() { + return grpcLockSuccess; + } + + public static AtomicInteger getGrpcUnLockSuccess() { + return grpcUnLockSuccess; + } + + public static AtomicInteger getGrpcLockTotal() { + return grpcLockTotal; + } + + public static AtomicInteger getGrpcUnLockTotal() { + return grpcUnLockTotal; + } + + public static Timer getLockHandlerTimer() { + return NacosMeterRegistryCenter + .timer(METER_REGISTRY, "nacos_timer", "module", "lock", "name", "lockHandlerRt"); + } + + public static AtomicInteger getSuccessMeter(LockOperationEnum lockOperationEnum) { + if (lockOperationEnum == LockOperationEnum.ACQUIRE) { + return grpcLockSuccess; + } else { + return grpcUnLockSuccess; + } + } + + public static AtomicInteger getTotalMeter(LockOperationEnum lockOperationEnum) { + if (lockOperationEnum == LockOperationEnum.ACQUIRE) { + return grpcLockTotal; + } else { + return grpcUnLockTotal; + } + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/persistence/NacosLockSnapshotOperation.java b/lock/src/main/java/com/alibaba/nacos/lock/persistence/NacosLockSnapshotOperation.java new file mode 100644 index 00000000000..3e9fadc011a --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/persistence/NacosLockSnapshotOperation.java @@ -0,0 +1,159 @@ +/* + * 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.lock.persistence; + +import com.alibaba.nacos.consistency.SerializeFactory; +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.snapshot.LocalFileMeta; +import com.alibaba.nacos.consistency.snapshot.Reader; +import com.alibaba.nacos.consistency.snapshot.SnapshotOperation; +import com.alibaba.nacos.consistency.snapshot.Writer; +import com.alibaba.nacos.core.distributed.raft.utils.RaftExecutor; +import com.alibaba.nacos.core.utils.Loggers; +import com.alibaba.nacos.lock.LockManager; +import com.alibaba.nacos.lock.core.reentrant.AtomicLockService; +import com.alibaba.nacos.lock.model.LockKey; +import com.alibaba.nacos.sys.utils.DiskUtils; +import com.alibaba.nacos.sys.utils.TimerContext; +import com.alipay.sofa.jraft.util.CRC64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Paths; +import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.function.BiConsumer; +import java.util.zip.Checksum; + +/** + * nacosLock snapshot handler. + * + * @author 985492783@qq.com + * @date 2023/9/7 20:42 + */ +public class NacosLockSnapshotOperation implements SnapshotOperation { + + protected static final String CHECK_SUM_KEY = "checksum"; + + private final ReentrantReadWriteLock.WriteLock writeLock; + + private final LockManager lockManager; + + private static final Logger LOGGER = LoggerFactory.getLogger(NacosLockSnapshotOperation.class); + + private static final String LOCK_SNAPSHOT_SAVE = NacosLockSnapshotOperation.class.getSimpleName() + ".SAVE"; + + private static final String LOCK_SNAPSHOT_LOAD = NacosLockSnapshotOperation.class.getSimpleName() + ".LOAD"; + + private final Serializer serializer = SerializeFactory.getDefault(); + + private static final String SNAPSHOT_ARCHIVE = "nacos_lock.zip"; + + public NacosLockSnapshotOperation(LockManager lockManager, ReentrantReadWriteLock.WriteLock writeLock) { + this.lockManager = lockManager; + this.writeLock = writeLock; + } + + @Override + public void onSnapshotSave(Writer writer, BiConsumer callFinally) { + RaftExecutor.doSnapshot(() -> { + TimerContext.start(getSnapshotSaveTag()); + final Lock lock = writeLock; + lock.lock(); + try { + callFinally.accept(writeSnapshot(writer), null); + } catch (Throwable t) { + Loggers.RAFT.error("Fail to compress snapshot, path={}, file list={}.", writer.getPath(), + writer.listFiles(), t); + callFinally.accept(false, t); + } finally { + lock.unlock(); + TimerContext.end(getSnapshotSaveTag(), Loggers.RAFT); + } + }); + } + + private boolean writeSnapshot(Writer writer) throws IOException { + final String writePath = writer.getPath(); + final String outputFile = Paths.get(writePath, SNAPSHOT_ARCHIVE).toString(); + final Checksum checksum = new CRC64(); + try (InputStream inputStream = dumpSnapshot()) { + DiskUtils.compressIntoZipFile("lock", inputStream, outputFile, checksum); + } + final LocalFileMeta meta = new LocalFileMeta(); + meta.append(CHECK_SUM_KEY, Long.toHexString(checksum.getValue())); + return writer.addFile(SNAPSHOT_ARCHIVE, meta); + } + + private InputStream dumpSnapshot() { + ConcurrentHashMap lockMap = lockManager.showLocks(); + return new ByteArrayInputStream(serializer.serialize(lockMap)); + } + + @Override + public boolean onSnapshotLoad(Reader reader) { + TimerContext.start(getSnapshotLoadTag()); + final Lock lock = writeLock; + lock.lock(); + try { + return readSnapshot(reader); + } catch (final Throwable t) { + Loggers.RAFT.error("Fail to load snapshot, path={}, file list={}.", reader.getPath(), reader.listFiles(), + t); + return false; + } finally { + lock.unlock(); + TimerContext.end(getSnapshotLoadTag(), Loggers.RAFT); + } + } + + private boolean readSnapshot(Reader reader) throws Exception { + final String readerPath = reader.getPath(); + Loggers.RAFT.info("snapshot start to load from : {}", readerPath); + final String sourceFile = Paths.get(readerPath, SNAPSHOT_ARCHIVE).toString(); + final Checksum checksum = new CRC64(); + byte[] snapshotBytes = DiskUtils.decompress(sourceFile, checksum); + LocalFileMeta fileMeta = reader.getFileMeta(SNAPSHOT_ARCHIVE); + if (fileMeta.getFileMeta().containsKey(CHECK_SUM_KEY) && !Objects.equals(Long.toHexString(checksum.getValue()), + fileMeta.get(CHECK_SUM_KEY))) { + throw new IllegalArgumentException("Snapshot checksum failed"); + } + loadSnapshot(snapshotBytes); + Loggers.RAFT.info("snapshot success to load from : {}", readerPath); + return true; + } + + private void loadSnapshot(byte[] snapshotBytes) { + ConcurrentHashMap newData = serializer.deserialize(snapshotBytes); + ConcurrentHashMap lockMap = lockManager.showLocks(); + //loadSnapshot + lockMap.putAll(newData); + } + + protected String getSnapshotSaveTag() { + return LOCK_SNAPSHOT_SAVE; + } + + protected String getSnapshotLoadTag() { + return LOCK_SNAPSHOT_LOAD; + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/raft/request/MutexLockRequest.java b/lock/src/main/java/com/alibaba/nacos/lock/raft/request/MutexLockRequest.java new file mode 100644 index 00000000000..98ec399014c --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/raft/request/MutexLockRequest.java @@ -0,0 +1,42 @@ +/* + * 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.lock.raft.request; + +import com.alibaba.nacos.lock.model.LockInfo; + +import java.io.Serializable; + +/** + * mutex lock request. + * + * @author 985492783@qq.com + * @date 2023/8/24 18:40 + */ +public class MutexLockRequest implements Serializable { + + private static final long serialVersionUID = -925543547156890549L; + + private LockInfo lockInfo; + + public LockInfo getLockInfo() { + return lockInfo; + } + + public void setLockInfo(LockInfo lockInfo) { + this.lockInfo = lockInfo; + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/remote/rpc/handler/LockRequestHandler.java b/lock/src/main/java/com/alibaba/nacos/lock/remote/rpc/handler/LockRequestHandler.java new file mode 100644 index 00000000000..59f369cd9c5 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/remote/rpc/handler/LockRequestHandler.java @@ -0,0 +1,70 @@ +/* + * 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.lock.remote.rpc.handler; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.api.lock.remote.LockOperationEnum; +import com.alibaba.nacos.api.lock.remote.request.LockOperationRequest; +import com.alibaba.nacos.api.lock.remote.response.LockOperationResponse; +import com.alibaba.nacos.api.remote.request.RequestMeta; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.core.remote.RequestHandler; +import com.alibaba.nacos.lock.exception.NacosLockException; +import com.alibaba.nacos.lock.service.LockOperationService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * lock grpc handler. + * + * @author 985492783@qq.com + * @description LockRequestHandler + * @date 2023/6/29 14:00 + */ +@Component +public class LockRequestHandler extends RequestHandler { + + private static final Logger LOGGER = LoggerFactory.getLogger(LockRequestHandler.class); + + private final LockOperationService lockOperationService; + + public LockRequestHandler(LockOperationService lockOperationService) { + this.lockOperationService = lockOperationService; + } + + @Override + @Secured(resource = "grpc/lock") + public LockOperationResponse handle(LockOperationRequest request, RequestMeta meta) throws NacosException { + Boolean lock = null; + LOGGER.info("request: {}, instance: {}", request.getLockOperationEnum(), request.getLockInstance()); + try { + if (request.getLockOperationEnum() == LockOperationEnum.ACQUIRE) { + LockInstance lockInstance = request.getLockInstance(); + lock = lockOperationService.lock(lockInstance); + } else if (request.getLockOperationEnum() == LockOperationEnum.RELEASE) { + lock = lockOperationService.unLock(request.getLockInstance()); + } else { + return LockOperationResponse.fail("There is no Handler of such operations!"); + } + return LockOperationResponse.success(lock); + } catch (NacosLockException e) { + return LockOperationResponse.fail(e.getMessage()); + } + } +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/service/LockOperationService.java b/lock/src/main/java/com/alibaba/nacos/lock/service/LockOperationService.java new file mode 100644 index 00000000000..c5d0fa5971e --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/service/LockOperationService.java @@ -0,0 +1,45 @@ +/* + * 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.lock.service; + +import com.alibaba.nacos.api.lock.model.LockInstance; + +/** + * lock operator service. + * + * @author 985492783@qq.com + * @date 2023/6/28 2:38 + */ +public interface LockOperationService { + + /** + * get lock operator. + * + * @param lockInstance lockInstance + * @return boolean + */ + Boolean lock(LockInstance lockInstance); + + /** + * unLock. + * + * @param lockInstance lockInstance + * @return Boolean + */ + Boolean unLock(LockInstance lockInstance); + +} diff --git a/lock/src/main/java/com/alibaba/nacos/lock/service/impl/LockOperationServiceImpl.java b/lock/src/main/java/com/alibaba/nacos/lock/service/impl/LockOperationServiceImpl.java new file mode 100644 index 00000000000..4b8f1b182d4 --- /dev/null +++ b/lock/src/main/java/com/alibaba/nacos/lock/service/impl/LockOperationServiceImpl.java @@ -0,0 +1,209 @@ +/* + * 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.lock.service.impl; + +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.api.lock.remote.LockOperationEnum; +import com.alibaba.nacos.consistency.SerializeFactory; +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.cp.CPProtocol; +import com.alibaba.nacos.consistency.cp.RequestProcessor4CP; +import com.alibaba.nacos.consistency.entity.ReadRequest; +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.consistency.entity.WriteRequest; +import com.alibaba.nacos.consistency.snapshot.SnapshotOperation; +import com.alibaba.nacos.core.distributed.ProtocolManager; +import com.alibaba.nacos.lock.LockManager; +import com.alibaba.nacos.lock.constant.Constants; +import com.alibaba.nacos.lock.constant.PropertiesConstant; +import com.alibaba.nacos.lock.core.reentrant.AtomicLockService; +import com.alibaba.nacos.lock.exception.NacosLockException; +import com.alibaba.nacos.lock.model.LockInfo; +import com.alibaba.nacos.lock.model.LockKey; +import com.alibaba.nacos.lock.persistence.NacosLockSnapshotOperation; +import com.alibaba.nacos.lock.raft.request.MutexLockRequest; +import com.alibaba.nacos.lock.service.LockOperationService; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import com.google.protobuf.ByteString; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantReadWriteLock; + +/** + * lock operation and CPHandler. + * + * @author 985492783@qq.com + * @date 2023/8/22 20:17 + */ +@Component +public class LockOperationServiceImpl extends RequestProcessor4CP implements LockOperationService { + + private static final Logger LOGGER = LoggerFactory.getLogger(LockOperationServiceImpl.class); + + private final Serializer serializer = SerializeFactory.getDefault(); + + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + + private final ReentrantReadWriteLock.ReadLock readLock = lock.readLock(); + + private final CPProtocol protocol; + + private final LockManager lockManager; + + private final long defaultExpireTime; + + private final long maxExpireTime; + + public LockOperationServiceImpl(LockManager lockManager) { + this.lockManager = lockManager; + this.protocol = ApplicationUtils.getBean(ProtocolManager.class).getCpProtocol(); + this.protocol.addRequestProcessors(Collections.singletonList(this)); + this.defaultExpireTime = EnvUtil.getProperty(PropertiesConstant.DEFAULT_AUTO_EXPIRE, Long.class, + PropertiesConstant.DEFAULT_AUTO_EXPIRE_TIME); + this.maxExpireTime = EnvUtil.getProperty(PropertiesConstant.MAX_AUTO_EXPIRE, Long.class, + PropertiesConstant.MAX_AUTO_EXPIRE_TIME); + } + + @Override + public Response onApply(WriteRequest request) { + final Lock lock = readLock; + lock.lock(); + try { + LockOperationEnum lockOperation = LockOperationEnum.valueOf(request.getOperation()); + Object data = null; + if (lockOperation == LockOperationEnum.ACQUIRE) { + final MutexLockRequest mutexLockRequest = serializer.deserialize(request.getData().toByteArray()); + data = acquireLock(mutexLockRequest); + } else if (lockOperation == LockOperationEnum.RELEASE) { + final MutexLockRequest mutexLockRequest = serializer.deserialize(request.getData().toByteArray()); + data = releaseLock(mutexLockRequest); + } else { + throw new NacosLockException("lockOperation is not exist."); + } + LOGGER.info("thread: {}, operator: {}, request: {}, success: {}", Thread.currentThread().getName(), + lockOperation, serializer.deserialize(request.getData().toByteArray()), data); + ByteString bytes = ByteString.copyFrom(serializer.serialize(data)); + return Response.newBuilder().setSuccess(true).setData(bytes).build(); + } catch (NacosLockException e) { + return Response.newBuilder().setSuccess(false).setErrMsg(e.getMessage()).build(); + } finally { + lock.unlock(); + } + } + + private Boolean releaseLock(MutexLockRequest request) { + LockInfo lockInfo = request.getLockInfo(); + AtomicLockService mutexLock = lockManager.getMutexLock(lockInfo.getKey()); + Boolean unLock = mutexLock.unLock(lockInfo); + if (mutexLock.isClear()) { + lockManager.removeMutexLock(lockInfo.getKey()); + } + return unLock; + } + + private Boolean acquireLock(MutexLockRequest request) { + LockInfo lockInfo = request.getLockInfo(); + AtomicLockService mutexLock = lockManager.getMutexLock(lockInfo.getKey()); + return mutexLock.tryLock(lockInfo); + } + + @Override + public Boolean lock(LockInstance lockInstance) { + final MutexLockRequest request = new MutexLockRequest(); + final LockInfo lockInfo = new LockInfo(); + lockInfo.setKey(new LockKey(lockInstance.getLockType(), lockInstance.getKey())); + lockInfo.setParams(lockInstance.getParams()); + + long expiredTime = lockInstance.getExpiredTime(); + if (expiredTime < 0) { + lockInfo.setEndTime(defaultExpireTime + getNowTimestamp()); + } else { + lockInfo.setEndTime(Math.min(maxExpireTime, expiredTime) + getNowTimestamp()); + } + request.setLockInfo(lockInfo); + WriteRequest writeRequest = WriteRequest.newBuilder().setGroup(group()) + .setData(ByteString.copyFrom(serializer.serialize(request))) + .setOperation(LockOperationEnum.ACQUIRE.name()).build(); + try { + Response response = protocol.write(writeRequest); + if (response.getSuccess()) { + return serializer.deserialize(response.getData().toByteArray()); + } + throw new NacosLockException(response.getErrMsg()); + } catch (NacosLockException e) { + int paramSize = lockInstance.getParams() == null ? 0 : lockInstance.getParams().size(); + LOGGER.error("key: {}, lockType:{}, paramSize:{} lock fail, errorMsg: {}", lockInstance.getKey(), + lockInstance.getLockType(), paramSize, e.getMessage()); + throw e; + } catch (Exception e) { + LOGGER.error("lock fail.", e); + throw new NacosLockException("tryLock error.", e); + } + } + + @Override + public List loadSnapshotOperate() { + return Collections.singletonList(new NacosLockSnapshotOperation(lockManager, lock.writeLock())); + } + + @Override + public Boolean unLock(LockInstance lockInstance) { + MutexLockRequest request = new MutexLockRequest(); + LockInfo lockInfo = new LockInfo(); + lockInfo.setKey(new LockKey(lockInstance.getLockType(), lockInstance.getKey())); + lockInfo.setParams(lockInstance.getParams()); + request.setLockInfo(lockInfo); + WriteRequest writeRequest = WriteRequest.newBuilder().setGroup(group()) + .setData(ByteString.copyFrom(serializer.serialize(request))) + .setOperation(LockOperationEnum.RELEASE.name()).build(); + try { + Response response = protocol.write(writeRequest); + if (response.getSuccess()) { + return serializer.deserialize(response.getData().toByteArray()); + } + throw new NacosLockException(response.getErrMsg()); + } catch (NacosLockException e) { + int paramSize = lockInstance.getParams() == null ? 0 : lockInstance.getParams().size(); + LOGGER.error("key: {}, lockType:{}, paramSize:{} lock fail, errorMsg: {}", lockInstance.getKey(), + lockInstance.getLockType(), paramSize, e.getMessage()); + throw e; + } catch (Exception e) { + throw new NacosLockException("unLock error.", e); + } + } + + public long getNowTimestamp() { + return System.currentTimeMillis(); + } + + @Override + public Response onRequest(ReadRequest request) { + return null; + } + + @Override + public String group() { + return Constants.LOCK_ACQUIRE_SERVICE_GROUP_V2; + } + +} diff --git a/lock/src/main/resources/META-INF/services/com.alibaba.nacos.lock.factory.LockFactory b/lock/src/main/resources/META-INF/services/com.alibaba.nacos.lock.factory.LockFactory new file mode 100644 index 00000000000..32f66bb8c69 --- /dev/null +++ b/lock/src/main/resources/META-INF/services/com.alibaba.nacos.lock.factory.LockFactory @@ -0,0 +1,17 @@ +# +# 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. +# + +com.alibaba.nacos.lock.factory.SimpleLockFactory diff --git a/lock/src/test/java/com/alibaba/nacos/lock/LockManagerTest.java b/lock/src/test/java/com/alibaba/nacos/lock/LockManagerTest.java new file mode 100644 index 00000000000..73318819b0a --- /dev/null +++ b/lock/src/test/java/com/alibaba/nacos/lock/LockManagerTest.java @@ -0,0 +1,87 @@ +/* + * 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.lock; + +import com.alibaba.nacos.lock.core.reentrant.AtomicLockService; +import com.alibaba.nacos.lock.core.reentrant.mutex.ClientAtomicLock; +import com.alibaba.nacos.lock.exception.NacosLockException; +import com.alibaba.nacos.lock.factory.ClientLockFactory; +import com.alibaba.nacos.lock.factory.LockFactory; +import com.alibaba.nacos.lock.model.LockInfo; +import com.alibaba.nacos.lock.model.LockKey; +import org.junit.jupiter.api.Test; + +import java.lang.reflect.Field; +import java.util.HashMap; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * lock manager test. + * + * @author 985492783@qq.com + * @date 2023/8/28 13:26 + */ +public class LockManagerTest { + + private LockManager lockManager = new NacosLockManager(); + + @Test + public void testLockManagerError() { + String emptyType = "testLockFactory_lock"; + assertThrows(NacosLockException.class, () -> { + lockManager.getMutexLock(new LockKey(emptyType, "key")); + }); + + assertThrows(NacosLockException.class, () -> { + lockManager.getMutexLock(new LockKey(emptyType, null)); + }); + + assertThrows(NacosLockException.class, () -> { + lockManager.getMutexLock(new LockKey(null, "key")); + }); + } + + @Test + public void testLockFactory() throws NoSuchFieldException, IllegalAccessException { + Field factoryMap = NacosLockManager.class.getDeclaredField("factoryMap"); + factoryMap.setAccessible(true); + Map map = (Map) factoryMap.get(lockManager); + assertEquals(2, map.size()); + } + + @Test + public void testClientLockFactory() { + AtomicLockService lock = lockManager.getMutexLock(new LockKey(ClientLockFactory.TYPE, "key")); + assertEquals(ClientAtomicLock.class, lock.getClass()); + assertEquals("key", lock.getKey()); + + LockInfo lockInfo = new ClientLockFactory.ClientLockInstance(); + lockInfo.setParams(new HashMap() { + { + put("nacosClientId", "123456"); + } + }); + + assertTrue(lock.tryLock(lockInfo)); + assertTrue(lock.tryLock(lockInfo)); + assertTrue(lock.unLock(lockInfo)); + } +} diff --git a/lock/src/test/java/com/alibaba/nacos/lock/core/reentrant/mutex/ClientAtomicLock.java b/lock/src/test/java/com/alibaba/nacos/lock/core/reentrant/mutex/ClientAtomicLock.java new file mode 100644 index 00000000000..0e799fc54c3 --- /dev/null +++ b/lock/src/test/java/com/alibaba/nacos/lock/core/reentrant/mutex/ClientAtomicLock.java @@ -0,0 +1,67 @@ +/* + * 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.lock.core.reentrant.mutex; + +import com.alibaba.nacos.lock.core.reentrant.AbstractAtomicLock; +import com.alibaba.nacos.lock.model.LockInfo; + +import java.util.concurrent.atomic.AtomicReference; + +/** + * use clientId to get lock. + * + * @author 985492783@qq.com + * @date 2023/8/30 1:02 + */ +public class ClientAtomicLock extends AbstractAtomicLock { + + private static final String EMPTY = null; + + private final AtomicReference state; + + private Long expireTimestamp; + + public ClientAtomicLock(String key) { + super(key); + this.state = new AtomicReference<>(EMPTY); + } + + @Override + public Boolean tryLock(LockInfo lockInfo) { + String nacosClientId = (String) lockInfo.getParams().get("nacosClientId"); + if (nacosClientId == null) { + return false; + } + return state.compareAndSet(EMPTY, nacosClientId) || state.get().equals(nacosClientId); + } + + @Override + public Boolean unLock(LockInfo lockInfo) { + String nacosClientId = (String) lockInfo.getParams().get("nacosClientId"); + return state.compareAndSet(nacosClientId, EMPTY); + } + + @Override + public Boolean autoExpire() { + return System.currentTimeMillis() >= this.expireTimestamp; + } + + @Override + public Boolean isClear() { + return state.get() == null || autoExpire(); + } +} diff --git a/lock/src/test/java/com/alibaba/nacos/lock/core/reentrant/mutex/MutexAtomicLockTest.java b/lock/src/test/java/com/alibaba/nacos/lock/core/reentrant/mutex/MutexAtomicLockTest.java new file mode 100644 index 00000000000..dfd6d03cf12 --- /dev/null +++ b/lock/src/test/java/com/alibaba/nacos/lock/core/reentrant/mutex/MutexAtomicLockTest.java @@ -0,0 +1,71 @@ +/* + * 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.lock.core.reentrant.mutex; + +import com.alibaba.nacos.api.lock.common.LockConstants; +import com.alibaba.nacos.lock.LockManager; +import com.alibaba.nacos.lock.core.reentrant.AtomicLockService; +import com.alibaba.nacos.lock.model.LockInfo; +import com.alibaba.nacos.lock.model.LockKey; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * mutex atomic lock test. + * + * @author 985492783@qq.com + * @date 2023/8/28 13:13 + */ +@ExtendWith(MockitoExtension.class) +public class MutexAtomicLockTest { + + @Mock + private LockManager lockManager; + + @Test + public void testLockAndUnlock() { + Mockito.when(lockManager.getMutexLock(Mockito.any())).thenReturn(new MutexAtomicLock("key")); + AtomicLockService lock = lockManager.getMutexLock(new LockKey(LockConstants.NACOS_LOCK_TYPE, "key")); + LockInfo lockInfo = new LockInfo(); + lockInfo.setEndTime(System.currentTimeMillis() + 2_000_000); + assertTrue(lock.tryLock(lockInfo)); + assertTrue(lock.unLock(lockInfo)); + } + + @Test + public void testAutoExpire() { + Mockito.when(lockManager.getMutexLock(Mockito.any())) + .thenReturn(new MutexAtomicLock("key")); + AtomicLockService lock = lockManager.getMutexLock(new LockKey(LockConstants.NACOS_LOCK_TYPE, "key")); + + LockInfo lockInfo = new LockInfo(); + lockInfo.setEndTime(System.currentTimeMillis() - 2_000_000); + assertTrue(lock.tryLock(lockInfo)); + assertTrue(lock.autoExpire()); + + LockInfo lockInstanceAuto = new LockInfo(); + lockInstanceAuto.setEndTime(System.currentTimeMillis() + 2_000_000); + assertTrue(lock.tryLock(lockInstanceAuto)); + } + +} diff --git a/lock/src/test/java/com/alibaba/nacos/lock/factory/ClientLockFactory.java b/lock/src/test/java/com/alibaba/nacos/lock/factory/ClientLockFactory.java new file mode 100644 index 00000000000..6cf3165cb34 --- /dev/null +++ b/lock/src/test/java/com/alibaba/nacos/lock/factory/ClientLockFactory.java @@ -0,0 +1,46 @@ +/* + * 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.lock.factory; + +import com.alibaba.nacos.lock.core.reentrant.AbstractAtomicLock; +import com.alibaba.nacos.lock.core.reentrant.mutex.ClientAtomicLock; +import com.alibaba.nacos.lock.model.LockInfo; + +/** + * create clientLock. + * + * @author 985492783@qq.com + * @date 2023/8/30 0:59 + */ +public class ClientLockFactory implements LockFactory { + + public static final String TYPE = "CLIENT_LOCK_TYPE"; + + @Override + public String getLockType() { + return TYPE; + } + + @Override + public AbstractAtomicLock createLock(String key) { + return new ClientAtomicLock(key); + } + + public static class ClientLockInstance extends LockInfo { + + } +} diff --git a/lock/src/test/java/com/alibaba/nacos/lock/remote/rpc/handler/LockRequestHandlerTest.java b/lock/src/test/java/com/alibaba/nacos/lock/remote/rpc/handler/LockRequestHandlerTest.java new file mode 100644 index 00000000000..301d1c6c710 --- /dev/null +++ b/lock/src/test/java/com/alibaba/nacos/lock/remote/rpc/handler/LockRequestHandlerTest.java @@ -0,0 +1,73 @@ +/* + * 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.lock.remote.rpc.handler; + +import com.alibaba.nacos.api.exception.NacosException; +import com.alibaba.nacos.api.lock.common.LockConstants; +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.api.lock.remote.LockOperationEnum; +import com.alibaba.nacos.api.lock.remote.request.LockOperationRequest; +import com.alibaba.nacos.api.lock.remote.response.LockOperationResponse; +import com.alibaba.nacos.lock.service.LockOperationService; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * lockRequest handler test. + * + * @author 985492783@qq.com + * @date 2023/9/1 10:00 + */ +@ExtendWith(MockitoExtension.class) +public class LockRequestHandlerTest { + + @Mock + private LockOperationService lockOperationService; + + private LockRequestHandler lockRequestHandler; + + @Test + public void testAcquireHandler() throws NacosException { + lockRequestHandler = new LockRequestHandler(lockOperationService); + + LockInstance lockInstance = new LockInstance("key", 1L, LockConstants.NACOS_LOCK_TYPE); + LockOperationRequest request = new LockOperationRequest(); + request.setLockInstance(lockInstance); + request.setLockOperationEnum(LockOperationEnum.ACQUIRE); + Mockito.when(lockOperationService.lock(lockInstance)).thenReturn(true); + LockOperationResponse response = lockRequestHandler.handle(request, null); + assertTrue((Boolean) response.getResult()); + } + + @Test + public void testReleaseHandler() throws NacosException { + lockRequestHandler = new LockRequestHandler(lockOperationService); + + LockInstance lockInstance = new LockInstance("key", 1L, LockConstants.NACOS_LOCK_TYPE); + LockOperationRequest request = new LockOperationRequest(); + request.setLockInstance(lockInstance); + request.setLockOperationEnum(LockOperationEnum.RELEASE); + Mockito.when(lockOperationService.unLock(lockInstance)).thenReturn(true); + LockOperationResponse response = lockRequestHandler.handle(request, null); + assertTrue((Boolean) response.getResult()); + } +} diff --git a/lock/src/test/java/com/alibaba/nacos/lock/service/impl/LockOperationServiceImplTest.java b/lock/src/test/java/com/alibaba/nacos/lock/service/impl/LockOperationServiceImplTest.java new file mode 100644 index 00000000000..4c36e3f49a5 --- /dev/null +++ b/lock/src/test/java/com/alibaba/nacos/lock/service/impl/LockOperationServiceImplTest.java @@ -0,0 +1,191 @@ +/* + * 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.lock.service.impl; + +import com.alibaba.nacos.api.lock.common.LockConstants; +import com.alibaba.nacos.api.lock.model.LockInstance; +import com.alibaba.nacos.api.lock.remote.LockOperationEnum; +import com.alibaba.nacos.consistency.SerializeFactory; +import com.alibaba.nacos.consistency.Serializer; +import com.alibaba.nacos.consistency.cp.CPProtocol; +import com.alibaba.nacos.consistency.entity.Response; +import com.alibaba.nacos.consistency.entity.WriteRequest; +import com.alibaba.nacos.core.distributed.ProtocolManager; +import com.alibaba.nacos.lock.LockManager; +import com.alibaba.nacos.lock.constant.PropertiesConstant; +import com.alibaba.nacos.lock.core.reentrant.mutex.MutexAtomicLock; +import com.alibaba.nacos.lock.model.LockInfo; +import com.alibaba.nacos.lock.model.LockKey; +import com.alibaba.nacos.lock.raft.request.MutexLockRequest; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import com.google.protobuf.ByteString; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.MockedStatic; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import static com.alibaba.nacos.lock.constant.Constants.LOCK_ACQUIRE_SERVICE_GROUP_V2; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * lock operation service test. + * + * @author 985492783@qq.com + * @date 2023/8/30 14:01 + */ +@ExtendWith(MockitoExtension.class) +public class LockOperationServiceImplTest { + + @Mock + private ProtocolManager protocolManager; + + @Mock + private static CPProtocol cpProtocol; + + @Mock + private static LockManager lockManager; + + private final Serializer serializer = SerializeFactory.getDefault(); + + private LockOperationServiceImpl lockOperationService; + + private static MockedStatic mockedStatic; + + private static MockedStatic mockedEnv; + + @BeforeAll + public static void setUp() { + mockedStatic = Mockito.mockStatic(ApplicationUtils.class); + mockedEnv = Mockito.mockStatic(EnvUtil.class); + mockedEnv.when(() -> EnvUtil.getProperty(Mockito.any(), Mockito.any(), Mockito.any())) + .thenAnswer(ins -> ins.getArgument(2)); + } + + /** + * build test service. + */ + public void buildService() { + mockedStatic.when(() -> ApplicationUtils.getBean(ProtocolManager.class)).thenReturn(protocolManager); + Mockito.when(protocolManager.getCpProtocol()).thenReturn(cpProtocol); + lockOperationService = Mockito.spy(new LockOperationServiceImpl(lockManager)); + } + + @Test + public void testGroup() { + buildService(); + + assertEquals(lockOperationService.group(), LOCK_ACQUIRE_SERVICE_GROUP_V2); + } + + @Test + public void testLockExpire() throws Exception { + buildService(); + + long timestamp = 1 << 10; + Mockito.when(lockOperationService.getNowTimestamp()).thenReturn(timestamp); + Mockito.when(cpProtocol.write(Mockito.any())).thenAnswer((i) -> { + WriteRequest request = i.getArgument(0); + MutexLockRequest mutexLockRequest = serializer.deserialize(request.getData().toByteArray()); + LockInfo lockInfo = mutexLockRequest.getLockInfo(); + assertEquals(LockConstants.NACOS_LOCK_TYPE, lockInfo.getKey().getLockType()); + assertEquals(timestamp + PropertiesConstant.DEFAULT_AUTO_EXPIRE_TIME, (long) lockInfo.getEndTime()); + + return getResponse(); + }); + LockInstance lockInstance = new LockInstance("key", -1L, LockConstants.NACOS_LOCK_TYPE); + lockOperationService.lock(lockInstance); + } + + @Test + public void testLockSimple() throws Exception { + buildService(); + + long timestamp = 1 << 10; + Mockito.when(lockOperationService.getNowTimestamp()).thenReturn(timestamp); + Mockito.when(cpProtocol.write(Mockito.any())).thenAnswer((i) -> { + WriteRequest request = i.getArgument(0); + MutexLockRequest mutexLockRequest = serializer.deserialize(request.getData().toByteArray()); + LockInfo lockInfo = mutexLockRequest.getLockInfo(); + assertEquals(lockInfo.getKey().getLockType(), LockConstants.NACOS_LOCK_TYPE); + assertEquals((long) lockInfo.getEndTime(), timestamp + 1_000L); + + return getResponse(); + }); + LockInstance lockInstance = new LockInstance("key", 1_000L, LockConstants.NACOS_LOCK_TYPE); + lockOperationService.lock(lockInstance); + } + + @Test + public void testLockMaxExpire() throws Exception { + buildService(); + + long timestamp = 1 << 10; + Mockito.when(lockOperationService.getNowTimestamp()).thenReturn(timestamp); + Mockito.when(cpProtocol.write(Mockito.any())).thenAnswer((i) -> { + WriteRequest request = i.getArgument(0); + MutexLockRequest mutexLockRequest = serializer.deserialize(request.getData().toByteArray()); + LockInfo lockInfo = mutexLockRequest.getLockInfo(); + assertEquals(lockInfo.getKey().getLockType(), LockConstants.NACOS_LOCK_TYPE); + assertEquals((long) lockInfo.getEndTime(), timestamp + PropertiesConstant.MAX_AUTO_EXPIRE_TIME); + + return getResponse(); + }); + LockInstance lockInstance = new LockInstance("key", PropertiesConstant.MAX_AUTO_EXPIRE_TIME + 1_000L, + LockConstants.NACOS_LOCK_TYPE); + lockOperationService.lock(lockInstance); + } + + @Test + public void testOnApply() { + buildService(); + Mockito.when(lockManager.getMutexLock(new LockKey(LockConstants.NACOS_LOCK_TYPE, "key"))) + .thenReturn(new MutexAtomicLock("key")); + + WriteRequest request = getRequest(LockOperationEnum.ACQUIRE); + Response response = lockOperationService.onApply(request); + assertTrue(response.getSuccess()); + assertTrue(serializer.deserialize(response.getData().toByteArray())); + } + + public WriteRequest getRequest(LockOperationEnum lockOperationEnum) { + MutexLockRequest mutexLockRequest = new MutexLockRequest(); + LockInfo lockInfo = new LockInfo(); + lockInfo.setEndTime(1L + System.currentTimeMillis()); + lockInfo.setKey(new LockKey(LockConstants.NACOS_LOCK_TYPE, "key")); + mutexLockRequest.setLockInfo(lockInfo); + WriteRequest writeRequest = WriteRequest.newBuilder().setGroup(lockOperationService.group()) + .setData(ByteString.copyFrom(serializer.serialize(mutexLockRequest))) + .setOperation(lockOperationEnum.name()).build(); + return writeRequest; + } + + public Response getResponse() { + return Response.newBuilder().setSuccess(true).setData(ByteString.copyFrom(serializer.serialize(true))).build(); + } + + @AfterAll + public static void destroy() { + mockedStatic.close(); + mockedEnv.close(); + } +} diff --git a/lock/src/test/resources/META-INF/services/com.alibaba.nacos.lock.factory.LockFactory b/lock/src/test/resources/META-INF/services/com.alibaba.nacos.lock.factory.LockFactory new file mode 100644 index 00000000000..efe84fa304d --- /dev/null +++ b/lock/src/test/resources/META-INF/services/com.alibaba.nacos.lock.factory.LockFactory @@ -0,0 +1,17 @@ +# +# 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. +# + +com.alibaba.nacos.lock.factory.ClientLockFactory diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java index 767db3503e7..c741f4c4f28 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/CatalogController.java @@ -23,12 +23,14 @@ import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.naming.core.CatalogService; import com.alibaba.nacos.naming.core.CatalogServiceV2Impl; import com.alibaba.nacos.naming.misc.UtilsAndCommons; import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; @@ -61,6 +63,7 @@ public class CatalogController { */ @Secured(action = ActionTypes.READ) @GetMapping("/service") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/ns/service") public Object serviceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, String serviceName) throws NacosException { String serviceNameWithoutGroup = NamingUtils.getServiceName(serviceName); @@ -81,6 +84,7 @@ public Object serviceDetail(@RequestParam(defaultValue = Constants.DEFAULT_NAMES */ @Secured(action = ActionTypes.READ) @RequestMapping(value = "/instances") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/ns/instance/list") public ObjectNode instanceList(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName, @RequestParam String clusterName, @RequestParam(name = "pageNo") int page, @RequestParam int pageSize) throws NacosException { @@ -125,6 +129,7 @@ public ObjectNode instanceList(@RequestParam(defaultValue = Constants.DEFAULT_NA */ @Secured(action = ActionTypes.READ) @GetMapping("/services") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/ns/service/list") public Object listDetail(@RequestParam(required = false) boolean withInstances, @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam(required = false) int pageNo, @RequestParam(required = false) int pageSize, diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java index f3d0fe31c34..7ca877493b3 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ClusterController.java @@ -24,6 +24,7 @@ import com.alibaba.nacos.common.utils.ConvertUtils; import com.alibaba.nacos.common.utils.NumberUtils; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.ClusterOperator; @@ -32,6 +33,7 @@ import com.alibaba.nacos.naming.misc.UtilsAndCommons; import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -63,6 +65,7 @@ public ClusterController(ClusterOperatorV2Impl clusterOperatorV2) { */ @PutMapping @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "PUT ${contextPath:nacos}/v3/console/ns/service/cluster") public String update(HttpServletRequest request) throws Exception { final String namespaceId = WebUtils .optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java index a923fe37c9f..7772a0e99a0 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/HealthController.java @@ -25,6 +25,7 @@ import com.alibaba.nacos.common.utils.ConvertUtils; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.HealthOperator; @@ -35,6 +36,7 @@ import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.naming.web.CanDistro; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.sys.env.EnvUtil; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; @@ -75,6 +77,7 @@ public class HealthController { * @return hello message */ @RequestMapping("/server") + @Compatibility(apiType = ApiType.ADMIN_API) public ResponseEntity server() { ObjectNode result = JacksonUtils.createEmptyJsonNode(); result.put("msg", "Hello! I am Nacos-Naming and healthy! total services: " + MetricsMonitor.getDomCountMonitor() @@ -91,6 +94,7 @@ public ResponseEntity server() { @CanDistro @PutMapping(value = {"", "/instance"}) @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public ResponseEntity update(HttpServletRequest request) throws NacosException { String healthyString = WebUtils.optional(request, HEALTHY_KEY, StringUtils.EMPTY); if (StringUtils.isBlank(healthyString)) { @@ -117,6 +121,7 @@ public ResponseEntity update(HttpServletRequest request) throws NacosException { * @return health checkers map */ @GetMapping("/checkers") + @Compatibility(apiType = ApiType.ADMIN_API) public ResponseEntity checkers() { List> classes = HealthCheckType.getLoadedHealthCheckerClasses(); Map checkerMap = new HashMap<>(8); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java index 1c6d6005600..08899735da4 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/InstanceController.java @@ -32,6 +32,7 @@ import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.InstanceOperator; @@ -54,6 +55,7 @@ import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.naming.web.CanDistro; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -110,6 +112,7 @@ public InstanceController() { @PostMapping @TpsControl(pointName = "NamingInstanceRegister", name = "HttpNamingInstanceRegister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.OPEN_API) public String register(HttpServletRequest request) throws Exception { final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, @@ -139,6 +142,7 @@ public String register(HttpServletRequest request) throws Exception { @DeleteMapping @TpsControl(pointName = "NamingInstanceDeregister", name = "HttpNamingInstanceDeregister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.OPEN_API) public String deregister(HttpServletRequest request) throws Exception { Instance instance = HttpRequestInstanceBuilder.newBuilder() .setDefaultInstanceEphemeral(switchDomain.isDefaultInstanceEphemeral()).setRequest(request).build(); @@ -165,6 +169,7 @@ public String deregister(HttpServletRequest request) throws Exception { @PutMapping @TpsControl(pointName = "NamingInstanceUpdate", name = "HttpNamingInstanceUpdate") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.OPEN_API, alternatives = "PUT ${contextPath:nacos}/v3/console/ns/instance") public String update(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); @@ -192,6 +197,7 @@ public String update(HttpServletRequest request) throws Exception { @TpsControl(pointName = "NamingInstanceMetadataUpdate", name = "HttpNamingInstanceMetadataBatchUpdate") @Secured(action = ActionTypes.WRITE) @ExtractorManager.Extractor(httpExtractor = NamingInstanceMetadataBatchHttpParamExtractor.class) + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode batchUpdateInstanceMetadata(HttpServletRequest request) throws Exception { final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); @@ -227,6 +233,7 @@ public ObjectNode batchUpdateInstanceMetadata(HttpServletRequest request) throws @TpsControl(pointName = "NamingInstanceMetadataUpdate", name = "HttpNamingInstanceMetadataBatchUpdate") @Secured(action = ActionTypes.WRITE) @ExtractorManager.Extractor(httpExtractor = NamingInstanceMetadataBatchHttpParamExtractor.class) + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode batchDeleteInstanceMetadata(HttpServletRequest request) throws Exception { final String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); @@ -281,6 +288,7 @@ private List parseBatchInstances(String instances) { @CanDistro @PatchMapping @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public String patch(HttpServletRequest request) throws Exception { String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); NamingUtils.checkServiceNameFormat(serviceName); @@ -323,6 +331,7 @@ public String patch(HttpServletRequest request) throws Exception { @TpsControl(pointName = "NamingServiceSubscribe", name = "HttpNamingServiceSubscribe") @Secured(action = ActionTypes.READ) @ExtractorManager.Extractor(httpExtractor = NamingInstanceListHttpParamExtractor.class) + @Compatibility(apiType = ApiType.OPEN_API) public Object list(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); @@ -351,6 +360,7 @@ public Object list(HttpServletRequest request) throws Exception { @GetMapping @TpsControl(pointName = "NamingInstanceQuery", name = "HttpNamingInstanceQuery") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.OPEN_API) public ObjectNode detail(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); @@ -386,6 +396,7 @@ public ObjectNode detail(HttpServletRequest request) throws Exception { @TpsControl(pointName = "HttpHealthCheck", name = "HttpHealthCheck") @Secured(action = ActionTypes.WRITE) @ExtractorManager.Extractor(httpExtractor = NamingInstanceBeatHttpParamExtractor.class) + @Compatibility(apiType = ApiType.OPEN_API) public ObjectNode beat(HttpServletRequest request) throws Exception { ObjectNode result = JacksonUtils.createEmptyJsonNode(); @@ -434,6 +445,7 @@ public ObjectNode beat(HttpServletRequest request) throws Exception { * @throws NacosException any error during handle */ @RequestMapping("/statuses") + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode listWithHealthStatus(@RequestParam String key) throws NacosException { String serviceName; diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java index 124966f519b..5236fb1731b 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/OperatorController.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.utils.InternetAddressUtil; import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.cluster.ServerStatusManager; @@ -35,6 +36,7 @@ import com.alibaba.nacos.naming.monitor.MetricsMonitor; import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.sys.env.EnvUtil; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.web.bind.annotation.GetMapping; @@ -85,6 +87,7 @@ public OperatorController(SwitchManager switchManager, ServerStatusManager serve * @return push metric status */ @RequestMapping("/push/state") + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode pushState(@RequestParam(required = false) boolean detail, @RequestParam(required = false) boolean reset) { ObjectNode result = JacksonUtils.createEmptyJsonNode(); @@ -117,6 +120,7 @@ public ObjectNode pushState(@RequestParam(required = false) boolean detail, * @return switchDomain */ @GetMapping("/switches") + @Compatibility(apiType = ApiType.ADMIN_API) public SwitchDomain switches(HttpServletRequest request) { return switchDomain; } @@ -132,6 +136,7 @@ public SwitchDomain switches(HttpServletRequest request) { */ @Secured(resource = "naming/switches", action = ActionTypes.WRITE) @PutMapping("/switches") + @Compatibility(apiType = ApiType.ADMIN_API) public String updateSwitch(@RequestParam(required = false) boolean debug, @RequestParam String entry, @RequestParam String value) throws Exception { @@ -147,6 +152,7 @@ public String updateSwitch(@RequestParam(required = false) boolean debug, @Reque * @return metrics information */ @GetMapping("/metrics") + @Compatibility(apiType = ApiType.OPEN_API) public ObjectNode metrics(HttpServletRequest request) { boolean onlyStatus = Boolean.parseBoolean(WebUtils.optional(request, "onlyStatus", "true")); ObjectNode result = JacksonUtils.createEmptyJsonNode(); @@ -192,6 +198,7 @@ public ObjectNode metrics(HttpServletRequest request) { } @GetMapping("/distro/client") + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode getResponsibleServer4Client(@RequestParam String ip, @RequestParam String port) { ObjectNode result = JacksonUtils.createEmptyJsonNode(); String tag = ip + InternetAddressUtil.IP_PORT_SPLITER + port; @@ -200,6 +207,7 @@ public ObjectNode getResponsibleServer4Client(@RequestParam String ip, @RequestP } @PutMapping("/log") + @Compatibility(apiType = ApiType.ADMIN_API) public String setLogLevel(@RequestParam String logName, @RequestParam String logLevel) { Loggers.setLogLevel(logName, logLevel); return "ok"; diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java index f74522c4e1d..ac47b3ef56a 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/ServiceController.java @@ -32,6 +32,7 @@ import com.alibaba.nacos.common.utils.NumberUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.utils.WebUtils; import com.alibaba.nacos.naming.core.ServiceOperator; @@ -46,6 +47,7 @@ import com.alibaba.nacos.naming.selector.SelectorManager; import com.alibaba.nacos.naming.utils.ServiceUtil; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; @@ -99,6 +101,7 @@ public class ServiceController { @PostMapping @TpsControl(pointName = "NamingServiceRegister", name = "HttpNamingServiceRegister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.OPEN_API, alternatives = "POST ${contextPath:nacos}/v3/console/ns/service") public String create(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName, @RequestParam(required = false, defaultValue = "0.0F") float protectThreshold, @@ -126,6 +129,7 @@ public String create(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID @DeleteMapping @TpsControl(pointName = "NamingServiceDeregister", name = "HttpNamingServiceDeregister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.OPEN_API, alternatives = "DELETE ${contextPath:nacos}/v3/console/ns/service") public String remove(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName) throws Exception { @@ -147,6 +151,7 @@ public String remove(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID @GetMapping @TpsControl(pointName = "NamingServiceQuery", name = "HttpNamingServiceQuery") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.OPEN_API) public ObjectNode detail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName) throws NacosException { return getServiceOperator().queryService(namespaceId, serviceName); @@ -162,6 +167,7 @@ public ObjectNode detail(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPAC @GetMapping("/list") @TpsControl(pointName = "NamingServiceListQuery", name = "HttpNamingServiceListQuery") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.OPEN_API) public ObjectNode list(HttpServletRequest request) throws Exception { final int pageNo = NumberUtils.toInt(WebUtils.required(request, "pageNo")); final int pageSize = NumberUtils.toInt(WebUtils.required(request, "pageSize")); @@ -187,6 +193,7 @@ public ObjectNode list(HttpServletRequest request) throws Exception { @PutMapping @TpsControl(pointName = "NamingServiceUpdate", name = "HttpNamingServiceUpdate") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.OPEN_API, alternatives = "PUT ${contextPath:nacos}/v3/console/ns/service") public String update(HttpServletRequest request) throws Exception { String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID); String serviceName = WebUtils.required(request, CommonParams.SERVICE_NAME); @@ -213,6 +220,7 @@ public String update(HttpServletRequest request) throws Exception { */ @RequestMapping("/names") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode searchService(@RequestParam(defaultValue = StringUtils.EMPTY) String namespaceId, @RequestParam(defaultValue = StringUtils.EMPTY) String expr) throws NacosException { Map> serviceNameMap = new HashMap<>(16); @@ -242,6 +250,7 @@ public ObjectNode searchService(@RequestParam(defaultValue = StringUtils.EMPTY) */ @GetMapping("/subscribers") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/ns/service/subscribers") public ObjectNode subscribers(HttpServletRequest request) { int pageNo = NumberUtils.toInt(WebUtils.optional(request, "pageNo", "1")); @@ -288,6 +297,7 @@ public ObjectNode subscribers(HttpServletRequest request) { * @return {@link Selector} types. */ @GetMapping("/selector/types") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/ns/service/selector/types") public RestResult> listSelectorTypes() { return RestResultUtils.success(selectorManager.getAllSelectorTypes()); } diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/CatalogControllerV2.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/CatalogControllerV2.java index d70a6fe17e4..5006fdba4dd 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/CatalogControllerV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/CatalogControllerV2.java @@ -22,11 +22,13 @@ import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.naming.core.CatalogServiceV2Impl; import com.alibaba.nacos.naming.misc.UtilsAndCommons; import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; @@ -64,6 +66,7 @@ public class CatalogControllerV2 { */ @Secured(action = ActionTypes.READ) @RequestMapping(value = "/instances") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/console/ns/instance/list") public Result instanceList( @RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName, @RequestParam(required = false) Boolean healthyOnly, diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ClientInfoControllerV2.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ClientInfoControllerV2.java index 7625a170ed7..e02e85b85ba 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ClientInfoControllerV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ClientInfoControllerV2.java @@ -23,6 +23,7 @@ import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.core.remote.Connection; import com.alibaba.nacos.core.remote.ConnectionManager; @@ -39,6 +40,7 @@ import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.fasterxml.jackson.databind.node.ObjectNode; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; @@ -82,6 +84,7 @@ public ClientInfoControllerV2(ClientManager clientManager, ConnectionManager con */ @GetMapping("/list") @Secured(action = ActionTypes.READ, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public Result> getClientList() { return Result.success(new ArrayList<>(clientManager.allClientId())); } @@ -93,6 +96,7 @@ public Result> getClientList() { */ @GetMapping() @Secured(action = ActionTypes.READ, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public Result getClientDetail(@RequestParam("clientId") String clientId) throws NacosApiException { checkClientId(clientId); Client client = clientManager.getClient(clientId); @@ -131,6 +135,7 @@ public Result getClientDetail(@RequestParam("clientId") String clien */ @GetMapping("/publish/list") @Secured(action = ActionTypes.READ, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public Result> getPublishedServiceList(@RequestParam("clientId") String clientId) throws NacosApiException { checkClientId(clientId); @@ -175,6 +180,7 @@ private ObjectNode wrapSingleInstance(InstancePublishInfo instancePublishInfo) { */ @GetMapping("/subscribe/list") @Secured(action = ActionTypes.READ, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public Result> getSubscribeServiceList(@RequestParam("clientId") String clientId) throws NacosApiException { checkClientId(clientId); @@ -210,6 +216,7 @@ public Result> getSubscribeServiceList(@RequestParam("clientId" */ @GetMapping("/service/publisher/list") @Secured(action = ActionTypes.READ, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public Result> getPublishedClientList( @RequestParam(value = "namespaceId", required = false, defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam(value = "groupName", required = false, defaultValue = Constants.DEFAULT_GROUP) String groupName, @@ -256,6 +263,7 @@ public Result> getPublishedClientList( */ @GetMapping("/service/subscriber/list") @Secured(action = ActionTypes.READ, resource = "nacos/admin") + @Compatibility(apiType = ApiType.ADMIN_API) public Result> getSubscribeClientList( @RequestParam(value = "namespaceId", required = false, defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam(value = "groupName", required = false, defaultValue = Constants.DEFAULT_GROUP) String groupName, diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/HealthControllerV2.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/HealthControllerV2.java index 28b5b2b0bda..73981f4dd1f 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/HealthControllerV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/HealthControllerV2.java @@ -21,6 +21,7 @@ import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.api.naming.utils.NamingUtils; import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.naming.core.HealthOperatorV2Impl; import com.alibaba.nacos.naming.misc.UtilsAndCommons; @@ -28,6 +29,7 @@ import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.naming.web.CanDistro; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestMapping; @@ -56,6 +58,7 @@ public class HealthControllerV2 { @CanDistro @PutMapping(value = {"", "/instance"}) @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public Result update(UpdateHealthForm updateHealthForm) throws NacosException { updateHealthForm.validate(); healthOperatorV2.updateHealthStatusForPersistentInstance(updateHealthForm.getNamespaceId(), buildCompositeServiceName(updateHealthForm), diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java index a57df409dee..60a9b2f3cdd 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/InstanceControllerV2.java @@ -37,6 +37,7 @@ import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.naming.core.InstanceOperatorClientImpl; import com.alibaba.nacos.naming.core.InstancePatchObject; @@ -59,6 +60,7 @@ import com.alibaba.nacos.naming.utils.NamingRequestUtil; import com.alibaba.nacos.naming.web.CanDistro; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; @@ -107,6 +109,7 @@ public class InstanceControllerV2 { @PostMapping @TpsControl(pointName = "NamingInstanceRegister", name = "HttpNamingInstanceRegister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public Result register(InstanceForm instanceForm) throws NacosException { // check param instanceForm.validate(); @@ -129,6 +132,7 @@ public Result register(InstanceForm instanceForm) throws NacosException @DeleteMapping @TpsControl(pointName = "NamingInstanceDeregister", name = "HttpNamingInstanceDeregister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public Result deregister(InstanceForm instanceForm) throws NacosException { // check param instanceForm.validate(); @@ -151,6 +155,7 @@ public Result deregister(InstanceForm instanceForm) throws NacosExceptio @PutMapping @TpsControl(pointName = "NamingInstanceUpdate", name = "HttpNamingInstanceUpdate") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public Result update(InstanceForm instanceForm) throws NacosException { // check param instanceForm.validate(); @@ -174,6 +179,7 @@ public Result update(InstanceForm instanceForm) throws NacosException { @TpsControl(pointName = "NamingInstanceMetadataUpdate", name = "HttpNamingInstanceMetadataBatchUpdate") @Secured(action = ActionTypes.WRITE) @ExtractorManager.Extractor(httpExtractor = NamingInstanceMetadataBatchHttpParamExtractor.class) + @Compatibility(apiType = ApiType.ADMIN_API) public Result batchUpdateInstanceMetadata(InstanceMetadataBatchOperationForm form) throws NacosException { form.validate(); @@ -198,6 +204,7 @@ public Result batchUpdateInstanceMetadata(Inst @TpsControl(pointName = "NamingInstanceMetadataUpdate", name = "HttpNamingInstanceMetadataBatchUpdate") @Secured(action = ActionTypes.WRITE) @ExtractorManager.Extractor(httpExtractor = NamingInstanceMetadataBatchHttpParamExtractor.class) + @Compatibility(apiType = ApiType.ADMIN_API) public Result batchDeleteInstanceMetadata(InstanceMetadataBatchOperationForm form) throws NacosException { form.validate(); @@ -250,6 +257,7 @@ private List parseBatchInstances(String instances) { @CanDistro @PatchMapping @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public String patch(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName, @RequestParam String ip, @RequestParam(defaultValue = UtilsAndCommons.DEFAULT_CLUSTER_NAME) String cluster, @@ -290,6 +298,7 @@ public String patch(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) @TpsControl(pointName = "NamingServiceSubscribe", name = "HttpNamingServiceSubscribe") @Secured(action = ActionTypes.READ) @ExtractorManager.Extractor(httpExtractor = NamingInstanceListHttpParamExtractor.class) + @Compatibility(apiType = ApiType.ADMIN_API) public Result list( @RequestParam(value = "namespaceId", defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam(value = "groupName", defaultValue = Constants.DEFAULT_GROUP) String groupName, @@ -325,6 +334,7 @@ public Result list( @GetMapping @TpsControl(pointName = "NamingInstanceQuery", name = "HttpNamingInstanceQuery") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.ADMIN_API) public Result detail( @RequestParam(value = "namespaceId", defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam(value = "groupName", defaultValue = Constants.DEFAULT_GROUP) String groupName, @@ -365,6 +375,7 @@ public Result detail( @TpsControl(pointName = "HttpHealthCheck", name = "HttpHealthCheck") @Secured(action = ActionTypes.WRITE) @ExtractorManager.Extractor(httpExtractor = NamingInstanceBeatHttpParamExtractor.class) + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode beat(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam String serviceName, @RequestParam(defaultValue = StringUtils.EMPTY) String ip, @RequestParam(defaultValue = UtilsAndCommons.DEFAULT_CLUSTER_NAME) String clusterName, @@ -409,6 +420,7 @@ public ObjectNode beat(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ * @throws NacosException any error during handle */ @RequestMapping("/statuses/{key}") + @Compatibility(apiType = ApiType.ADMIN_API) public ObjectNode listWithHealthStatus(@PathVariable String key) throws NacosException { String serviceName; diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/OperatorControllerV2.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/OperatorControllerV2.java index 22c345cf8a7..99cc59e868f 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/OperatorControllerV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/OperatorControllerV2.java @@ -21,6 +21,7 @@ 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.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.naming.cluster.ServerStatusManager; import com.alibaba.nacos.naming.constants.ClientConstants; @@ -34,6 +35,7 @@ import com.alibaba.nacos.naming.monitor.MetricsMonitor; import com.alibaba.nacos.naming.paramcheck.NamingDefaultHttpParamExtractor; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.sys.env.EnvUtil; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; @@ -79,6 +81,7 @@ public OperatorControllerV2(SwitchManager switchManager, ServerStatusManager ser * @return switchDomain */ @GetMapping("/switches") + @Compatibility(apiType = ApiType.ADMIN_API) public Result switches() { return Result.success(switchDomain); } @@ -92,6 +95,7 @@ public Result switches() { */ @Secured(resource = "naming/switches", action = ActionTypes.WRITE) @PutMapping("/switches") + @Compatibility(apiType = ApiType.ADMIN_API) public Result updateSwitch(UpdateSwitchForm updateSwitchForm) throws Exception { updateSwitchForm.validate(); try { @@ -111,6 +115,7 @@ public Result updateSwitch(UpdateSwitchForm updateSwitchForm) throws Exc * @return metrics information */ @GetMapping("/metrics") + @Compatibility(apiType = ApiType.ADMIN_API) public Result metrics( @RequestParam(value = "onlyStatus", required = false, defaultValue = "true") Boolean onlyStatus) { MetricsInfoVo metricsInfoVo = new MetricsInfoVo(); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ServiceControllerV2.java b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ServiceControllerV2.java index 0a8a89ecf6c..859f46359b8 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ServiceControllerV2.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/controllers/v2/ServiceControllerV2.java @@ -31,6 +31,7 @@ import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.control.TpsControl; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.core.paramcheck.ExtractorManager; import com.alibaba.nacos.naming.core.ServiceOperatorV2Impl; import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata; @@ -44,6 +45,7 @@ import com.alibaba.nacos.naming.selector.SelectorManager; import com.alibaba.nacos.naming.utils.ServiceUtil; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.fasterxml.jackson.databind.JsonNode; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -85,6 +87,7 @@ public ServiceControllerV2(ServiceOperatorV2Impl serviceOperatorV2, SelectorMana @PostMapping() @TpsControl(pointName = "NamingServiceRegister", name = "HttpNamingServiceRegister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public Result create(ServiceForm serviceForm) throws Exception { serviceForm.validate(); ServiceMetadata serviceMetadata = new ServiceMetadata(); @@ -106,6 +109,7 @@ public Result create(ServiceForm serviceForm) throws Exception { @DeleteMapping() @TpsControl(pointName = "NamingServiceDeregister", name = "HttpNamingServiceDeregister") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public Result remove( @RequestParam(value = "namespaceId", defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam("serviceName") String serviceName, @@ -123,6 +127,7 @@ public Result remove( @GetMapping() @TpsControl(pointName = "NamingServiceQuery", name = "HttpNamingServiceQuery") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.ADMIN_API) public Result detail( @RequestParam(value = "namespaceId", defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam("serviceName") String serviceName, @@ -139,6 +144,7 @@ public Result detail( @GetMapping("/list") @TpsControl(pointName = "NamingServiceListQuery", name = "HttpNamingServiceListQuery") @Secured(action = ActionTypes.READ) + @Compatibility(apiType = ApiType.ADMIN_API) public Result list( @RequestParam(value = "namespaceId", required = false, defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId, @RequestParam(value = "groupName", required = false, defaultValue = Constants.DEFAULT_GROUP) String groupName, @@ -160,6 +166,7 @@ public Result list( @PutMapping() @TpsControl(pointName = "NamingServiceUpdate", name = "HttpNamingServiceUpdate") @Secured(action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.ADMIN_API) public Result update(ServiceForm serviceForm) throws Exception { serviceForm.validate(); Map metadata = UtilsAndCommons.parseMetadata(serviceForm.getMetadata()); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java b/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java index fc5d9be4ac2..68d013651c0 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/InstanceOperatorClientImpl.java @@ -24,6 +24,7 @@ import com.alibaba.nacos.api.naming.pojo.Instance; import com.alibaba.nacos.api.naming.pojo.ServiceInfo; import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.utils.ConvertUtils; import com.alibaba.nacos.common.utils.InternetAddressUtil; import com.alibaba.nacos.naming.core.v2.ServiceManager; @@ -32,6 +33,7 @@ import com.alibaba.nacos.naming.core.v2.client.impl.IpPortBasedClient; import com.alibaba.nacos.naming.core.v2.client.manager.ClientManager; import com.alibaba.nacos.naming.core.v2.client.manager.ClientManagerDelegate; +import com.alibaba.nacos.naming.core.v2.event.metadata.InfoChangeEvent; import com.alibaba.nacos.naming.core.v2.index.ServiceStorage; import com.alibaba.nacos.naming.core.v2.metadata.InstanceMetadata; import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager; @@ -133,6 +135,7 @@ public void updateInstance(String namespaceId, String serviceName, Instance inst String metadataId = InstancePublishInfo.genMetadataId(instance.getIp(), instance.getPort(), instance.getClusterName()); metadataOperateService.updateInstanceMetadata(service, metadataId, buildMetadata(instance)); + NotifyCenter.publishEvent(new InfoChangeEvent.InstanceInfoChangeEvent(service, instance)); } private InstanceMetadata buildMetadata(Instance instance) { @@ -341,4 +344,4 @@ private Service getService(String namespaceId, String serviceName, boolean ephem return Service.newService(namespaceId, groupName, serviceNameNoGrouped, ephemeral); } -} +} \ No newline at end of file diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/ServiceOperatorV2Impl.java b/naming/src/main/java/com/alibaba/nacos/naming/core/ServiceOperatorV2Impl.java index 8afac9d9f5f..738710f3c8c 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/ServiceOperatorV2Impl.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/ServiceOperatorV2Impl.java @@ -21,9 +21,11 @@ import com.alibaba.nacos.api.exception.api.NacosApiException; import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.api.naming.utils.NamingUtils; +import com.alibaba.nacos.common.notify.NotifyCenter; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.naming.constants.FieldsConstants; import com.alibaba.nacos.naming.core.v2.ServiceManager; +import com.alibaba.nacos.naming.core.v2.event.metadata.InfoChangeEvent; import com.alibaba.nacos.naming.core.v2.index.ServiceStorage; import com.alibaba.nacos.naming.core.v2.metadata.ClusterMetadata; import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataManager; @@ -92,6 +94,7 @@ public void update(Service service, ServiceMetadata metadata) throws NacosExcept String.format("service %s not found!", service.getGroupedServiceName())); } metadataOperateService.updateServiceMetadata(service, metadata); + NotifyCenter.publishEvent(new InfoChangeEvent.ServiceInfoChangeEvent(service)); } @Override @@ -250,4 +253,4 @@ public Collection searchServiceName(String namespaceId, String expr) thr } return result; } -} +} \ No newline at end of file diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java b/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java index 9f47ae0a489..5e65da610c6 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/SubscribeManager.java @@ -16,12 +16,12 @@ package com.alibaba.nacos.naming.core; +import com.alibaba.nacos.naming.core.v2.pojo.Service; import com.alibaba.nacos.naming.pojo.Subscriber; import com.alibaba.nacos.naming.push.NamingSubscriberServiceAggregationImpl; import com.alibaba.nacos.naming.push.NamingSubscriberServiceLocalImpl; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; import java.util.Collection; import java.util.Collections; @@ -40,7 +40,7 @@ * @author xiweng.yy * @since 1.0.1 */ -@Service +@org.springframework.stereotype.Service public class SubscribeManager { @Autowired @@ -67,6 +67,23 @@ public List getSubscribers(String serviceName, String namespaceId, b } } + /** + * Get subscribers. + * + * @param service service info + * @param aggregation aggregation + * @return list of subscriber + */ + public List getSubscribers(Service service, boolean aggregation) { + if (aggregation) { + Collection result = aggregationService.getFuzzySubscribers(service); + return CollectionUtils.isNotEmpty(result) ? result.stream().filter(distinctByKey(Subscriber::toString)) + .collect(Collectors.toList()) : Collections.EMPTY_LIST; + } else { + return new LinkedList<>(localService.getFuzzySubscribers(service)); + } + } + public static Predicate distinctByKey(Function keyExtractor) { Map seen = new ConcurrentHashMap<>(128); return object -> seen.putIfAbsent(keyExtractor.apply(object), Boolean.TRUE) == null; diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/metadata/InfoChangeEvent.java b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/metadata/InfoChangeEvent.java new file mode 100644 index 00000000000..da85e091081 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/event/metadata/InfoChangeEvent.java @@ -0,0 +1,66 @@ +/* + * Copyright 1999-2022 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.naming.core.v2.event.metadata; + +import com.alibaba.nacos.api.naming.pojo.Instance; +import com.alibaba.nacos.common.notify.Event; +import com.alibaba.nacos.naming.core.v2.pojo.Service; + +/**. + * @author RocketEngine26 + * @date 2022/9/14 下午6:12 + */ +public class InfoChangeEvent extends Event { + private static final long serialVersionUID = 2222222222222L; + + private final Service service; + + public InfoChangeEvent(Service service) { + this.service = service; + } + + public Service getService() { + return service; + } + + public static class ServiceInfoChangeEvent extends InfoChangeEvent { + + private static final long serialVersionUID = 3333333333333L; + + public ServiceInfoChangeEvent(Service service) { + super(service); + service.renewUpdateTime(); + } + } + + public static class InstanceInfoChangeEvent extends InfoChangeEvent { + + private static final long serialVersionUID = 4444444444444L; + + private final Instance instance; + + public InstanceInfoChangeEvent(Service service, Instance instance) { + super(service); + this.instance = instance; + } + + public Instance getInstance() { + return instance; + } + } +} \ No newline at end of file diff --git a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManager.java b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManager.java index a4f34661d59..44ca8cbe82b 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManager.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManager.java @@ -151,7 +151,7 @@ public void removeInstanceMetadata(Service service, String metadataId) { if (null != instanceMetadataMapForService) { instanceMetadataMapForService.remove(metadataId); if (instanceMetadataMapForService.isEmpty()) { - serviceMetadataMap.remove(service); + instanceMetadataMap.remove(service); } } expiredMetadataInfos.remove(ExpiredMetadataInfo.newExpiredInstanceMetadata(service, metadataId)); diff --git a/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClientManager.java b/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClientManager.java index 4b20328f742..b5b81685b91 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClientManager.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/misc/HttpClientManager.java @@ -105,7 +105,7 @@ public static NacosAsyncRestTemplate getProcessorNacosAsyncRestTemplate() { } private static void shutdown() { - SRV_LOG.warn("[NamingServerHttpClientManager] Start destroying HTTP-Client"); + SRV_LOG.info("[NamingServerHttpClientManager] Start destroying HTTP-Client"); try { HttpClientBeanHolder.shutdownNacosSyncRest(SYNC_HTTP_CLIENT_FACTORY.getClass().getName()); HttpClientBeanHolder.shutdownNacosSyncRest(APACHE_SYNC_HTTP_CLIENT_FACTORY.getClass().getName()); @@ -115,7 +115,7 @@ private static void shutdown() { SRV_LOG.error("[NamingServerHttpClientManager] An exception occurred when the HTTP client was closed : {}", ExceptionUtil.getStackTrace(ex)); } - SRV_LOG.warn("[NamingServerHttpClientManager] Destruction of the end"); + SRV_LOG.info("[NamingServerHttpClientManager] Completed destruction of HTTP-Client"); } private static class AsyncHttpClientFactory extends AbstractHttpClientFactory { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/model/form/ServiceForm.java b/naming/src/main/java/com/alibaba/nacos/naming/model/form/ServiceForm.java index e3895cb4cdf..8422e3eb5f5 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/model/form/ServiceForm.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/model/form/ServiceForm.java @@ -20,9 +20,9 @@ import com.alibaba.nacos.api.exception.api.NacosApiException; import com.alibaba.nacos.api.model.v2.ErrorCode; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.model.form.NacosForm; import org.springframework.http.HttpStatus; -import java.io.Serializable; import java.util.Objects; /** @@ -30,7 +30,7 @@ * @author dongyafei * @date 2022/9/7 */ -public class ServiceForm implements Serializable { +public class ServiceForm implements NacosForm { private static final long serialVersionUID = -4905650083916616115L; @@ -51,11 +51,7 @@ public class ServiceForm implements Serializable { public ServiceForm() { } - /** - * check param. - * - * @throws NacosApiException NacosApiException - */ + @Override public void validate() throws NacosApiException { fillDefaultValue(); if (StringUtils.isBlank(serviceName)) { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/model/form/ServiceListForm.java b/naming/src/main/java/com/alibaba/nacos/naming/model/form/ServiceListForm.java new file mode 100644 index 00000000000..51dd0ec2e69 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/model/form/ServiceListForm.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.naming.model.form; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.model.form.NacosForm; + +/** + * Nacos HTTP service list API form. + * + * @author xiweng.yy + */ +public class ServiceListForm implements NacosForm { + + private static final long serialVersionUID = 541715462458894942L; + + private String namespaceId = Constants.DEFAULT_NAMESPACE_ID; + + private String serviceNameParam = StringUtils.EMPTY; + + private String groupNameParam = StringUtils.EMPTY; + + private boolean hasIpCount; + + boolean withInstances; + + @Override + public void validate() throws NacosApiException { + if (StringUtils.isBlank(namespaceId)) { + namespaceId = Constants.DEFAULT_NAMESPACE_ID; + } + } + + public String getNamespaceId() { + return namespaceId; + } + + public void setNamespaceId(String namespaceId) { + this.namespaceId = namespaceId; + } + + public String getServiceNameParam() { + return serviceNameParam; + } + + public void setServiceNameParam(String serviceNameParam) { + this.serviceNameParam = serviceNameParam; + } + + public String getGroupNameParam() { + return groupNameParam; + } + + public void setGroupNameParam(String groupNameParam) { + this.groupNameParam = groupNameParam; + } + + public boolean isHasIpCount() { + return hasIpCount; + } + + public void setHasIpCount(boolean hasIpCount) { + this.hasIpCount = hasIpCount; + } + + public boolean isWithInstances() { + return withInstances; + } + + public void setWithInstances(boolean withInstances) { + this.withInstances = withInstances; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/model/form/UpdateClusterForm.java b/naming/src/main/java/com/alibaba/nacos/naming/model/form/UpdateClusterForm.java new file mode 100644 index 00000000000..0fe19b6da69 --- /dev/null +++ b/naming/src/main/java/com/alibaba/nacos/naming/model/form/UpdateClusterForm.java @@ -0,0 +1,151 @@ +/* + * 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.naming.model.form; + +import com.alibaba.nacos.api.common.Constants; +import com.alibaba.nacos.api.exception.api.NacosApiException; +import com.alibaba.nacos.api.model.v2.ErrorCode; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.model.form.NacosForm; +import org.springframework.http.HttpStatus; + +/** + * Nacos HTTP update cluster API form. + * + * @author xiweng.yy + */ +public class UpdateClusterForm implements NacosForm { + + private static final long serialVersionUID = 4724672496526879919L; + + private String namespaceId = Constants.DEFAULT_NAMESPACE_ID; + + private String groupName; + + private String serviceName; + + private String clusterName; + + private Integer checkPort; + + private Boolean useInstancePort4Check; + + private String healthChecker; + + private String metadata; + + @Override + public void validate() throws NacosApiException { + if (StringUtils.isBlank(serviceName)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'serviceName' type String is not present"); + } + if (StringUtils.isBlank(clusterName)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'clusterName' type String is not present"); + } + if (null == checkPort) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'checkPort' type Integer is not present"); + } + if (null == useInstancePort4Check) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'useInstancePort4Check' type Boolean is not present"); + } + if (StringUtils.isEmpty(healthChecker)) { + throw new NacosApiException(HttpStatus.BAD_REQUEST.value(), ErrorCode.PARAMETER_MISSING, + "Required parameter 'healthChecker' type String is not present"); + } + fillDefaultValue(); + } + + private void fillDefaultValue() { + if (StringUtils.isEmpty(namespaceId)) { + namespaceId = Constants.DEFAULT_NAMESPACE_ID; + } + if (StringUtils.isEmpty(groupName)) { + groupName = Constants.DEFAULT_GROUP; + } + if (null == metadata) { + metadata = StringUtils.EMPTY; + } + } + + public String getNamespaceId() { + return namespaceId; + } + + public void setNamespaceId(String namespaceId) { + this.namespaceId = namespaceId; + } + + public String getGroupName() { + return groupName; + } + + public void setGroupName(String groupName) { + this.groupName = groupName; + } + + public String getServiceName() { + return serviceName; + } + + public void setServiceName(String serviceName) { + this.serviceName = serviceName; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public Integer getCheckPort() { + return checkPort; + } + + public void setCheckPort(Integer checkPort) { + this.checkPort = checkPort; + } + + public Boolean isUseInstancePort4Check() { + return useInstancePort4Check; + } + + public void setUseInstancePort4Check(Boolean useInstancePort4Check) { + this.useInstancePort4Check = useInstancePort4Check; + } + + public String getHealthChecker() { + return healthChecker; + } + + public void setHealthChecker(String healthChecker) { + this.healthChecker = healthChecker; + } + + public String getMetadata() { + return metadata; + } + + public void setMetadata(String metadata) { + this.metadata = metadata; + } +} diff --git a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/DistroDataRequestHandler.java b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/DistroDataRequestHandler.java index 765b06b8049..3b589b02a31 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/DistroDataRequestHandler.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/remote/rpc/handler/DistroDataRequestHandler.java @@ -20,6 +20,7 @@ import com.alibaba.nacos.api.remote.RemoteConstants; import com.alibaba.nacos.api.remote.request.RequestMeta; import com.alibaba.nacos.api.remote.response.ResponseCode; +import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.core.distributed.distro.DistroProtocol; import com.alibaba.nacos.core.distributed.distro.entity.DistroData; import com.alibaba.nacos.core.distributed.distro.entity.DistroKey; @@ -29,6 +30,7 @@ import com.alibaba.nacos.naming.cluster.remote.response.DistroDataResponse; import com.alibaba.nacos.naming.consistency.ephemeral.distro.v2.DistroClientDataProcessor; import com.alibaba.nacos.naming.misc.Loggers; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import org.springframework.stereotype.Component; /** @@ -47,6 +49,7 @@ public DistroDataRequestHandler(DistroProtocol distroProtocol) { } @Override + @Secured(apiType = ApiType.INNER_API) public DistroDataResponse handle(DistroDataRequest request, RequestMeta meta) throws NacosException { try { switch (request.getDataOperation()) { diff --git a/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java index 093530b9b38..57774a48bd5 100644 --- a/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java +++ b/naming/src/main/java/com/alibaba/nacos/naming/web/NamingConfig.java @@ -16,16 +16,21 @@ package com.alibaba.nacos.naming.web; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.core.web.NacosWebBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import javax.annotation.PostConstruct; + /** * Naming spring configuration. * * @author nkorange */ @Configuration +@NacosWebBean public class NamingConfig { private static final String URL_PATTERNS = "/v1/ns/*"; @@ -40,7 +45,16 @@ public class NamingConfig { private static final String CLIENT_ATTRIBUTES_FILTER = "clientAttributes_filter"; - private static final String NAMING_PARAM_CHECK_FILTER = "namingparamCheckFilter"; + private final ControllerMethodsCache methodsCache; + + public NamingConfig(ControllerMethodsCache methodsCache) { + this.methodsCache = methodsCache; + } + + @PostConstruct + public void init() { + methodsCache.initClassMethod("com.alibaba.nacos.naming.controllers"); + } @Bean public FilterRegistrationBean distroFilterRegistration() { diff --git a/naming/src/test/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManagerTest.java b/naming/src/test/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManagerTest.java index ac6e6aed2fd..99383753b1c 100644 --- a/naming/src/test/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManagerTest.java +++ b/naming/src/test/java/com/alibaba/nacos/naming/core/v2/metadata/NamingMetadataManagerTest.java @@ -170,6 +170,10 @@ void testRemoveInstanceMetadata() { Optional instanceMetadata = namingMetadataManager.getInstanceMetadata(service, METADATA_ID); assertFalse(instanceMetadata.isPresent()); + + Map> instanceMetadataSnapshot = namingMetadataManager.getInstanceMetadataSnapshot(); + + assertEquals(0, instanceMetadataSnapshot.size()); } @Test @@ -233,4 +237,4 @@ void testOnEvent() { namingMetadataManager.onEvent(clientDisconnectEvent); Mockito.verify(clientDisconnectEvent).getClient(); } -} \ No newline at end of file +} diff --git a/persistence/src/test/resources/META-INF/derby-schema.sql b/persistence/src/test/resources/META-INF/derby-schema.sql index 601d38ab2e6..6a2392369ca 100644 --- a/persistence/src/test/resources/META-INF/derby-schema.sql +++ b/persistence/src/test/resources/META-INF/derby-schema.sql @@ -1,4 +1,4 @@ -build/* +/* * Copyright 1999-2018 Alibaba Group Holding Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,46 +17,48 @@ build/* CREATE SCHEMA nacos AUTHORIZATION nacos; CREATE TABLE config_info ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128) DEFAULT NULL, - src_ip varchar(50) DEFAULT NULL, - c_desc varchar(256) DEFAULT NULL, - c_use varchar(64) DEFAULT NULL, - effect varchar(64) DEFAULT NULL, - type varchar(64) DEFAULT NULL, - c_schema LONG VARCHAR DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint configinfo_id_key PRIMARY KEY (id), - constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128) DEFAULT NULL, + src_ip varchar(50) DEFAULT NULL, + c_desc varchar(256) DEFAULT NULL, + c_use varchar(64) DEFAULT NULL, + effect varchar(64) DEFAULT NULL, + type varchar(64) DEFAULT NULL, + c_schema LONG VARCHAR DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint configinfo_id_key PRIMARY KEY (id), + constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id); CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id); CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id); CREATE TABLE his_config_info ( - id bigint NOT NULL, - nid bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - op_type char(10) DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); + id bigint NOT NULL, + nid bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + publish_type varchar(50) DEFAULT 'formal', + ext_info CLOB, + op_type char(10) DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id); CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create); @@ -64,164 +66,174 @@ CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified); CREATE TABLE config_info_beta ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - beta_ips varchar(1024), - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint configinfobeta_id_key PRIMARY KEY (id), - constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + beta_ips varchar(1024), + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint configinfobeta_id_key PRIMARY KEY (id), + constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); CREATE TABLE config_info_tag ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - tag_id varchar(128) NOT NULL, - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - constraint configinfotag_id_key PRIMARY KEY (id), - constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); - -CREATE TABLE config_info_aggr ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - datum_id varchar(255) NOT NULL, - app_name varchar(128), - content CLOB, - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - constraint configinfoaggr_id_key PRIMARY KEY (id), - constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + tag_id varchar(128) NOT NULL, + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + constraint configinfotag_id_key PRIMARY KEY (id), + constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); + +CREATE TABLE config_info_gray ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + gray_name varchar(128) NOT NULL, + gray_rule CLOB, + app_name varchar(128), + src_ip varchar(128), + src_user varchar(128) default '', + content CLOB, + md5 varchar(32) DEFAULT NULL, + encrypted_data_key varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfogray_id_key PRIMARY KEY (id), + constraint uk_configinfogray_datagrouptenantgrayname UNIQUE (data_id,group_id,tenant_id,gray_name)); +CREATE INDEX config_info_gray_dataid_gmt_modified ON config_info_gray(data_id,gmt_modified); +CREATE INDEX config_info_gray_gmt_modified ON config_info_gray(gmt_modified); CREATE TABLE app_list ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - is_dynamic_collect_disabled smallint DEFAULT 0, - last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', - sub_info_lock_owner varchar(128), - sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', - constraint applist_id_key PRIMARY KEY (id), - constraint uk_appname UNIQUE (app_name)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + is_dynamic_collect_disabled smallint DEFAULT 0, + last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', + sub_info_lock_owner varchar(128), + sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', + constraint applist_id_key PRIMARY KEY (id), + constraint uk_appname UNIQUE (app_name)); CREATE TABLE app_configdata_relation_subs ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint configdatarelationsubs_id_key PRIMARY KEY (id), - constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationsubs_id_key PRIMARY KEY (id), + constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); CREATE TABLE app_configdata_relation_pubs ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint configdatarelationpubs_id_key PRIMARY KEY (id), - constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationpubs_id_key PRIMARY KEY (id), + constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); CREATE TABLE config_tags_relation ( - id bigint NOT NULL, - tag_name varchar(128) NOT NULL, - tag_type varchar(64) DEFAULT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) DEFAULT '', - nid bigint NOT NULL generated by default as identity, - constraint config_tags_id_key PRIMARY KEY (nid), - constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); + id bigint NOT NULL, + tag_name varchar(128) NOT NULL, + tag_type varchar(64) DEFAULT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + nid bigint NOT NULL generated by default as identity, + constraint config_tags_id_key PRIMARY KEY (nid), + constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id); CREATE TABLE group_capacity ( - id bigint NOT NULL generated by default as identity, - group_id varchar(128) DEFAULT '', - quota int DEFAULT 0, - usage int DEFAULT 0, - max_size int DEFAULT 0, - max_aggr_count int DEFAULT 0, - max_aggr_size int DEFAULT 0, - max_history_count int DEFAULT 0, - gmt_create timestamp DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint group_capacity_id_key PRIMARY KEY (id), - constraint uk_group_id UNIQUE (group_id)); + id bigint NOT NULL generated by default as identity, + group_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint group_capacity_id_key PRIMARY KEY (id), + constraint uk_group_id UNIQUE (group_id)); CREATE TABLE tenant_capacity ( - id bigint NOT NULL generated by default as identity, - tenant_id varchar(128) DEFAULT '', - quota int DEFAULT 0, - usage int DEFAULT 0, - max_size int DEFAULT 0, - max_aggr_count int DEFAULT 0, - max_aggr_size int DEFAULT 0, - max_history_count int DEFAULT 0, - gmt_create timestamp DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint tenant_capacity_id_key PRIMARY KEY (id), - constraint uk_tenant_id UNIQUE (tenant_id)); + id bigint NOT NULL generated by default as identity, + tenant_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint tenant_capacity_id_key PRIMARY KEY (id), + constraint uk_tenant_id UNIQUE (tenant_id)); CREATE TABLE tenant_info ( - id bigint NOT NULL generated by default as identity, - kp varchar(128) NOT NULL, - tenant_id varchar(128) DEFAULT '', - tenant_name varchar(128) DEFAULT '', - tenant_desc varchar(256) DEFAULT NULL, - create_source varchar(32) DEFAULT NULL, - gmt_create bigint NOT NULL, - gmt_modified bigint NOT NULL, - constraint tenant_info_id_key PRIMARY KEY (id), - constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); + id bigint NOT NULL generated by default as identity, + kp varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + tenant_name varchar(128) DEFAULT '', + tenant_desc varchar(256) DEFAULT NULL, + create_source varchar(32) DEFAULT NULL, + gmt_create bigint NOT NULL, + gmt_modified bigint NOT NULL, + constraint tenant_info_id_key PRIMARY KEY (id), + constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id); CREATE TABLE users ( - username varchar(50) NOT NULL PRIMARY KEY, - password varchar(500) NOT NULL, - enabled boolean NOT NULL DEFAULT true + username varchar(50) NOT NULL PRIMARY KEY, + password varchar(500) NOT NULL, + enabled boolean NOT NULL DEFAULT true ); CREATE TABLE roles ( - username varchar(50) NOT NULL, - role varchar(50) NOT NULL, - constraint uk_username_role UNIQUE (username,role) + username varchar(50) NOT NULL, + role varchar(50) NOT NULL, + constraint uk_username_role UNIQUE (username,role) ); CREATE TABLE permissions ( - role varchar(50) NOT NULL, - resource varchar(512) NOT NULL, - action varchar(8) NOT NULL, - constraint uk_role_permission UNIQUE (role,resource,action) + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) ); + /******************************************/ /* ipv6 support */ /******************************************/ -ALTER TABLE `config_info_tag` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `his_config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `src_user`; +ALTER TABLE config_info_tag ADD src_ip varchar(50) DEFAULT NULL; + +ALTER TABLE his_config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info_beta ADD src_ip varchar(50) DEFAULT NULL ; -ALTER TABLE `config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `config_info_beta` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; \ No newline at end of file +ALTER TABLE his_config_info ADD publish_type varchar(50) DEFAULT 'formal'; +ALTER TABLE his_config_info ADD ext_info CLOB DEFAULT NULL ; diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/CustomAuthenticationProvider.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/CustomAuthenticationProvider.java deleted file mode 100644 index 5a84b414362..00000000000 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/CustomAuthenticationProvider.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * 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.plugin.auth.impl; - -import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationProvider; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.userdetails.UserDetails; -import org.springframework.stereotype.Component; - -/** - * auth provider. - * - * @author wfnuser - */ -@Component -@Deprecated -public class CustomAuthenticationProvider implements AuthenticationProvider { - - @Autowired - private NacosUserDetailsServiceImpl userDetailsService; - - @Override - public Authentication authenticate(Authentication authentication) throws AuthenticationException { - - String username = (String) authentication.getPrincipal(); - String password = (String) authentication.getCredentials(); - UserDetails userDetails = userDetailsService.loadUserByUsername(username); - - if (!password.equals(userDetails.getPassword())) { - return new UsernamePasswordAuthenticationToken(username, null, null); - } - return null; - } - - @Override - public boolean supports(Class aClass) { - return aClass.equals(UsernamePasswordAuthenticationToken.class); - } - -} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtAuthenticationEntryPoint.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtAuthenticationEntryPoint.java index 2c919a82032..4d1f14f26fc 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtAuthenticationEntryPoint.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/JwtAuthenticationEntryPoint.java @@ -20,7 +20,6 @@ import org.slf4j.LoggerFactory; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; -import org.springframework.stereotype.Component; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -32,7 +31,6 @@ * * @author wfnuser */ -@Component @Deprecated public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint { diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthConfig.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthConfig.java deleted file mode 100644 index e956b402a93..00000000000 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthConfig.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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.plugin.auth.impl; - -import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.core.code.ControllerMethodsCache; -import com.alibaba.nacos.plugin.auth.impl.authenticate.AuthenticationManagerDelegator; -import com.alibaba.nacos.plugin.auth.impl.authenticate.DefaultAuthenticationManager; -import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; -import com.alibaba.nacos.plugin.auth.impl.authenticate.LdapAuthenticationManager; -import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; -import com.alibaba.nacos.plugin.auth.impl.filter.JwtAuthenticationTokenFilter; -import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; -import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; -import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; -import com.alibaba.nacos.sys.utils.ApplicationUtils; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Configuration; -import org.springframework.context.annotation.Primary; -import org.springframework.core.env.Environment; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.config.BeanIds; -import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; -import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; -import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter; -import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; -import org.springframework.security.config.annotation.web.builders.HttpSecurity; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; -import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer; -import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer; -import org.springframework.security.config.http.SessionCreationPolicy; -import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.security.web.SecurityFilterChain; -import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; -import org.springframework.web.cors.CorsUtils; - -import javax.annotation.PostConstruct; - -/** - * Spring security config. - * - * @author Nacos - */ -@Configuration -@EnableMethodSecurity -public class NacosAuthConfig { - - private static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ","; - - private static final String LOGIN_ENTRY_POINT = "/v1/auth/login"; - - private static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/v1/auth/**"; - - private static final String DEFAULT_ALL_PATH_PATTERN = "/**"; - - private static final String PROPERTY_IGNORE_URLS = "nacos.security.ignore.urls"; - - private final Environment env; - - private final TokenManagerDelegate tokenProvider; - - private final AuthConfigs authConfigs; - - private final NacosUserDetailsServiceImpl userDetailsService; - - private final LdapAuthenticationProvider ldapAuthenticationProvider; - - private final ControllerMethodsCache methodsCache; - - public NacosAuthConfig(Environment env, TokenManagerDelegate tokenProvider, AuthConfigs authConfigs, - NacosUserDetailsServiceImpl userDetailsService, - ObjectProvider ldapAuthenticationProvider, - ControllerMethodsCache methodsCache) { - - this.env = env; - this.tokenProvider = tokenProvider; - this.authConfigs = authConfigs; - this.userDetailsService = userDetailsService; - this.ldapAuthenticationProvider = ldapAuthenticationProvider.getIfAvailable(); - this.methodsCache = methodsCache; - - } - - /** - * Init. - */ - @PostConstruct - public void init() { - methodsCache.initClassMethod("com.alibaba.nacos.plugin.auth.impl.controller"); - } - - @Bean(name = BeanIds.AUTHENTICATION_MANAGER) - public AuthenticationManager authenticationManagerBean() throws Exception { - AuthenticationConfiguration authenticationConfiguration = ApplicationUtils.getBean( - AuthenticationConfiguration.class); - return authenticationConfiguration.getAuthenticationManager(); - } - - @Bean - public WebSecurityCustomizer webSecurityCustomizer() { - return web -> { - String ignoreUrls = null; - if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { - ignoreUrls = DEFAULT_ALL_PATH_PATTERN; - } else if (AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { - ignoreUrls = DEFAULT_ALL_PATH_PATTERN; - } - if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) { - ignoreUrls = env.getProperty(PROPERTY_IGNORE_URLS, DEFAULT_ALL_PATH_PATTERN); - } - if (StringUtils.isNotBlank(ignoreUrls)) { - for (String each : ignoreUrls.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) { - // switch this to springboot3 syntax - web.ignoring().requestMatchers(each.trim()); - } - } - }; - } - - @Bean - public GlobalAuthenticationConfigurerAdapter authenticationConfigurer() { - return new GlobalAuthenticationConfigurerAdapter() { - @Override - public void init(AuthenticationManagerBuilder auth) throws Exception { - if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { - auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); - } else if (AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { - auth.authenticationProvider(ldapAuthenticationProvider); - } - } - }; - } - - @Bean - public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { - if (StringUtils.isBlank(authConfigs.getNacosAuthSystemType())) { - // switch this to springboot3 syntax - http.csrf(AbstractHttpConfigurer::disable) - // We don't need CSRF for JWT based authentication - .cors(AbstractHttpConfigurer::disable) - .sessionManagement(sess -> sess.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) - .authorizeHttpRequests(auth -> { - auth.requestMatchers(CorsUtils::isPreFlightRequest).permitAll(); - auth.requestMatchers(LOGIN_ENTRY_POINT).permitAll(); - auth.requestMatchers(TOKEN_BASED_AUTH_ENTRY_POINT).authenticated(); - }) - .exceptionHandling(exp -> exp.authenticationEntryPoint(new JwtAuthenticationEntryPoint())); - // disable cache - http.headers(headers -> headers.cacheControl(HeadersConfigurer.CacheControlConfig::disable)); - - http.addFilterBefore(new JwtAuthenticationTokenFilter(tokenProvider), - UsernamePasswordAuthenticationFilter.class); - } - return http.build(); - } - - @Bean - public PasswordEncoder passwordEncoder() { - return new BCryptPasswordEncoder(); - } - - @Bean - @Primary - public IAuthenticationManager authenticationManager( - ObjectProvider ldapAuthenticatoinManagerObjectProvider, - ObjectProvider defaultAuthenticationManagers, AuthConfigs authConfigs) { - return new AuthenticationManagerDelegator(defaultAuthenticationManagers, - ldapAuthenticatoinManagerObjectProvider, authConfigs); - } - - @Bean - public IAuthenticationManager defaultAuthenticationManager(NacosUserDetailsServiceImpl userDetailsService, - TokenManagerDelegate jwtTokenManager, NacosRoleServiceImpl roleService) { - return new DefaultAuthenticationManager(userDetailsService, jwtTokenManager, roleService); - } -} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthManager.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthManager.java deleted file mode 100644 index 1108621171d..00000000000 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthManager.java +++ /dev/null @@ -1,177 +0,0 @@ -/* - * 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.plugin.auth.impl; - -import com.alibaba.nacos.api.common.Constants; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.core.utils.Loggers; -import com.alibaba.nacos.plugin.auth.api.IdentityContext; -import com.alibaba.nacos.plugin.auth.api.Permission; -import com.alibaba.nacos.plugin.auth.exception.AccessException; -import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; -import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; -import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; -import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; -import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.security.authentication.AuthenticationManager; -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.stereotype.Component; - -import jakarta.servlet.http.HttpServletRequest; -import java.util.List; - -/** - * Builtin access control entry of Nacos. - * - * @author nkorange - * @since 1.2.0 - */ -@Component -@Deprecated -public class NacosAuthManager { - - @Autowired - private TokenManagerDelegate tokenManager; - - @Autowired - private AuthenticationManager authenticationManager; - - @Autowired - private NacosRoleServiceImpl roleService; - - /** - * Authentication of request, identify the user who request the resource. - * - * @param request where we can find the user information - * @return user related to this request, null if no user info is found. - * @throws AccessException if authentication is failed - */ - public NacosUser login(Object request) throws AccessException { - HttpServletRequest req = (HttpServletRequest) request; - String token = resolveToken(req); - validate0(token); - return getNacosUser(token); - } - - NacosUser login(IdentityContext identityContext) throws AccessException { - String token = resolveToken(identityContext); - validate0(token); - return getNacosUser(token); - } - - /** - * Authorization of request, constituted with resource and user. - * - * @param permission permission to auth - * @param user user who wants to access the resource. - * @throws AccessException if authorization is failed - */ - public void auth(Permission permission, NacosUser user) throws AccessException { - if (Loggers.AUTH.isDebugEnabled()) { - Loggers.AUTH.debug("auth permission: {}, user: {}", permission, user); - } - - if (!roleService.hasPermission(user, permission)) { - throw new AccessException("authorization failed!"); - } - } - - /** - * Get token from header. - */ - private String resolveToken(HttpServletRequest request) throws AccessException { - String bearerToken = request.getHeader(AuthConstants.AUTHORIZATION_HEADER); - if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(AuthConstants.TOKEN_PREFIX)) { - return bearerToken.substring(7); - } - bearerToken = request.getParameter(Constants.ACCESS_TOKEN); - if (StringUtils.isBlank(bearerToken)) { - String userName = request.getParameter(AuthConstants.PARAM_USERNAME); - String password = request.getParameter(AuthConstants.PARAM_PASSWORD); - bearerToken = resolveTokenFromUser(userName, password); - } - - return bearerToken; - } - - private String resolveToken(IdentityContext identityContext) throws AccessException { - String bearerToken = identityContext.getParameter(AuthConstants.AUTHORIZATION_HEADER, StringUtils.EMPTY); - if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(AuthConstants.TOKEN_PREFIX)) { - return bearerToken.substring(7); - } - bearerToken = identityContext.getParameter(Constants.ACCESS_TOKEN, StringUtils.EMPTY); - if (StringUtils.isBlank(bearerToken)) { - String userName = (String) identityContext.getParameter(AuthConstants.PARAM_USERNAME); - String password = (String) identityContext.getParameter(AuthConstants.PARAM_PASSWORD); - bearerToken = resolveTokenFromUser(userName, password); - } - return bearerToken; - } - - private String resolveTokenFromUser(String userName, String rawPassword) throws AccessException { - String finalName; - Authentication authenticate; - try { - UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(userName, - rawPassword); - authenticate = authenticationManager.authenticate(authenticationToken); - } catch (AuthenticationException e) { - throw new AccessException("unknown user!"); - } - - if (null == authenticate || StringUtils.isBlank(authenticate.getName())) { - finalName = userName; - } else { - finalName = authenticate.getName(); - } - - return tokenManager.createToken(finalName); - } - - private void validate0(String token) throws AccessException { - if (StringUtils.isBlank(token)) { - throw new AccessException("user not found!"); - } - - tokenManager.validateToken(token); - - } - - private NacosUser getNacosUser(String token) throws AccessException { - Authentication authentication = tokenManager.getAuthentication(token); - SecurityContextHolder.getContext().setAuthentication(authentication); - - String username = authentication.getName(); - NacosUser user = new NacosUser(); - user.setUserName(username); - user.setToken(token); - List roleInfoList = roleService.getRoles(username); - if (roleInfoList != null) { - for (RoleInfo roleInfo : roleInfoList) { - if (roleInfo.getRole().equals(AuthConstants.GLOBAL_ADMIN_ROLE)) { - user.setGlobalAdmin(true); - break; - } - } - } - return user; - } -} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java index eef3a3e7902..f71349b5cae 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosAuthPluginService.java @@ -24,7 +24,6 @@ import com.alibaba.nacos.plugin.auth.api.Resource; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; import com.alibaba.nacos.plugin.auth.exception.AccessException; -import com.alibaba.nacos.plugin.auth.impl.authenticate.DefaultAuthenticationManager; import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; @@ -43,9 +42,6 @@ @SuppressWarnings("PMD.ServiceOrDaoClassShouldEndWithImplRule") public class NacosAuthPluginService implements AuthPluginService { - @Deprecated - private static final String USER_IDENTITY_PARAM_KEY = "user"; - private static final List IDENTITY_NAMES = new LinkedList() { { add(AuthConstants.AUTHORIZATION_HEADER); @@ -109,7 +105,7 @@ public String getAuthServiceName() { @Override public boolean isLoginEnabled() { - return ApplicationUtils.getBean(AuthConfigs.class).isAuthEnabled(); + return ApplicationUtils.getBean(AuthConfigs.class).isConsoleAuthEnabled(); } /** @@ -119,14 +115,15 @@ public boolean isLoginEnabled() { */ @Override public boolean isAdminRequest() { - boolean authEnabled = ApplicationUtils.getBean(AuthConfigs.class).isAuthEnabled(); + AuthConfigs authConfigs = ApplicationUtils.getBean(AuthConfigs.class); + boolean authEnabled = authConfigs.isConsoleAuthEnabled() || authConfigs.isAuthEnabled(); boolean hasGlobalAdminRole = ApplicationUtils.getBean(IAuthenticationManager.class).hasGlobalAdminRole(); return authEnabled && !hasGlobalAdminRole; } protected void checkNacosAuthManager() { if (null == authenticationManager) { - authenticationManager = ApplicationUtils.getBean(DefaultAuthenticationManager.class); + authenticationManager = ApplicationUtils.getBean(IAuthenticationManager.class); } } } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/authenticate/AuthenticationManagerDelegator.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/authenticate/AuthenticationManagerDelegator.java deleted file mode 100644 index 42a8251955c..00000000000 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/authenticate/AuthenticationManagerDelegator.java +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 1999-2022 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.plugin.auth.impl.authenticate; - -import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.plugin.auth.api.Permission; -import com.alibaba.nacos.plugin.auth.exception.AccessException; -import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; -import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; -import org.springframework.beans.factory.ObjectProvider; - -import jakarta.servlet.http.HttpServletRequest; - -/** - * Authentication Proxy. - * - * @author Weizhan▪Yun - * @date 2023/1/12 23:31 - */ -public class AuthenticationManagerDelegator implements IAuthenticationManager { - - private ObjectProvider defaultAuthenticationManager; - - private ObjectProvider ldapAuthenticationManager; - - private AuthConfigs authConfigs; - - public AuthenticationManagerDelegator(ObjectProvider nacosAuthManager, - ObjectProvider ldapAuthenticationProvider, AuthConfigs authConfigs) { - this.defaultAuthenticationManager = nacosAuthManager; - this.ldapAuthenticationManager = ldapAuthenticationProvider; - this.authConfigs = authConfigs; - } - - @Override - public NacosUser authenticate(String username, String password) throws AccessException { - return getManager().authenticate(username, password); - } - - @Override - public NacosUser authenticate(String jwtToken) throws AccessException { - return getManager().authenticate(jwtToken); - } - - @Override - public NacosUser authenticate(HttpServletRequest httpServletRequest) throws AccessException { - return getManager().authenticate(httpServletRequest); - } - - @Override - public void authorize(Permission permission, NacosUser nacosUser) throws AccessException { - getManager().authorize(permission, nacosUser); - } - - @Override - public boolean hasGlobalAdminRole(String username) { - return getManager().hasGlobalAdminRole(username); - } - - @Override - public boolean hasGlobalAdminRole() { - return getManager().hasGlobalAdminRole(); - } - - @Override - public boolean hasGlobalAdminRole(NacosUser nacosUser) { - return getManager().hasGlobalAdminRole(nacosUser); - } - - private IAuthenticationManager getManager() { - if (AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { - return ldapAuthenticationManager.getIfAvailable(); - } - - return defaultAuthenticationManager.getIfAvailable(); - } -} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/configuration/NacosAuthPluginConfig.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/configuration/NacosAuthPluginConfig.java new file mode 100644 index 00000000000..b521f1a0b8e --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/configuration/NacosAuthPluginConfig.java @@ -0,0 +1,110 @@ +/* + * 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.plugin.auth.impl.configuration; + +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.core.auth.NacosServerAuthConfig; +import com.alibaba.nacos.core.code.ControllerMethodsCache; +import com.alibaba.nacos.plugin.auth.impl.authenticate.DefaultAuthenticationManager; +import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.token.TokenManager; +import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; +import com.alibaba.nacos.plugin.auth.impl.token.impl.CachedJwtTokenManager; +import com.alibaba.nacos.plugin.auth.impl.token.impl.JwtTokenManager; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +import javax.annotation.PostConstruct; + +/** + * Spring security config. + * + * @author Nacos + */ +@Configuration +public class NacosAuthPluginConfig { + + private final NacosUserDetailsServiceImpl userDetailsService; + + private final ControllerMethodsCache methodsCache; + + public NacosAuthPluginConfig(NacosUserDetailsServiceImpl userDetailsService, ControllerMethodsCache methodsCache) { + this.userDetailsService = userDetailsService; + this.methodsCache = methodsCache; + + } + + /** + * Init. + */ + @PostConstruct + public void init() { + methodsCache.initClassMethod("com.alibaba.nacos.plugin.auth.impl.controller"); + } + + @Bean + @ConditionalOnMissingBean + public GlobalAuthenticationConfigurerAdapter authenticationConfigurer() { + return new GlobalAuthenticationConfigurerAdapter() { + @Override + public void init(AuthenticationManagerBuilder auth) throws Exception { + if (AuthSystemTypes.NACOS.name() + .equalsIgnoreCase(NacosServerAuthConfig.getInstance().getNacosAuthSystemType())) { + auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); + } + } + }; + } + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + + @Bean + @ConditionalOnMissingBean + public IAuthenticationManager defaultAuthenticationManager(NacosUserDetailsServiceImpl userDetailsService, + TokenManagerDelegate jwtTokenManager, NacosRoleServiceImpl roleService) { + return new DefaultAuthenticationManager(userDetailsService, jwtTokenManager, roleService); + } + + @Bean + @ConditionalOnProperty(value = TokenManagerDelegate.NACOS_AUTH_TOKEN_CACHING_ENABLED, havingValue = "false", matchIfMissing = true) + public TokenManager tokenManager(AuthConfigs authConfigs) { + return new JwtTokenManager(authConfigs); + } + + @Bean + @ConditionalOnProperty(value = TokenManagerDelegate.NACOS_AUTH_TOKEN_CACHING_ENABLED, havingValue = "true") + public TokenManager cachedTokenManager(AuthConfigs authConfigs) { + return new CachedJwtTokenManager(new JwtTokenManager(authConfigs)); + } + + @Bean + public TokenManagerDelegate tokenManagerDelegate(TokenManager tokenManager) { + return new TokenManagerDelegate(tokenManager); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/configuration/NacosAuthPluginWebConfig.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/configuration/NacosAuthPluginWebConfig.java new file mode 100644 index 00000000000..e4660f1d024 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/configuration/NacosAuthPluginWebConfig.java @@ -0,0 +1,83 @@ +/* + * 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.plugin.auth.impl.configuration; + +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.auth.NacosServerAuthConfig; +import com.alibaba.nacos.core.web.NacosWebBean; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; +import com.alibaba.nacos.sys.env.EnvUtil; +import com.alibaba.nacos.sys.utils.ApplicationUtils; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.BeanIds; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.web.SecurityFilterChain; + +/** + * Nacos Auth http config. + * + * @author xiweng.yy + */ +@Configuration +@NacosWebBean +@EnableWebSecurity +public class NacosAuthPluginWebConfig { + + private static final String SECURITY_IGNORE_URLS_SPILT_CHAR = ","; + + private static final String DEFAULT_ALL_PATH_PATTERN = "/**"; + + private static final String PROPERTY_IGNORE_URLS = "nacos.security.ignore.urls"; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + String ignoreUrls = null; + String authSystemTYpe = NacosServerAuthConfig.getInstance().getNacosAuthSystemType(); + if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authSystemTYpe)) { + ignoreUrls = DEFAULT_ALL_PATH_PATTERN; + } else if (AuthSystemTypes.LDAP.name().equalsIgnoreCase(authSystemTYpe)) { + ignoreUrls = DEFAULT_ALL_PATH_PATTERN; + } + if (StringUtils.isBlank(authSystemTYpe)) { + ignoreUrls = EnvUtil.getProperty(PROPERTY_IGNORE_URLS, DEFAULT_ALL_PATH_PATTERN); + } + if (StringUtils.isBlank(ignoreUrls)) { + return http.build(); + } + return http.authorizeHttpRequests().requestMatchers(ignoreUrls.trim().split(SECURITY_IGNORE_URLS_SPILT_CHAR)) + .permitAll().and().csrf().disable().build(); + } + + /** + * Build required {@link AuthenticationManager} for v1 auth API. + * + * @return spring security default authentication manager + * @throws Exception any exception during build authentication manager. + * @deprecated will be removed after v1 auth API removed. + */ + @Bean(name = BeanIds.AUTHENTICATION_MANAGER) + @Deprecated() + public AuthenticationManager authenticationManagerBean() throws Exception { + AuthenticationConfiguration authenticationConfiguration = ApplicationUtils.getBean( + AuthenticationConfiguration.class); + return authenticationConfiguration.getAuthenticationManager(); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/constant/AuthConstants.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/constant/AuthConstants.java index 56d6dd75a5b..dd86a4f509b 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/constant/AuthConstants.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/constant/AuthConstants.java @@ -45,6 +45,8 @@ public class AuthConstants { public static final String UPDATE_PASSWORD_ENTRY_POINT = CONSOLE_RESOURCE_NAME_PREFIX + "user/password"; + public static final String LOCK_OPERATOR_POINT = "grpc/lock"; + public static final String NACOS_USER_KEY = "nacosuser"; public static final String TOKEN_SECRET_KEY = "nacos.core.auth.plugin.nacos.token.secret.key"; diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionController.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionController.java index 1392f7d2129..a029362e20f 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionController.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionController.java @@ -16,11 +16,14 @@ package com.alibaba.nacos.plugin.auth.impl.controller; +import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.model.RestResultUtils; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; @@ -55,6 +58,7 @@ public class PermissionController { */ @GetMapping(params = "search=accurate") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/permission/list") public Object getPermissions(@RequestParam int pageNo, @RequestParam int pageSize, @RequestParam(name = "role", defaultValue = StringUtils.EMPTY) String role) { return nacosRoleService.getPermissionsFromDatabase(role, pageNo, pageSize); @@ -70,6 +74,7 @@ public Object getPermissions(@RequestParam int pageNo, @RequestParam int pageSiz */ @GetMapping(params = "search=blur") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/permission/list") public Page fuzzySearchPermission(@RequestParam int pageNo, @RequestParam int pageSize, @RequestParam(name = "role", defaultValue = StringUtils.EMPTY) String role) { return nacosRoleService.findPermissionsLike4Page(role, pageNo, pageSize); @@ -85,6 +90,7 @@ public Page fuzzySearchPermission(@RequestParam int pageNo, @Req */ @PostMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "POST ${contextPath:nacos}/v3/auth/permission") public Object addPermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { nacosRoleService.addPermission(role, resource, action); return RestResultUtils.success("add permission ok!"); @@ -100,9 +106,25 @@ public Object addPermission(@RequestParam String role, @RequestParam String reso */ @DeleteMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "DELETE ${contextPath:nacos}/v3/auth/permission") public Object deletePermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { nacosRoleService.deletePermission(role, resource, action); return RestResultUtils.success("delete permission ok!"); } + + /** + * Judge whether a permission is duplicate. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if duplicate, false otherwise + */ + @GetMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/permission") + public Result isDuplicatePermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { + return nacosRoleService.isDuplicatePermission(role, resource, action); + } } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/RoleController.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/RoleController.java index edc1564b3e8..57f40224047 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/RoleController.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/RoleController.java @@ -19,8 +19,10 @@ import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.common.model.RestResultUtils; import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; @@ -58,6 +60,7 @@ public class RoleController { */ @GetMapping(params = "search=accurate") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/role/list") public Object getRoles(@RequestParam int pageNo, @RequestParam int pageSize, @RequestParam(name = "username", defaultValue = "") String username, @RequestParam(name = "role", defaultValue = "") String role) { @@ -74,6 +77,7 @@ public Object getRoles(@RequestParam int pageNo, @RequestParam int pageSize, */ @GetMapping(params = "search=blur") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/role/list") public Page fuzzySearchRole(@RequestParam int pageNo, @RequestParam int pageSize, @RequestParam(name = "username", defaultValue = "") String username, @RequestParam(name = "role", defaultValue = "") String role) { @@ -88,6 +92,7 @@ public Page fuzzySearchRole(@RequestParam int pageNo, @RequestParam in */ @GetMapping("/search") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/role/search") public List searchRoles(@RequestParam String role) { return roleService.findRolesLikeRoleName(role); } @@ -103,6 +108,7 @@ public List searchRoles(@RequestParam String role) { */ @PostMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "POST ${contextPath:nacos}/v3/auth/role") public Object addRole(@RequestParam String role, @RequestParam String username) { roleService.addRole(role, username); return RestResultUtils.success("add role ok!"); @@ -117,6 +123,7 @@ public Object addRole(@RequestParam String role, @RequestParam String username) */ @DeleteMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "DELETE ${contextPath:nacos}/v3/auth/role") public Object deleteRole(@RequestParam String role, @RequestParam(name = "username", defaultValue = StringUtils.EMPTY) String username) { if (StringUtils.isBlank(username)) { diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java index 38378ba581f..e9c49f169a0 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/UserController.java @@ -19,14 +19,15 @@ import com.alibaba.nacos.api.common.Constants; import com.alibaba.nacos.auth.annotation.Secured; import com.alibaba.nacos.auth.config.AuthConfigs; -import com.alibaba.nacos.common.model.RestResult; import com.alibaba.nacos.common.model.RestResultUtils; import com.alibaba.nacos.common.utils.JacksonUtils; import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.core.context.RequestContextHolder; +import com.alibaba.nacos.core.controller.compatibility.Compatibility; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.plugin.auth.api.IdentityContext; import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.constant.ApiType; import com.alibaba.nacos.plugin.auth.exception.AccessException; import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; @@ -47,7 +48,6 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.security.core.userdetails.UserDetails; import org.springframework.web.HttpSessionRequiredException; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -102,6 +102,7 @@ public class UserController { */ @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) @PostMapping + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "POST ${contextPath:nacos}/v3/auth/user") public Object createUser(@RequestParam String username, @RequestParam String password) { if (AuthConstants.DEFAULT_USER.equals(username)) { return RestResultUtils.failed(HttpStatus.CONFLICT.value(), @@ -120,6 +121,7 @@ public Object createUser(@RequestParam String username, @RequestParam String pas * Create a admin user only not exist admin user can use. */ @PostMapping("/admin") + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "POST ${contextPath:nacos}/v3/auth/user/admin") public Object createAdminUser(@RequestParam(required = false) String password) { if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { if (iAuthenticationManager.hasGlobalAdminRole()) { @@ -150,6 +152,7 @@ public Object createAdminUser(@RequestParam(required = false) String password) { */ @DeleteMapping @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "DELETE ${contextPath:nacos}/v3/auth/user") public Object deleteUser(@RequestParam String username) { List roleInfoList = roleService.getRoles(username); if (roleInfoList != null) { @@ -177,6 +180,7 @@ public Object deleteUser(@RequestParam String username) { @PutMapping @Secured(resource = AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, action = ActionTypes.WRITE, tags = { AuthConstants.UPDATE_PASSWORD_ENTRY_POINT}) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "PUT ${contextPath:nacos}/v3/auth/user") public Object updateUser(@RequestParam String username, @RequestParam String newPassword, HttpServletResponse response, HttpServletRequest request) throws IOException { // admin or same user @@ -239,6 +243,7 @@ private boolean hasPermission(String username, HttpServletRequest request) */ @GetMapping(params = "search=accurate") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/user/list") public Page getUsers(@RequestParam int pageNo, @RequestParam int pageSize, @RequestParam(name = "username", required = false, defaultValue = "") String username) { return userDetailsService.getUsersFromDatabase(pageNo, pageSize, username); @@ -246,6 +251,7 @@ public Page getUsers(@RequestParam int pageNo, @RequestParam int pageSize, @GetMapping(params = "search=blur") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.READ) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/user/list") public Page fuzzySearchUser(@RequestParam int pageNo, @RequestParam int pageSize, @RequestParam(name = "username", required = false, defaultValue = "") String username) { return userDetailsService.findUsersLike4Page(username, pageNo, pageSize); @@ -264,6 +270,7 @@ public Page fuzzySearchUser(@RequestParam int pageNo, @RequestParam int pa * @throws AccessException if user info is incorrect */ @PostMapping("/login") + @Compatibility(apiType = ApiType.OPEN_API, alternatives = "POST ${contextPath:nacos}/v3/auth/user/login") public Object login(@RequestParam String username, @RequestParam String password, HttpServletResponse response, HttpServletRequest request) throws AccessException, IOException { @@ -301,34 +308,6 @@ public Object login(@RequestParam String username, @RequestParam String password } } - /** - * Update password. - * - * @param oldPassword old password - * @param newPassword new password - * @return Code 200 if update successfully, Code 401 if old password invalid, otherwise 500 - */ - @PutMapping("/password") - @Deprecated - public RestResult updatePassword(@RequestParam(value = "oldPassword") String oldPassword, - @RequestParam(value = "newPassword") String newPassword) { - Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal(); - String username = ((UserDetails) principal).getUsername(); - User user = userDetailsService.getUserFromDatabase(username); - String password = user.getPassword(); - - // TODO: throw out more fine grained exceptions - try { - if (PasswordEncoderUtil.matches(oldPassword, password)) { - userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword)); - return RestResultUtils.success("Update password success"); - } - return RestResultUtils.failed(HttpStatus.UNAUTHORIZED.value(), "Old password is invalid"); - } catch (Exception e) { - return RestResultUtils.failed(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Update userpassword failed"); - } - } - /** * Fuzzy matching username. * @@ -337,6 +316,7 @@ public RestResult updatePassword(@RequestParam(value = "oldPassword") St */ @GetMapping("/search") @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + @Compatibility(apiType = ApiType.CONSOLE_API, alternatives = "GET ${contextPath:nacos}/v3/auth/user/search") public List searchUsersLikeUsername(@RequestParam String username) { return userDetailsService.findUserLikeUsername(username); } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3.java new file mode 100644 index 00000000000..e9d3cd20c0c --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3.java @@ -0,0 +1,130 @@ +/* + * Copyright 1999-2024 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +/** + * Controller for handling HTTP requests related to permission operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/auth/permission") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class PermissionControllerV3 { + + private final NacosRoleServiceImpl nacosRoleService; + + private static final String SEARCH_TYPE_BLUR = "blur"; + + /** + * Constructs a new ConsolePermissionController with the provided PermissionProxy. + * + * @param nacosRoleService nacosRoleService instance + */ + @Autowired + public PermissionControllerV3(NacosRoleServiceImpl nacosRoleService) { + this.nacosRoleService = nacosRoleService; + } + + /** + * Add a permission to a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return ok if succeed + */ + @PostMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + public Result createPermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { + nacosRoleService.addPermission(role, resource, action); + return Result.success("add permission ok!"); + } + + + /** + * Delete a permission from a role. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return ok if succeed + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.WRITE) + public Result deletePermission(@RequestParam String role, @RequestParam String resource, + @RequestParam String action) { + nacosRoleService.deletePermission(role, resource, action); + return Result.success("delete permission ok!"); + } + + /** + * Query permissions of a role. + * + * @param role the role + * @param pageNo page index + * @param pageSize page size + * @param search the type of search (accurate or blur) + * @return permission of a role + */ + @GetMapping("/list") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ) + public Result> getPermissionList(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "role", defaultValue = StringUtils.EMPTY) String role, + @RequestParam(name = "search", defaultValue = "accurate") String search) { + Page permissionPage; + if (SEARCH_TYPE_BLUR.equalsIgnoreCase(search)) { + permissionPage = nacosRoleService.findPermissionsLike4Page(role, pageNo, pageSize); + } else { + permissionPage = nacosRoleService.getPermissionsFromDatabase(role, pageNo, pageSize); + } + return Result.success(permissionPage); + } + + /** + * Judge whether a permission is duplicate. + * + * @param role the role + * @param resource the related resource + * @param action the related action + * @return true if duplicate, false otherwise + */ + @GetMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "permissions", action = ActionTypes.READ) + public Result isDuplicatePermission(@RequestParam String role, @RequestParam String resource, @RequestParam String action) { + return nacosRoleService.isDuplicatePermission(role, resource, action); + } +} \ No newline at end of file diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3.java new file mode 100644 index 00000000000..f644dc1f0f0 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3.java @@ -0,0 +1,129 @@ +/* + * Copyright 1999-2024 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.annotation.Secured; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + * Controller for handling HTTP requests related to role operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/auth/role") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class RoleControllerV3 { + + private final NacosRoleServiceImpl roleService; + + private static final String SEARCH_TYPE_BLUR = "blur"; + + public RoleControllerV3(NacosRoleServiceImpl roleService) { + this.roleService = roleService; + } + + /** + * Add a role to a user + * + *

This method is used for 2 functions: 1. create a role and bind it to GLOBAL_ADMIN. 2. bind a role to an user. + * + * @param role role name + * @param username username + * @return Code 200 and message 'add role ok!' + */ + @PostMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + public Result createRole(@RequestParam String role, @RequestParam String username) { + roleService.addRole(role, username); + return Result.success("add role ok!"); + } + + /** + * Delete a role. If no username is specified, all users under this role are deleted. + * + * @param role role + * @param username username + * @return ok if succeed + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.WRITE) + public Result deleteRole(@RequestParam String role, + @RequestParam(name = "username", defaultValue = StringUtils.EMPTY) String username) { + if (StringUtils.isBlank(username)) { + roleService.deleteRole(role); + } else { + roleService.deleteRole(role, username); + } + return Result.success("delete role of user " + username + " ok!"); + } + + /** + * Get roles list with the option for accurate or fuzzy search. + * + * @param pageNo number index of page + * @param pageSize page size + * @param username optional, username of user + * @param role optional role + * @param search the type of search: "accurate" for exact match, "blur" for fuzzy match + * @return role list + */ + @GetMapping("/list") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + public Result> getRoleList(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "username", defaultValue = "") String username, + @RequestParam(name = "role", defaultValue = "") String role, + @RequestParam(name = "search", required = false, defaultValue = "accurate") String search) { + Page rolePage; + if (SEARCH_TYPE_BLUR.equalsIgnoreCase(search)) { + rolePage = roleService.findRolesLike4Page(username, role, pageNo, pageSize); + } else { + rolePage = roleService.getRolesFromDatabase(username, role, pageNo, pageSize); + } + return Result.success(rolePage); + } + + /** + * Fuzzy matching role name . + * + * @param role role id + * @return role list + */ + @GetMapping("/search") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "roles", action = ActionTypes.READ) + public Result> getRoleListByRoleName(@RequestParam String role) { + List roles = roleService.findRolesLikeRoleName(role); + return Result.success(roles); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java new file mode 100644 index 00000000000..f972e27e6a5 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3.java @@ -0,0 +1,302 @@ +/* + * Copyright 1999-2024 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.common.Constants; +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.auth.config.AuthConfigs; +import com.alibaba.nacos.common.utils.JacksonUtils; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.config.server.paramcheck.ConfigDefaultHttpParamExtractor; +import com.alibaba.nacos.core.paramcheck.ExtractorManager; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.api.IdentityContext; +import com.alibaba.nacos.plugin.auth.constant.ActionTypes; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.persistence.User; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.utils.PasswordEncoderUtil; +import com.alibaba.nacos.plugin.auth.impl.utils.PasswordGeneratorUtil; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.http.HttpStatus; +import org.springframework.web.HttpSessionRequiredException; +import org.springframework.web.bind.annotation.DeleteMapping; +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.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.List; + +/** + * Controller for handling HTTP requests related to user operations. + * + * @author zhangyukun on:2024/8/16 + */ +@RestController +@RequestMapping("/v3/auth/user") +@ExtractorManager.Extractor(httpExtractor = ConfigDefaultHttpParamExtractor.class) +public class UserControllerV3 { + + private final NacosUserDetailsServiceImpl userDetailsService; + + private final NacosRoleServiceImpl roleService; + + private final AuthConfigs authConfigs; + + private final IAuthenticationManager iAuthenticationManager; + + private final TokenManagerDelegate jwtTokenManager; + + private static final String SEARCH_TYPE_BLUR = "blur"; + + /** + * Constructs a new UserInnerHandler with the provided dependencies. + * + * @param userDetailsService the service for user details operations + * @param roleService the service for role operations + * @param authConfigs the authentication configuration + * @param iAuthenticationManager the authentication manager interface + * @param jwtTokenManager the JWT token manager + */ + public UserControllerV3(NacosUserDetailsServiceImpl userDetailsService, NacosRoleServiceImpl roleService, + AuthConfigs authConfigs, IAuthenticationManager iAuthenticationManager, + TokenManagerDelegate jwtTokenManager) { + this.userDetailsService = userDetailsService; + this.roleService = roleService; + this.authConfigs = authConfigs; + this.iAuthenticationManager = iAuthenticationManager; + this.jwtTokenManager = jwtTokenManager; + } + + /** + * Create a new user. + * + * @param username username + * @param password password + * @return ok if create succeed + * @throws IllegalArgumentException if user already exist + * @since 1.2.0 + */ + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + @PostMapping + public Result createUser(@RequestParam String username, @RequestParam String password) { + User user = userDetailsService.getUserFromDatabase(username); + if (user != null) { + throw new IllegalArgumentException("user '" + username + "' already exist!"); + } + userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); + return Result.success("create user ok!"); + } + + /** + * Create a admin user only not exist admin user can use. + */ + @PostMapping("/admin") + public Result createAdminUser(@RequestParam(required = false) String password) { + + if (StringUtils.isBlank(password)) { + password = PasswordGeneratorUtil.generateRandomPassword(); + } + + if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { + if (iAuthenticationManager.hasGlobalAdminRole()) { + return Result.failure(HttpStatus.CONFLICT.value(), "have admin user cannot use it.", null); + } + String username = AuthConstants.DEFAULT_USER; + userDetailsService.createUser(username, PasswordEncoderUtil.encode(password)); + roleService.addAdminRole(username); + User result = new User(); + result.setUsername(username); + result.setPassword(password); + return Result.success(result); + } else { + return Result.failure(HttpStatus.NOT_IMPLEMENTED.value(), + "Current auth type not supported create admin user.", null); + } + } + + /** + * Delete an existed user. + * + * @param username username of user + * @return ok if deleted succeed, keep silent if user not exist + * @since 1.2.0 + */ + @DeleteMapping + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + public Result deleteUser(@RequestParam String username) { + List roleInfoList = roleService.getRoles(username); + if (roleInfoList != null) { + for (RoleInfo roleInfo : roleInfoList) { + if (AuthConstants.GLOBAL_ADMIN_ROLE.equals(roleInfo.getRole())) { + throw new IllegalArgumentException("cannot delete admin: " + username); + } + } + } + userDetailsService.deleteUser(username); + return Result.success("delete user ok!"); + } + + /** + * Update an user. + * + * @param username username of user + * @param newPassword new password of user + * @param response http response + * @param request http request + * @return ok if update succeed + * @throws IllegalArgumentException if user not exist or oldPassword is incorrect + * @since 1.2.0 + */ + @PutMapping + @Secured(resource = AuthConstants.UPDATE_PASSWORD_ENTRY_POINT, action = ActionTypes.WRITE) + public Result updateUser(@RequestParam String username, @RequestParam String newPassword, + HttpServletResponse response, HttpServletRequest request) throws IOException { + try { + if (!hasPermission(username, request)) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "authorization failed!"); + return null; + } + } catch (HttpSessionRequiredException e) { + response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "session expired!"); + return null; + } catch (AccessException exception) { + response.sendError(HttpServletResponse.SC_FORBIDDEN, "authorization failed!"); + return null; + } + + User user = userDetailsService.getUserFromDatabase(username); + if (user == null) { + throw new IllegalArgumentException("user " + username + " not exist!"); + } + + userDetailsService.updateUserPassword(username, PasswordEncoderUtil.encode(newPassword)); + return Result.success("update user ok!"); + + } + + private boolean hasPermission(String username, HttpServletRequest request) + throws HttpSessionRequiredException, AccessException { + if (!authConfigs.isAuthEnabled()) { + return true; + } + IdentityContext identityContext = (IdentityContext) request.getSession() + .getAttribute(com.alibaba.nacos.plugin.auth.constant.Constants.Identity.IDENTITY_CONTEXT); + if (identityContext == null) { + throw new HttpSessionRequiredException("session expired!"); + } + NacosUser user = (NacosUser) identityContext.getParameter(AuthConstants.NACOS_USER_KEY); + if (user == null) { + user = iAuthenticationManager.authenticate(request); + if (user == null) { + throw new HttpSessionRequiredException("session expired!"); + } + //get user form jwt need check permission + iAuthenticationManager.hasGlobalAdminRole(user); + } + // admin + if (user.isGlobalAdmin()) { + return true; + } + // same user + return user.getUserName().equals(username); + } + + /** + * Get paged users with the option for accurate or fuzzy search. + * + * @param pageNo number index of page + * @param pageSize size of page + * @param username the username to search for, can be an empty string + * @param search the type of search: "accurate" for exact match, "blur" for fuzzy match + * @return A collection of users, empty set if no user is found + * @since 1.2.0 + */ + @GetMapping("/list") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.READ) + public Result> getUserList(@RequestParam int pageNo, @RequestParam int pageSize, + @RequestParam(name = "username", required = false, defaultValue = "") String username, + @RequestParam(name = "search", required = false, defaultValue = "accurate") String search) { + Page userPage; + if (SEARCH_TYPE_BLUR.equalsIgnoreCase(search)) { + userPage = userDetailsService.findUsersLike4Page(username, pageNo, pageSize); + } else { + userPage = userDetailsService.getUsersFromDatabase(pageNo, pageSize, username); + } + return Result.success(userPage); + } + + /** + * Fuzzy matching username. + * + * @param username username + * @return Matched username + */ + @GetMapping("/search") + @Secured(resource = AuthConstants.CONSOLE_RESOURCE_NAME_PREFIX + "users", action = ActionTypes.WRITE) + public Result> getUserListByUsername(@RequestParam String username) { + List userList = userDetailsService.findUserLikeUsername(username); + return Result.success(userList); + } + + /** + * Login to Nacos + * + *

This methods uses username and password to require a new token. + * + * @param response http response + * @param request http request + * @return new token of the user + * @throws AccessException if user info is incorrect + */ + @PostMapping("/login") + public Object login(HttpServletResponse response, HttpServletRequest request) throws AccessException, IOException { + if (AuthSystemTypes.NACOS.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType()) + || AuthSystemTypes.LDAP.name().equalsIgnoreCase(authConfigs.getNacosAuthSystemType())) { + + NacosUser user = iAuthenticationManager.authenticate(request); + + response.addHeader(AuthConstants.AUTHORIZATION_HEADER, AuthConstants.TOKEN_PREFIX + user.getToken()); + + ObjectNode result = JacksonUtils.createEmptyJsonNode(); + result.put(Constants.ACCESS_TOKEN, user.getToken()); + result.put(Constants.TOKEN_TTL, jwtTokenManager.getTokenTtlInSeconds(user.getToken())); + result.put(Constants.GLOBAL_ADMIN, iAuthenticationManager.hasGlobalAdminRole(user)); + result.put(Constants.USERNAME, user.getUserName()); + return result; + } + return Result.failure(ErrorCode.ILLEGAL_STATE.getCode(), + "Current Nacos auth plugin type is not `nacos` or `nacos-ldap`, don't support login API.", null); + } +} + diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/filter/JwtAuthenticationTokenFilter.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/filter/JwtAuthenticationTokenFilter.java deleted file mode 100644 index e215a15a19f..00000000000 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/filter/JwtAuthenticationTokenFilter.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * 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.plugin.auth.impl.filter; - -import com.alibaba.nacos.api.common.Constants; -import com.alibaba.nacos.common.utils.StringUtils; -import com.alibaba.nacos.plugin.auth.exception.AccessException; -import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; -import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; -import org.springframework.security.core.Authentication; -import org.springframework.security.core.context.SecurityContextHolder; -import org.springframework.web.filter.OncePerRequestFilter; - -import jakarta.servlet.FilterChain; -import jakarta.servlet.ServletException; -import jakarta.servlet.http.HttpServletRequest; -import jakarta.servlet.http.HttpServletResponse; -import java.io.IOException; - -/** - * jwt auth token filter. - * - * @author wfnuser - */ -public class JwtAuthenticationTokenFilter extends OncePerRequestFilter { - - private static final String TOKEN_PREFIX = "Bearer "; - - private final TokenManagerDelegate tokenManager; - - public JwtAuthenticationTokenFilter(TokenManagerDelegate tokenManager) { - this.tokenManager = tokenManager; - } - - @Override - protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) - throws IOException, ServletException { - - String jwt = resolveToken(request); - - if (StringUtils.isNotBlank(jwt) && SecurityContextHolder.getContext().getAuthentication() == null) { - try { - Authentication authentication = this.tokenManager.getAuthentication(jwt); - SecurityContextHolder.getContext().setAuthentication(authentication); - } catch (AccessException e) { - throw new RuntimeException(e); - } - } - chain.doFilter(request, response); - } - - /** - * Get token from header. - */ - private String resolveToken(HttpServletRequest request) { - String bearerToken = request.getHeader(AuthConstants.AUTHORIZATION_HEADER); - if (StringUtils.isNotBlank(bearerToken) && bearerToken.startsWith(TOKEN_PREFIX)) { - return bearerToken.substring(TOKEN_PREFIX.length()); - } - String jwt = request.getParameter(Constants.ACCESS_TOKEN); - if (StringUtils.isNotBlank(jwt)) { - return jwt; - } - return null; - } -} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthConfig.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthPluginConfig.java similarity index 83% rename from plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthConfig.java rename to plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthPluginConfig.java index e0229e4d068..47974573314 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthConfig.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthPluginConfig.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.alibaba.nacos.plugin.auth.impl; +package com.alibaba.nacos.plugin.auth.impl.ldap; import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; import com.alibaba.nacos.plugin.auth.impl.authenticate.LdapAuthenticationManager; @@ -31,6 +31,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.ldap.core.LdapTemplate; import org.springframework.ldap.core.support.LdapContextSource; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; +import org.springframework.security.config.annotation.authentication.configuration.GlobalAuthenticationConfigurerAdapter; /** * ldap auth config. @@ -39,7 +41,8 @@ */ @Configuration(proxyBeanMethods = false) @EnableAutoConfiguration(exclude = LdapAutoConfiguration.class) -public class LdapAuthConfig { +@Conditional(ConditionOnLdapAuth.class) +public class LdapAuthPluginConfig { @Value(("${" + AuthConstants.NACOS_CORE_AUTH_LDAP_URL + ":ldap://localhost:389}")) private String ldapUrl; @@ -69,7 +72,6 @@ public class LdapAuthConfig { private boolean ignorePartialResultException; @Bean - @Conditional(ConditionOnLdapAuth.class) public LdapTemplate ldapTemplate(LdapContextSource ldapContextSource) { LdapTemplate ldapTemplate = new LdapTemplate(ldapContextSource); ldapTemplate.setIgnorePartialResultException(ignorePartialResultException); @@ -82,7 +84,6 @@ public LdapContextSource ldapContextSource() { } @Bean - @Conditional(ConditionOnLdapAuth.class) public LdapAuthenticationProvider ldapAuthenticationProvider(LdapTemplate ldapTemplate, NacosUserDetailsServiceImpl userDetailsService, NacosRoleServiceImpl nacosRoleService) { return new LdapAuthenticationProvider(ldapTemplate, userDetailsService, nacosRoleService, filterPrefix, @@ -90,7 +91,6 @@ public LdapAuthenticationProvider ldapAuthenticationProvider(LdapTemplate ldapTe } @Bean - @Conditional(ConditionOnLdapAuth.class) public IAuthenticationManager ldapAuthenticatoinManager(LdapTemplate ldapTemplate, NacosUserDetailsServiceImpl userDetailsService, TokenManagerDelegate jwtTokenManager, NacosRoleServiceImpl roleService) { @@ -98,4 +98,15 @@ public IAuthenticationManager ldapAuthenticatoinManager(LdapTemplate ldapTemplat filterPrefix, caseSensitive); } + @Bean + public GlobalAuthenticationConfigurerAdapter authenticationConfigurer( + LdapAuthenticationProvider ldapAuthenticationProvider) { + return new GlobalAuthenticationConfigurerAdapter() { + @Override + public void init(AuthenticationManagerBuilder auth) throws Exception { + auth.authenticationProvider(ldapAuthenticationProvider); + } + }; + } + } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthenticationProvider.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthenticationProvider.java similarity index 98% rename from plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthenticationProvider.java rename to plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthenticationProvider.java index d2fa4b07bc9..f55e6ba1637 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthenticationProvider.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthenticationProvider.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2021 Alibaba Group Holding Ltd. + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.alibaba.nacos.plugin.auth.impl; +package com.alibaba.nacos.plugin.auth.impl.ldap; import com.alibaba.nacos.common.utils.CollectionUtils; import com.alibaba.nacos.core.utils.Loggers; diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosLdapContextSource.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/NacosLdapContextSource.java similarity index 97% rename from plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosLdapContextSource.java rename to plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/NacosLdapContextSource.java index 571764348e1..623006bfdfe 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/NacosLdapContextSource.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/ldap/NacosLdapContextSource.java @@ -1,5 +1,5 @@ /* - * Copyright 1999-2018 Alibaba Group Holding Ltd. + * 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. @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.alibaba.nacos.plugin.auth.impl; +package com.alibaba.nacos.plugin.auth.impl.ldap; import com.alibaba.nacos.common.tls.TlsHelper; import com.alibaba.nacos.core.utils.Loggers; diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java index 7e6803d4b5d..852a4ef0946 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImpl.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.plugin.auth.impl.roles; +import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.auth.config.AuthConfigs; import com.alibaba.nacos.common.utils.CollectionUtils; import com.alibaba.nacos.common.utils.ConcurrentHashSet; @@ -59,6 +60,8 @@ public class NacosRoleServiceImpl { private static final int DEFAULT_PAGE_NO = 1; + private static final Set WHITE_PERMISSION = new HashSet<>(); + @Autowired private AuthConfigs authConfigs; @@ -77,6 +80,11 @@ public class NacosRoleServiceImpl { private volatile Map> permissionInfoMap = new ConcurrentHashMap<>(); + static { + WHITE_PERMISSION.add(AuthConstants.UPDATE_PASSWORD_ENTRY_POINT); + WHITE_PERMISSION.add(AuthConstants.LOCK_OPERATOR_POINT); + } + @Scheduled(initialDelay = 5000, fixedDelay = 15000) private void reload() { try { @@ -121,6 +129,11 @@ private void reload() { * @return true if granted, false otherwise */ public boolean hasPermission(NacosUser nacosUser, Permission permission) { + //white permission + if (WHITE_PERMISSION.contains(permission.getResource().getName())) { + return true; + } + if (isUpdatePasswordPermission(permission)) { return true; } @@ -235,6 +248,12 @@ public void addRole(String role, String username) { throw new IllegalArgumentException( "role '" + AuthConstants.GLOBAL_ADMIN_ROLE + "' is not permitted to create!"); } + + if (isUserBoundToRole(role, username)) { + throw new IllegalArgumentException( + "user '" + username + "' already bound to the role '" + role + "'!"); + } + rolePersistService.addRole(role, username); roleSet.add(role); } @@ -370,5 +389,46 @@ public boolean hasGlobalAdminRole() { authConfigs.setHasGlobalAdminRole(hasGlobalAdminRole); return hasGlobalAdminRole; } + + /** + * judge whether the permission is duplicate. + * + * @param role role name + * @param resource resource + * @param action action + * @return true if duplicate, false otherwise + */ + public Result isDuplicatePermission(String role, String resource, String action) { + List permissionInfos = getPermissions(role); + if (CollectionUtils.isEmpty(permissionInfos)) { + return Result.success(Boolean.FALSE); + } + for (PermissionInfo permissionInfo : permissionInfos) { + boolean resourceMatch = StringUtils.equals(resource, permissionInfo.getResource()); + boolean actionMatch = StringUtils.equals(action, permissionInfo.getAction()) || "rw".equals(permissionInfo.getAction()); + if (resourceMatch && actionMatch) { + return Result.success(Boolean.TRUE); + } + } + return Result.success(Boolean.FALSE); + } + + /** + * judge whether the user is already bound to the role. + * + * @param role role name + * @param username user name + * @return true if the user is already bound to the role. + */ + public boolean isUserBoundToRole(String role, String username) { + Page roleInfoPage = rolePersistService.getRolesByUserNameAndRoleName(username, + role, DEFAULT_PAGE_NO, 1); + if (roleInfoPage == null) { + return false; + } + List roleInfos = roleInfoPage.getPageItems(); + return CollectionUtils.isNotEmpty(roleInfos) && roleInfos.stream() + .anyMatch(roleInfo -> role.equals(roleInfo.getRole())); + } } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegate.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegate.java index 61e0adca048..5c6e6367611 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegate.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegate.java @@ -17,41 +17,26 @@ package com.alibaba.nacos.plugin.auth.impl.token; import com.alibaba.nacos.plugin.auth.exception.AccessException; -import com.alibaba.nacos.plugin.auth.impl.token.impl.CachedJwtTokenManager; -import com.alibaba.nacos.plugin.auth.impl.token.impl.JwtTokenManager; import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; -import com.alibaba.nacos.sys.env.EnvUtil; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; - -import javax.annotation.PostConstruct; /** * token manager delegate. * * @author majorhe */ -@Component public class TokenManagerDelegate implements TokenManager { public static final String NACOS_AUTH_TOKEN_CACHING_ENABLED = "nacos.core.auth.plugin.nacos.token.cache.enable"; - private boolean tokenCacheEnabled = false; - - @Autowired - private JwtTokenManager jwtTokenManager; - - @Autowired - private CachedJwtTokenManager cachedJwtTokenManager; + private final TokenManager delegate; - @PostConstruct - public void init() { - tokenCacheEnabled = EnvUtil.getProperty(NACOS_AUTH_TOKEN_CACHING_ENABLED, Boolean.class, false); + public TokenManagerDelegate(TokenManager delegate) { + this.delegate = delegate; } private TokenManager getExecuteTokenManager() { - return tokenCacheEnabled ? cachedJwtTokenManager : jwtTokenManager; + return delegate; } @Override diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManager.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManager.java index 0f5a3f11b66..f02c4a3a838 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManager.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManager.java @@ -19,10 +19,8 @@ import com.alibaba.nacos.plugin.auth.exception.AccessException; import com.alibaba.nacos.plugin.auth.impl.token.TokenManager; import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.security.core.Authentication; -import org.springframework.stereotype.Component; import java.util.ArrayList; import java.util.List; @@ -35,7 +33,6 @@ * * @author majorhe */ -@Component public class CachedJwtTokenManager implements TokenManager { /** @@ -48,8 +45,11 @@ public class CachedJwtTokenManager implements TokenManager { */ private volatile Map userMap = new ConcurrentHashMap<>(128); - @Autowired - private JwtTokenManager jwtTokenManager; + private final JwtTokenManager jwtTokenManager; + + public CachedJwtTokenManager(JwtTokenManager jwtTokenManager) { + this.jwtTokenManager = jwtTokenManager; + } @Scheduled(initialDelay = 30000, fixedDelay = 60000) private void cleanExpiredToken() { diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/JwtTokenManager.java b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/JwtTokenManager.java index 7dc2cc57469..250f96259c3 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/JwtTokenManager.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/main/java/com/alibaba/nacos/plugin/auth/impl/token/impl/JwtTokenManager.java @@ -35,7 +35,6 @@ import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.core.userdetails.User; -import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.TimeUnit; @@ -46,7 +45,6 @@ * @author wfnuser * @author nkorange */ -@Component public class JwtTokenManager extends Subscriber implements TokenManager { private static final String AUTH_DISABLED_TOKEN = "AUTH_DISABLED"; @@ -70,16 +68,16 @@ private void processProperties() { this.tokenValidityInSeconds = EnvUtil.getProperty(AuthConstants.TOKEN_EXPIRE_SECONDS, Long.class, AuthConstants.DEFAULT_TOKEN_EXPIRE_SECONDS); - String encodedSecretKey = EnvUtil - .getProperty(AuthConstants.TOKEN_SECRET_KEY, AuthConstants.DEFAULT_TOKEN_SECRET_KEY); + String encodedSecretKey = EnvUtil.getProperty(AuthConstants.TOKEN_SECRET_KEY, + AuthConstants.DEFAULT_TOKEN_SECRET_KEY); try { this.jwtParser = new NacosJwtParser(encodedSecretKey); } catch (Exception e) { this.jwtParser = null; - if (authConfigs.isAuthEnabled()) { + if (authConfigs.isAuthEnabled() || authConfigs.isConsoleAuthEnabled()) { throw new IllegalArgumentException( "the length of secret key must great than or equal 32 bytes; And the secret key must be encoded by base64." - + "Please see https://nacos.io/zh-cn/docs/v2/guide/user/auth.html", e); + + "Please see https://nacos.io/docs/latest/manual/admin/auth/", e); } } @@ -175,7 +173,7 @@ public Class subscribeType() { private void checkJwtParser() { if (null == jwtParser) { throw new NacosRuntimeException(NacosException.INVALID_PARAM, - "Please config `nacos.core.auth.plugin.nacos.token.secret.key`, detail see https://nacos.io/zh-cn/docs/v2/guide/user/auth.html"); + "Please config `nacos.core.auth.plugin.nacos.token.secret.key`, detail see https://nacos.io/docs/latest/manual/admin/auth/"); } } } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionControllerTest.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionControllerTest.java index 6f73ec63859..60eba753336 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionControllerTest.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/PermissionControllerTest.java @@ -16,6 +16,7 @@ package com.alibaba.nacos.plugin.auth.impl.controller; +import com.alibaba.nacos.api.model.v2.Result; import com.alibaba.nacos.common.model.RestResult; import com.alibaba.nacos.persistence.model.Page; import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; @@ -86,4 +87,12 @@ void testDeletePermission() { assertEquals(200, result.getCode()); } + @Test + void testDuplicatePermission() { + when(nacosRoleService.isDuplicatePermission(anyString(), anyString(), anyString())).thenReturn( + Result.success(Boolean.TRUE)); + Result result = permissionController.isDuplicatePermission("admin", "test", "test"); + assertEquals(0, result.getCode()); + } + } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java new file mode 100644 index 00000000000..96cec0da2e1 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/PermissionControllerV3Test.java @@ -0,0 +1,101 @@ +/* + * Copyright 1999-2024 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.PermissionInfo; +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.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * PermissionControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class PermissionControllerV3Test { + + @InjectMocks + private PermissionControllerV3 permissionController; + + @Mock + private NacosRoleServiceImpl nacosRoleService; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(permissionController).build(); + } + + @Test + void testGetPermissionListAccurateSearch() { + Page permissionInfoPage = new Page<>(); + when(nacosRoleService.getPermissionsFromDatabase(anyString(), anyInt(), anyInt())).thenReturn( + permissionInfoPage); + + Result> result = permissionController.getPermissionList(1, 10, "admin", "accurate"); + + assertEquals(permissionInfoPage, result.getData()); + verify(nacosRoleService, times(1)).getPermissionsFromDatabase("admin", 1, 10); + } + + @Test + void testGetPermissionListBlurSearch() { + Page permissionInfoPage = new Page<>(); + when(nacosRoleService.findPermissionsLike4Page(anyString(), anyInt(), anyInt())).thenReturn(permissionInfoPage); + + Result> result = permissionController.getPermissionList(1, 10, "admin", "blur"); + + assertEquals(permissionInfoPage, result.getData()); + verify(nacosRoleService, times(1)).findPermissionsLike4Page("admin", 1, 10); + } + + @Test + void testCreatePermission() { + Result result = (Result) permissionController.createPermission("admin", "testResource", + "write"); + + verify(nacosRoleService, times(1)).addPermission("admin", "testResource", "write"); + assertEquals("add permission ok!", result.getData()); + } + + @Test + void testDeletePermission() { + Result result = (Result) permissionController.deletePermission("admin", "testResource", + "write"); + + verify(nacosRoleService, times(1)).deletePermission("admin", "testResource", "write"); + assertEquals("delete permission ok!", result.getData()); + } +} \ No newline at end of file diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java new file mode 100644 index 00000000000..bfe508ecb69 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/RoleControllerV3Test.java @@ -0,0 +1,123 @@ +/* + * Copyright 1999-2024 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.persistence.model.Page; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +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.junit.jupiter.MockitoExtension; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * RoleControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +public class RoleControllerV3Test { + + @Mock + private NacosRoleServiceImpl roleService; + + @InjectMocks + private RoleControllerV3 roleControllerV3; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(roleControllerV3).build(); + } + + @Test + void testCreateRole() { + Result result = (Result) roleControllerV3.createRole("test", "nacos"); + + verify(roleService, times(1)).addRole(anyString(), anyString()); + + assertEquals("add role ok!", result.getData()); + } + + @Test + void testDeleteRoleWithoutUsername() { + Result result = (Result) roleControllerV3.deleteRole("test", ""); + + verify(roleService, times(1)).deleteRole(anyString()); + + assertEquals("delete role of user ok!", result.getData()); + } + + @Test + void testDeleteRoleWithUsername() { + Result result = (Result) roleControllerV3.deleteRole("test", "nacos"); + + verify(roleService, times(1)).deleteRole(anyString(), anyString()); + + assertEquals("delete role of user nacos ok!", result.getData()); + } + + @Test + void testGetRoleListAccurateSearch() { + Page rolesTest = new Page(); + + when(roleService.getRolesFromDatabase(anyString(), anyString(), anyInt(), anyInt())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleList(1, 10, "nacos", "test", "accurate"); + + assertEquals(rolesTest, result.getData()); + } + + @Test + void testGetRoleListFuzzySearch() { + Page rolesTest = new Page(); + + when(roleService.findRolesLike4Page(anyString(), anyString(), anyInt(), anyInt())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleList(1, 10, "nacos", "test", "blur"); + + assertEquals(rolesTest, result.getData()); + } + + @Test + void testGetRoleListByRoleName() { + List rolesTest = new ArrayList<>(); + + when(roleService.findRolesLikeRoleName(anyString())).thenReturn(rolesTest); + + Result> result = roleControllerV3.getRoleListByRoleName("test"); + + assertEquals(rolesTest, result.getData()); + } +} diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java new file mode 100644 index 00000000000..fa33e222d03 --- /dev/null +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/controller/v3/UserControllerV3Test.java @@ -0,0 +1,218 @@ +/* + * Copyright 1999-2024 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.plugin.auth.impl.controller.v3; + +import com.alibaba.nacos.api.model.v2.Result; +import com.alibaba.nacos.auth.config.AuthConfigs; +import com.alibaba.nacos.plugin.auth.exception.AccessException; +import com.alibaba.nacos.plugin.auth.impl.authenticate.IAuthenticationManager; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; +import com.alibaba.nacos.plugin.auth.impl.constant.AuthSystemTypes; +import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; +import com.alibaba.nacos.plugin.auth.impl.persistence.User; +import com.alibaba.nacos.plugin.auth.impl.roles.NacosRoleServiceImpl; +import com.alibaba.nacos.plugin.auth.impl.token.TokenManagerDelegate; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUser; +import com.alibaba.nacos.plugin.auth.impl.users.NacosUserDetailsServiceImpl; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.HttpStatus; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +/** + * UserControllerV3Test. + * + * @author zhangyukun on:2024/9/5 + */ +@ExtendWith(MockitoExtension.class) +class UserControllerV3Test { + + @Mock + private NacosUserDetailsServiceImpl userDetailsService; + + @Mock + private NacosRoleServiceImpl roleService; + + @Mock + private AuthConfigs authConfigs; + + @Mock + private IAuthenticationManager iAuthenticationManager; + + @Mock + private TokenManagerDelegate jwtTokenManager; + + @InjectMocks + private UserControllerV3 userControllerV3; + + private NacosUser user; + + @BeforeEach + void setUp() { + user = new NacosUser(); + user.setUserName("nacos"); + user.setToken("1234567890"); + user.setGlobalAdmin(true); + } + + @Test + void testCreateUserSuccess() { + when(userDetailsService.getUserFromDatabase("test")).thenReturn(null); + + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + Result result = (Result) userControllerV3.createUser("test", "testPass"); + + verify(userDetailsService, times(1)).createUser(eq("test"), passwordCaptor.capture()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$"), "Password hash should start with '$2a$10$'"); + + assertEquals("create user ok!", result.getData()); + } + + @Test + void testCreateUserUserAlreadyExists() { + when(userDetailsService.getUserFromDatabase("test")).thenReturn(new User()); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + userControllerV3.createUser("test", "testPass"); + }); + + assertEquals("user 'test' already exist!", exception.getMessage()); + } + + @Test + void testDeleteUserSuccess() { + when(roleService.getRoles("nacos")).thenReturn(new ArrayList<>()); + + Result result = (Result) userControllerV3.deleteUser("nacos"); + + verify(userDetailsService, times(1)).deleteUser("nacos"); + assertEquals("delete user ok!", result.getData()); + } + + @Test + void testDeleteUserCannotDeleteAdmin() { + List roleInfoList = new ArrayList<>(); + RoleInfo adminRole = new RoleInfo(); + adminRole.setRole(AuthConstants.GLOBAL_ADMIN_ROLE); + roleInfoList.add(adminRole); + + when(roleService.getRoles("nacos")).thenReturn(roleInfoList); + + IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> { + userControllerV3.deleteUser("nacos"); + }); + + assertEquals("cannot delete admin: nacos", exception.getMessage()); + } + + @Test + void testUpdateUserSuccess() throws IOException { + MockHttpServletRequest request = new MockHttpServletRequest(); + MockHttpServletResponse response = new MockHttpServletResponse(); + + when(userDetailsService.getUserFromDatabase("nacos")).thenReturn(new User()); + + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + Result result = (Result) userControllerV3.updateUser("nacos", "newPass", response, request); + + verify(userDetailsService, times(1)).updateUserPassword(eq("nacos"), passwordCaptor.capture()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$")); + assertEquals("update user ok!", result.getData()); + } + + @Test + void testLoginSuccess() throws AccessException, IOException { + NacosUser user = new NacosUser(); + user.setUserName("nacos"); + user.setToken("1234567890"); + user.setGlobalAdmin(true); + MockHttpServletRequest request = new MockHttpServletRequest(); + when(iAuthenticationManager.authenticate(request)).thenReturn(user); + when(iAuthenticationManager.hasGlobalAdminRole(user)).thenReturn(true); + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(jwtTokenManager.getTokenTtlInSeconds(anyString())).thenReturn(18000L); + MockHttpServletResponse response = new MockHttpServletResponse(); + Object actual = userControllerV3.login(response, request); + + assertTrue(actual instanceof ObjectNode); + + String actualString = actual.toString(); + + assertTrue(actualString.contains("\"accessToken\":\"1234567890\"")); + assertTrue(actualString.contains("\"tokenTtl\":18000")); + assertTrue(actualString.contains("\"globalAdmin\":true")); + + assertEquals(AuthConstants.TOKEN_PREFIX + "1234567890", response.getHeader(AuthConstants.AUTHORIZATION_HEADER)); + } + + @Test + void testCreateAdminUserSuccess() { + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(iAuthenticationManager.hasGlobalAdminRole()).thenReturn(false); + + Result result = userControllerV3.createAdminUser("testAdminPass"); + + ArgumentCaptor usernameCaptor = ArgumentCaptor.forClass(String.class); + ArgumentCaptor passwordCaptor = ArgumentCaptor.forClass(String.class); + + verify(userDetailsService, times(1)).createUser(usernameCaptor.capture(), passwordCaptor.capture()); + + assertEquals(AuthConstants.DEFAULT_USER, usernameCaptor.getValue()); + + User data = result.getData(); + assertEquals(AuthConstants.DEFAULT_USER, data.getUsername()); + assertEquals("testAdminPass", data.getPassword()); + + assertTrue(passwordCaptor.getValue().startsWith("$2a$10$")); + } + + @Test + void testCreateAdminUserConflict() { + when(authConfigs.getNacosAuthSystemType()).thenReturn(AuthSystemTypes.NACOS.name()); + when(iAuthenticationManager.hasGlobalAdminRole()).thenReturn(true); + + Result result = userControllerV3.createAdminUser("adminPass"); + + assertEquals(HttpStatus.CONFLICT.value(), result.getCode()); + } +} + diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthenticationProviderTest.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthenticationProviderTest.java similarity index 88% rename from plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthenticationProviderTest.java rename to plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthenticationProviderTest.java index 0b272384954..aec447ce831 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/LdapAuthenticationProviderTest.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/ldap/LdapAuthenticationProviderTest.java @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.alibaba.nacos.plugin.auth.impl; +package com.alibaba.nacos.plugin.auth.impl.ldap; import com.alibaba.nacos.plugin.auth.impl.constant.AuthConstants; import com.alibaba.nacos.plugin.auth.impl.persistence.RoleInfo; @@ -104,10 +104,10 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable { return false; } }); - this.ldapAuthenticationProvider = new LdapAuthenticationProvider(ldapTemplate, userDetailsService, nacosRoleService, filterPrefix, - caseSensitive); - this.ldapAuthenticationProviderForCloseCaseSensitive = new LdapAuthenticationProvider(ldapTemplate, userDetailsService, - nacosRoleService, filterPrefix, !caseSensitive); + this.ldapAuthenticationProvider = new LdapAuthenticationProvider(ldapTemplate, userDetailsService, + nacosRoleService, filterPrefix, caseSensitive); + this.ldapAuthenticationProviderForCloseCaseSensitive = new LdapAuthenticationProvider(ldapTemplate, + userDetailsService, nacosRoleService, filterPrefix, !caseSensitive); isAdmin = LdapAuthenticationProvider.class.getDeclaredMethod("isAdmin", String.class); isAdmin.setAccessible(true); ldapLogin = LdapAuthenticationProvider.class.getDeclaredMethod("ldapLogin", String.class, String.class); @@ -158,22 +158,24 @@ void testldapLogin() { @Test void testDefaultCaseSensitive() { String userName = StringUtils.upperCase(normalUserName); - when(ldapTemplate.authenticate("", "(" + filterPrefix + "=" + userName + ")", defaultPassWord)).thenAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - String b = (String) args[1]; - String c = (String) args[2]; - if (defaultPassWord.equals(c)) { - return true; - } - return false; - } - }); + when(ldapTemplate.authenticate("", "(" + filterPrefix + "=" + userName + ")", defaultPassWord)).thenAnswer( + new Answer() { + @Override + public Boolean answer(InvocationOnMock invocation) throws Throwable { + Object[] args = invocation.getArguments(); + String b = (String) args[1]; + String c = (String) args[2]; + if (defaultPassWord.equals(c)) { + return true; + } + return false; + } + }); User userUpperCase = new User(); userUpperCase.setUsername(LDAP_PREFIX + userName); userUpperCase.setPassword(defaultPassWord); - when(userDetailsService.loadUserByUsername(LDAP_PREFIX + userName)).thenReturn(new NacosUserDetails(userUpperCase)); + when(userDetailsService.loadUserByUsername(LDAP_PREFIX + userName)).thenReturn( + new NacosUserDetails(userUpperCase)); Authentication authentication = new UsernamePasswordAuthenticationToken(userName, defaultPassWord); Authentication result = ldapAuthenticationProvider.authenticate(authentication); NacosUserDetails nacosUserDetails = (NacosUserDetails) result.getPrincipal(); @@ -182,24 +184,23 @@ public Boolean answer(InvocationOnMock invocation) throws Throwable { @Test void testCloseCaseSensitive() { - when(ldapTemplate.authenticate("", "(" + filterPrefix + "=" + normalUserName + ")", defaultPassWord)).thenAnswer( - new Answer() { - @Override - public Boolean answer(InvocationOnMock invocation) throws Throwable { - Object[] args = invocation.getArguments(); - String b = (String) args[1]; - String c = (String) args[2]; - if (defaultPassWord.equals(c)) { - return true; - } - return false; + when(ldapTemplate.authenticate("", "(" + filterPrefix + "=" + normalUserName + ")", + defaultPassWord)).thenAnswer((Answer) invocation -> { + Object[] args = invocation.getArguments(); + String b = (String) args[1]; + String c = (String) args[2]; + if (defaultPassWord.equals(c)) { + return true; } + return false; }); User user = new User(); user.setUsername(LDAP_PREFIX + normalUserName); user.setPassword(defaultPassWord); - when(userDetailsService.loadUserByUsername(LDAP_PREFIX + normalUserName)).thenReturn(new NacosUserDetails(user)); - Authentication authentication = new UsernamePasswordAuthenticationToken(StringUtils.upperCase(normalUserName), defaultPassWord); + when(userDetailsService.loadUserByUsername(LDAP_PREFIX + normalUserName)).thenReturn( + new NacosUserDetails(user)); + Authentication authentication = new UsernamePasswordAuthenticationToken(StringUtils.upperCase(normalUserName), + defaultPassWord); Authentication result = ldapAuthenticationProviderForCloseCaseSensitive.authenticate(authentication); NacosUserDetails nacosUserDetails = (NacosUserDetails) result.getPrincipal(); assertEquals(nacosUserDetails.getUsername(), LDAP_PREFIX + normalUserName); diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java index 315b3b3cac2..7e91431ad7d 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/roles/NacosRoleServiceImplTest.java @@ -36,6 +36,7 @@ import java.lang.reflect.Field; import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Properties; @@ -45,6 +46,8 @@ import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; /** * NacosRoleServiceImpl Test. @@ -203,4 +206,27 @@ void joinResource() throws Exception { Object invoke = method.invoke(nacosRoleService, new Resource[] {resource}); assertNotNull(invoke); } + + @Test + void duplicatePermission() { + List permissionInfos = new ArrayList<>(); + PermissionInfo permissionInfo = new PermissionInfo(); + permissionInfo.setAction("rw"); + permissionInfo.setResource("test"); + permissionInfos.add(permissionInfo); + NacosRoleServiceImpl spy = spy(nacosRoleService); + when(spy.getPermissions("admin")).thenReturn(permissionInfos); + spy.isDuplicatePermission("admin", "test", "r"); + } + + @Test + void isUserBoundToRole() { + String role = "TEST"; + String userName = "nacos"; + assertFalse(nacosRoleService.isUserBoundToRole("", userName)); + assertFalse(nacosRoleService.isUserBoundToRole(role, "")); + assertFalse(nacosRoleService.isUserBoundToRole("", null)); + assertFalse(nacosRoleService.isUserBoundToRole(null, "")); + assertFalse(nacosRoleService.isUserBoundToRole(role, userName)); + } } diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegateTest.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegateTest.java index 815eb17d54f..b0a354b0e7e 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegateTest.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/TokenManagerDelegateTest.java @@ -63,9 +63,7 @@ class TokenManagerDelegateTest { @BeforeEach void setUp() throws Exception { - tokenManagerDelegate = new TokenManagerDelegate(); - injectObject("jwtTokenManager", jwtTokenManager); - injectObject("cachedJwtTokenManager", cachedJwtTokenManager); + tokenManagerDelegate = new TokenManagerDelegate(jwtTokenManager); injectObject("tokenCacheEnabled", Boolean.TRUE); when(cachedJwtTokenManager.getTokenValidityInSeconds()).thenReturn(100L); when(cachedJwtTokenManager.getTokenTtlInSeconds(anyString())).thenReturn(100L); diff --git a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManagerTest.java b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManagerTest.java index 9875a5b316a..4efb7b6a80c 100644 --- a/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManagerTest.java +++ b/plugin-default-impl/nacos-default-auth-plugin/src/test/java/com/alibaba/nacos/plugin/auth/impl/token/impl/CachedJwtTokenManagerTest.java @@ -27,8 +27,6 @@ import org.mockito.quality.Strictness; import org.springframework.security.core.Authentication; -import java.lang.reflect.Field; - import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -58,8 +56,7 @@ class CachedJwtTokenManagerTest { @BeforeEach void setUp() throws Exception { - cachedJwtTokenManager = new CachedJwtTokenManager(); - injectObject("jwtTokenManager", jwtTokenManager); + cachedJwtTokenManager = new CachedJwtTokenManager(jwtTokenManager); when(jwtTokenManager.getTokenValidityInSeconds()).thenReturn(100L); when(jwtTokenManager.getTokenTtlInSeconds(anyString())).thenReturn(100L); when(jwtTokenManager.getExpiredTimeInSeconds(anyString())).thenReturn(System.currentTimeMillis()); @@ -104,9 +101,4 @@ void testGetTokenValidityInSeconds() { assertTrue(cachedJwtTokenManager.getTokenValidityInSeconds() > 0); } - private void injectObject(String fieldName, Object value) throws NoSuchFieldException, IllegalAccessException { - Field field = CachedJwtTokenManager.class.getDeclaredField(fieldName); - field.setAccessible(true); - field.set(cachedJwtTokenManager, value); - } } diff --git a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/LoginIdentityContext.java b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/LoginIdentityContext.java index 5077d34d7a9..18124a88289 100644 --- a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/LoginIdentityContext.java +++ b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/LoginIdentityContext.java @@ -42,6 +42,17 @@ public String getParameter(String key) { return param.get(key); } + /** + * get key from context; if blank return default value. + * + * @param key key of request + * @return value of param key + */ + public String getParameter(String key, String defaultValue) { + String val = param.get(key); + return val == null ? defaultValue : val; + } + /** * put key and value to param. * diff --git a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/RequestResource.java b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/RequestResource.java index 99ce1e67565..d9659cfb89c 100644 --- a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/RequestResource.java +++ b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/api/RequestResource.java @@ -94,6 +94,17 @@ public static Builder configBuilder() { return result; } + /** + * Create new lock request resource builder. + * + * @return lock request resource builder + */ + public static Builder lockBuilder() { + Builder result = new Builder(); + result.setType(SignType.LOCK); + return result; + } + public static class Builder { private String type; diff --git a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/ApiType.java b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/ApiType.java new file mode 100644 index 00000000000..668e31af3ed --- /dev/null +++ b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/ApiType.java @@ -0,0 +1,54 @@ +/* + * 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.plugin.auth.constant; + +/** + * The type of Nacos API. + * + * @author zhangyukun + * @author xiweng.yy + */ +public enum ApiType { + + /** + * Admin API which nacos maintainer or administrator used. + */ + ADMIN_API("ADMIN_API"), + /** + * Console API which nacos console used. + */ + CONSOLE_API("CONSOLE_API"), + /** + * Open API which client used or basic data operation. + */ + OPEN_API("OPEN_API"), + /** + * Inner API which used between nacos servers. + */ + INNER_API("INNER_API"); + + private final String description; + + ApiType(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } +} diff --git a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/Constants.java b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/Constants.java index 195254b6b2d..627291dfbd2 100644 --- a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/Constants.java +++ b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/Constants.java @@ -26,6 +26,8 @@ public class Constants { public static class Auth { public static final String NACOS_CORE_AUTH_ENABLED = "nacos.core.auth.enabled"; + + public static final String NACOS_CORE_AUTH_CONSOLE_ENABLED = "nacos.core.auth.console.enabled"; public static final String NACOS_CORE_AUTH_SYSTEM_TYPE = "nacos.core.auth.system.type"; @@ -35,8 +37,6 @@ public static class Auth { public static final String NACOS_CORE_AUTH_SERVER_IDENTITY_VALUE = "nacos.core.auth.server.identity.value"; - public static final String NACOS_CORE_AUTH_ENABLE_USER_AGENT_AUTH_WHITE = "nacos.core.auth.enable.userAgentAuthWhite"; - } public static class Resource { diff --git a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/SignType.java b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/SignType.java index e1aa607ad80..972c0a732bd 100644 --- a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/SignType.java +++ b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/constant/SignType.java @@ -27,6 +27,8 @@ public class SignType { public static final String CONFIG = "config"; + public static final String LOCK = "lock"; + public static final String CONSOLE = "console"; public static final String SPECIFIED = "specified"; diff --git a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/AbstractClientAuthService.java b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/AbstractClientAuthService.java index e95d01bf212..d5d63411a19 100644 --- a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/AbstractClientAuthService.java +++ b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/AbstractClientAuthService.java @@ -27,7 +27,7 @@ */ public abstract class AbstractClientAuthService implements ClientAuthService { - protected List serverList; + protected volatile List serverList; protected NacosRestTemplate nacosRestTemplate; diff --git a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/ClientAuthPluginManager.java b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/ClientAuthPluginManager.java index 31cd731ff72..e1f84241c22 100644 --- a/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/ClientAuthPluginManager.java +++ b/plugin/auth/src/main/java/com/alibaba/nacos/plugin/auth/spi/client/ClientAuthPluginManager.java @@ -61,6 +61,17 @@ public void init(List serverList, NacosRestTemplate nacosRestTemplate) { } } + /** + * refresh ClientAuthService server list. + * + * @param serverList the new server list. + */ + public void refreshServerList(List serverList) { + for (ClientAuthService clientAuthService : clientAuthServiceHashSet) { + clientAuthService.setServerList(serverList); + } + } + /** * get all ClientAuthService instance. * diff --git a/plugin/auth/src/test/java/com/alibaba/nacos/plugin/auth/constant/ConstantsTest.java b/plugin/auth/src/test/java/com/alibaba/nacos/plugin/auth/constant/ConstantsTest.java index 582d182f773..9ba5b417ca5 100644 --- a/plugin/auth/src/test/java/com/alibaba/nacos/plugin/auth/constant/ConstantsTest.java +++ b/plugin/auth/src/test/java/com/alibaba/nacos/plugin/auth/constant/ConstantsTest.java @@ -29,7 +29,6 @@ void testConstantsForAuth() { assertEquals("nacos.core.auth.caching.enabled", Constants.Auth.NACOS_CORE_AUTH_CACHING_ENABLED); assertEquals("nacos.core.auth.server.identity.key", Constants.Auth.NACOS_CORE_AUTH_SERVER_IDENTITY_KEY); assertEquals("nacos.core.auth.server.identity.value", Constants.Auth.NACOS_CORE_AUTH_SERVER_IDENTITY_VALUE); - assertEquals("nacos.core.auth.enable.userAgentAuthWhite", Constants.Auth.NACOS_CORE_AUTH_ENABLE_USER_AGENT_AUTH_WHITE); } @Test diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/FieldConstant.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/FieldConstant.java index d5c7576e90c..db3bdb2d5f8 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/FieldConstant.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/FieldConstant.java @@ -43,6 +43,8 @@ public class FieldConstant { public static final String PAGE_SIZE = "pageSize"; public static final String ID = "id"; + + public static final String NID = "nid"; public static final String START_TIME = "startTime"; @@ -80,6 +82,12 @@ public class FieldConstant { public static final String TAG_ID = "tagId"; + public static final String PUBLISH_TYPE = "publishType"; + + public static final String GRAY_NAME = "grayName"; + + public static final String GRAY_RULE = "grayRule"; + public static final String QUOTA = "quota"; public static final String MAX_SIZE = "maxSize"; diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/TableConstant.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/TableConstant.java index 8ff06df344d..934bc4ee128 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/TableConstant.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/constants/TableConstant.java @@ -26,12 +26,12 @@ public class TableConstant { public static final String CONFIG_INFO = "config_info"; - public static final String CONFIG_INFO_AGGR = "config_info_aggr"; - public static final String CONFIG_INFO_BETA = "config_info_beta"; public static final String CONFIG_INFO_TAG = "config_info_tag"; + public static final String CONFIG_INFO_GRAY = "config_info_gray"; + public static final String CONFIG_TAGS_RELATION = "config_tags_relation"; public static final String GROUP_CAPACITY = "group_capacity"; diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoAggrMapperByDerby.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoAggrMapperByDerby.java deleted file mode 100644 index 3750ab86512..00000000000 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoAggrMapperByDerby.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 1999-2022 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.plugin.datasource.impl.derby; - -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; -import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; -import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoAggrMapper; -import com.alibaba.nacos.plugin.datasource.model.MapperContext; -import com.alibaba.nacos.plugin.datasource.model.MapperResult; - -import java.util.List; - -/** - * The derby implementation of ConfigInfoAggrMapper. - * - * @author hyx - **/ -public class ConfigInfoAggrMapperByDerby extends AbstractMapperByDerby implements ConfigInfoAggrMapper { - - @Override - public MapperResult findConfigInfoAggrByPageFetchRows(MapperContext context) { - final Integer startRow = context.getStartRow(); - final Integer pageSize = context.getPageSize(); - final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); - final String groupId = (String) context.getWhereParameter(FieldConstant.GROUP_ID); - final String tenantId = (String) context.getWhereParameter(FieldConstant.TENANT_ID); - - String sql = - "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM config_info_aggr WHERE data_id=? AND " - + "group_id=? AND tenant_id=? ORDER BY datum_id OFFSET " + startRow + " ROWS FETCH NEXT " - + pageSize + " ROWS ONLY"; - List paramList = CollectionUtils.list(dataId, groupId, tenantId); - return new MapperResult(sql, paramList); - } - - @Override - public String getDataSource() { - return DataSourceConstant.DERBY; - } -} diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoGrayMapperByDerby.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoGrayMapperByDerby.java new file mode 100644 index 00000000000..bb9f4c01e09 --- /dev/null +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoGrayMapperByDerby.java @@ -0,0 +1,59 @@ +/* + * Copyright 1999-2022 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.plugin.datasource.impl.derby; + +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; +import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; +import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoGrayMapper; +import com.alibaba.nacos.plugin.datasource.model.MapperContext; +import com.alibaba.nacos.plugin.datasource.model.MapperResult; + +import java.util.Collections; + +/** + * The derby implementation of ConfigInfoGrayMapper. + * + * @author rong + **/ + +public class ConfigInfoGrayMapperByDerby extends AbstractMapperByDerby implements ConfigInfoGrayMapper { + + @Override + public MapperResult findAllConfigInfoGrayForDumpAllFetchRows(MapperContext context) { + String sql = "SELECT t.id,data_id,group_id,tenant_id,gray_name,gray_rule,app_name,content,md5,gmt_modified " + + " FROM ( SELECT id FROM config_info_gray ORDER BY id OFFSET " + context.getStartRow() + + " ROWS FETCH NEXT " + context.getPageSize() + " ROWS ONLY ) " + + " g, config_info_gray t WHERE g.id = t.id"; + return new MapperResult(sql, Collections.emptyList()); + } + + @Override + public MapperResult findChangeConfig(MapperContext context) { + String sql = "SELECT id, data_id, group_id, tenant_id, app_name, content,gray_name,gray_rule, " + + "gmt_modified, encrypted_data_key FROM config_info_gray WHERE " + + "gmt_modified >= ? and id > ? order by id OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY"; + return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME), + context.getWhereParameter(FieldConstant.LAST_MAX_ID), + context.getWhereParameter(FieldConstant.PAGE_SIZE))); + } + + @Override + public String getDataSource() { + return DataSourceConstant.DERBY; + } +} diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java index 08942c9a4fe..47a5a5a31df 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerby.java @@ -33,8 +33,8 @@ public class HistoryConfigInfoMapperByDerby extends AbstractMapperByDerby implem @Override public MapperResult removeConfigHistory(MapperContext context) { - String sql = "DELETE FROM his_config_info WHERE id IN( " - + "SELECT id FROM his_config_info WHERE gmt_modified < ? OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY)"; + String sql = "DELETE FROM his_config_info WHERE nid IN( " + + "SELECT nid FROM his_config_info WHERE gmt_modified < ? OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY)"; return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME), context.getWhereParameter(FieldConstant.LIMIT_SIZE))); } @@ -42,7 +42,8 @@ public MapperResult removeConfigHistory(MapperContext context) { @Override public MapperResult pageFindConfigHistoryFetchRows(MapperContext context) { String sql = - "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gmt_create,gmt_modified FROM his_config_info " + "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gray_name,ext_info,publish_type,gmt_create,gmt_modified " + + "FROM his_config_info " + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC OFFSET " + context.getStartRow() + " ROWS FETCH NEXT " + context.getPageSize() + " ROWS ONLY"; return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.DATA_ID), @@ -57,9 +58,11 @@ public String getDataSource() { @Override public MapperResult findDeletedConfig(MapperContext context) { return new MapperResult( - "SELECT data_id, group_id, tenant_id,gmt_modified,nid FROM his_config_info WHERE op_type = 'D' AND " - + "gmt_modified >= ? and nid > ? order by nid OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY", - CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME), + "SELECT id, nid, data_id, group_id, app_name, content, md5, gmt_create, gmt_modified, src_user, src_ip, op_type, tenant_id, " + + "publish_type,gray_name, ext_info, encrypted_data_key FROM his_config_info WHERE op_type = 'D' AND " + + "publish_type = ? and gmt_modified >= ? and nid > ? order by nid OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY", + CollectionUtils.list(context.getWhereParameter(FieldConstant.PUBLISH_TYPE), + context.getWhereParameter(FieldConstant.START_TIME), context.getWhereParameter(FieldConstant.LAST_MAX_ID), context.getWhereParameter(FieldConstant.PAGE_SIZE))); } diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoAggrMapperByMySql.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoAggrMapperByMySql.java deleted file mode 100644 index 03382271cb0..00000000000 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoAggrMapperByMySql.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright 1999-2022 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.plugin.datasource.impl.mysql; - -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; -import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; -import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoAggrMapper; -import com.alibaba.nacos.plugin.datasource.model.MapperContext; -import com.alibaba.nacos.plugin.datasource.model.MapperResult; - -import java.util.List; - -/** - * The mysql implementation of ConfigInfoAggrMapper. - * - * @author hyx - **/ -public class ConfigInfoAggrMapperByMySql extends AbstractMapperByMysql implements ConfigInfoAggrMapper { - - @Override - public MapperResult findConfigInfoAggrByPageFetchRows(MapperContext context) { - int startRow = context.getStartRow(); - int pageSize = context.getPageSize(); - String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); - String groupId = (String) context.getWhereParameter(FieldConstant.GROUP_ID); - String tenantId = (String) context.getWhereParameter(FieldConstant.TENANT_ID); - - String sql = - "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM config_info_aggr WHERE data_id= ? AND " - + "group_id= ? AND tenant_id= ? ORDER BY datum_id LIMIT " + startRow + "," + pageSize; - List paramList = CollectionUtils.list(dataId, groupId, tenantId); - return new MapperResult(sql, paramList); - } - - @Override - public String getDataSource() { - return DataSourceConstant.MYSQL; - } -} diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoGrayMapperByMySql.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoGrayMapperByMySql.java new file mode 100644 index 00000000000..40e96450879 --- /dev/null +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoGrayMapperByMySql.java @@ -0,0 +1,45 @@ +/* + * Copyright 1999-2022 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.plugin.datasource.impl.mysql; + +import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; +import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoGrayMapper; +import com.alibaba.nacos.plugin.datasource.model.MapperContext; +import com.alibaba.nacos.plugin.datasource.model.MapperResult; + +import java.util.Collections; + +/** + * The mysql implementation of ConfigInfoGrayMapper. + * + * @author rong + **/ + +public class ConfigInfoGrayMapperByMySql extends AbstractMapperByMysql implements ConfigInfoGrayMapper { + + @Override + public MapperResult findAllConfigInfoGrayForDumpAllFetchRows(MapperContext context) { + String sql = " SELECT id,data_id,group_id,tenant_id,gray_name,gray_rule,app_name,content,md5,gmt_modified " + + " FROM config_info_gray ORDER BY id LIMIT " + context.getStartRow() + "," + context.getPageSize(); + return new MapperResult(sql, Collections.emptyList()); + } + + @Override + public String getDataSource() { + return DataSourceConstant.MYSQL; + } +} diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java index dd4de5b6efe..cd6925531e9 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySql.java @@ -250,7 +250,7 @@ public MapperResult findConfigInfoLike4PageFetchRows(MapperContext context) { where.and().like("content", content); } if (!ArrayUtils.isEmpty(types)) { - where.in("type", types); + where.and().in("type", types); } where.limit(context.getStartRow(), context.getPageSize()); return where.build(); diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySql.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySql.java index 39ec2a8ca0e..fad71981f32 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySql.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySql.java @@ -41,8 +41,8 @@ public MapperResult removeConfigHistory(MapperContext context) { @Override public MapperResult pageFindConfigHistoryFetchRows(MapperContext context) { String sql = - "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gmt_create,gmt_modified FROM his_config_info " - + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC LIMIT " + "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,ext_info,publish_type,gray_name,gmt_create,gmt_modified " + + "FROM his_config_info " + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC LIMIT " + context.getStartRow() + "," + context.getPageSize(); return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.DATA_ID), context.getWhereParameter(FieldConstant.GROUP_ID), context.getWhereParameter(FieldConstant.TENANT_ID))); diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapper.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapper.java index ba85b7ffaaa..81c9adc8a87 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapper.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapper.java @@ -19,6 +19,7 @@ import com.alibaba.nacos.common.utils.CollectionUtils; import java.util.List; +import java.util.stream.Collectors; /** * The abstract mapper contains CRUD methods. @@ -33,17 +34,9 @@ public String select(List columns, List where) { StringBuilder sql = new StringBuilder(); String method = "SELECT "; sql.append(method); - for (int i = 0; i < columns.size(); i++) { - sql.append(columns.get(i)); - if (i == columns.size() - 1) { - sql.append(" "); - } else { - sql.append(","); - } - } - sql.append("FROM "); + sql.append(columns.stream().collect(Collectors.joining(","))); + sql.append(" FROM "); sql.append(getTableName()); - sql.append(" "); if (CollectionUtils.isEmpty(where)) { return sql.toString(); @@ -111,7 +104,6 @@ public String update(List columns, List where) { return sql.toString(); } - sql.append(" "); appendWhereClause(where, sql); return sql.toString(); @@ -121,13 +113,8 @@ public String update(List columns, List where) { public String delete(List params) { StringBuilder sql = new StringBuilder(); String method = "DELETE "; - sql.append(method).append("FROM ").append(getTableName()).append(" ").append("WHERE "); - for (int i = 0; i < params.size(); i++) { - sql.append(params.get(i)).append(" ").append("=").append(" ? "); - if (i != params.size() - 1) { - sql.append("AND "); - } - } + sql.append(method).append("FROM ").append(getTableName()); + appendWhereClause(params, sql); return sql.toString(); } @@ -139,9 +126,8 @@ public String count(List where) { sql.append(method); sql.append("COUNT(*) FROM "); sql.append(getTableName()); - sql.append(" "); - if (null == where || where.size() == 0) { + if (CollectionUtils.isEmpty(where)) { return sql.toString(); } @@ -156,12 +142,7 @@ public String[] getPrimaryKeyGeneratedKeys() { } private void appendWhereClause(List where, StringBuilder sql) { - sql.append("WHERE "); - for (int i = 0; i < where.size(); i++) { - sql.append(where.get(i)).append(" = ").append("?"); - if (i != where.size() - 1) { - sql.append(" AND "); - } - } + sql.append(" WHERE "); + sql.append(where.stream().map(str -> (str + " = ?")).collect(Collectors.joining(" AND "))); } } diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/ConfigInfoAggrMapper.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/ConfigInfoAggrMapper.java deleted file mode 100644 index 0b3bae1fa0e..00000000000 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/ConfigInfoAggrMapper.java +++ /dev/null @@ -1,160 +0,0 @@ -/* - * Copyright 1999-2022 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.plugin.datasource.mapper; - -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; -import com.alibaba.nacos.plugin.datasource.constants.TableConstant; -import com.alibaba.nacos.plugin.datasource.model.MapperContext; -import com.alibaba.nacos.plugin.datasource.model.MapperResult; - -import java.util.ArrayList; -import java.util.List; - -/** - * The mapper of config info. - * - * @author hyx - **/ - -public interface ConfigInfoAggrMapper extends Mapper { - - /** - * To delete aggregated data in bulk, you need to specify a size of datum list. - * The default sql: - * DELETE FROM config_info_aggr WHERE data_id=? AND group_id=? AND tenant_id=? AND datum_id IN (...) - * - * @param context The context of datum_id, data_id, group_id, tenant_id - * @return The sql of deleting aggregated data in bulk. - */ - default MapperResult batchRemoveAggr(MapperContext context) { - final List datumList = (List) context.getWhereParameter(FieldConstant.DATUM_ID); - final String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); - final String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); - final String tenantTmp = (String) context.getWhereParameter(FieldConstant.TENANT_ID); - - List paramList = new ArrayList<>(); - paramList.add(dataId); - paramList.add(group); - paramList.add(tenantTmp); - - final StringBuilder placeholderString = new StringBuilder(); - for (int i = 0; i < datumList.size(); i++) { - if (i != 0) { - placeholderString.append(", "); - } - placeholderString.append('?'); - paramList.add(datumList.get(i)); - } - - String sql = - "DELETE FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND datum_id IN (" - + placeholderString + ")"; - - return new MapperResult(sql, paramList); - } - - /** - * Get count of aggregation config info. - * The default sql: - * SELECT count(*) FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? - * - * @param context The context of datum_id, isIn, data_id, group_id, tenant_id - * @return The sql of getting count of aggregation config info. - */ - default MapperResult aggrConfigInfoCount(MapperContext context) { - final List datumIds = (List) context.getWhereParameter(FieldConstant.DATUM_ID); - final Boolean isIn = (Boolean) context.getWhereParameter(FieldConstant.IS_IN); - String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); - String group = (String) context.getWhereParameter(FieldConstant.GROUP_ID); - String tenantTmp = (String) context.getWhereParameter(FieldConstant.TENANT_ID); - - List paramList = CollectionUtils.list(dataId, group, tenantTmp); - - StringBuilder sql = new StringBuilder( - "SELECT count(*) FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND datum_id"); - if (isIn) { - sql.append(" IN ("); - } else { - sql.append(" NOT IN ("); - } - for (int i = 0; i < datumIds.size(); i++) { - if (i > 0) { - sql.append(", "); - } - sql.append('?'); - paramList.add(datumIds.get(i)); - } - sql.append(')'); - - return new MapperResult(sql.toString(), paramList); - } - - /** - * Find all data before aggregation under a dataId. It is guaranteed not to return NULL. - * The default sql: - * SELECT data_id,group_id,tenant_id,datum_id,app_name,content - * FROM config_info_aggr WHERE data_id=? AND group_id=? AND tenant_id=? ORDER BY datum_id - * - * @param context The context of data_id, group_id, tenant_id - * @return The sql of finding all data before aggregation under a dataId. - */ - default MapperResult findConfigInfoAggrIsOrdered(MapperContext context) { - String dataId = (String) context.getWhereParameter(FieldConstant.DATA_ID); - String groupId = (String) context.getWhereParameter(FieldConstant.GROUP_ID); - String tenantId = (String) context.getWhereParameter(FieldConstant.TENANT_ID); - - String sql = "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM " - + "config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY datum_id"; - List paramList = CollectionUtils.list(dataId, groupId, tenantId); - - return new MapperResult(sql, paramList); - } - - /** - * Query aggregation config info. - * The default sql: - * SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM config_info_aggr WHERE data_id=? AND - * group_id=? AND tenant_id=? ORDER BY datum_id LIMIT startRow,pageSize - * - * @param context The context of startRow, pageSize, data_id, group_id, tenant_id - * @return The sql of querying aggregation config info. - */ - MapperResult findConfigInfoAggrByPageFetchRows(MapperContext context); - - /** - * Find all aggregated data sets. - * The default sql: - * SELECT DISTINCT data_id, group_id, tenant_id FROM config_info_aggr - * - * @param context sql paramMap - * @return The sql of finding all aggregated data sets. - */ - default MapperResult findAllAggrGroupByDistinct(MapperContext context) { - return new MapperResult("SELECT DISTINCT data_id, group_id, tenant_id FROM config_info_aggr", - CollectionUtils.list()); - } - - /** - * 获取返回表名. - * - * @return 表名 - */ - default String getTableName() { - return TableConstant.CONFIG_INFO_AGGR; - } -} diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/ConfigInfoGrayMapper.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/ConfigInfoGrayMapper.java new file mode 100644 index 00000000000..de2bf9dd1d0 --- /dev/null +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/ConfigInfoGrayMapper.java @@ -0,0 +1,97 @@ +/* + * Copyright 1999-2022 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.plugin.datasource.mapper; + +import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; +import com.alibaba.nacos.plugin.datasource.constants.TableConstant; +import com.alibaba.nacos.plugin.datasource.model.MapperContext; +import com.alibaba.nacos.plugin.datasource.model.MapperResult; + +/** + * The config gray info mapper. + * + * @author rong + **/ + +public interface ConfigInfoGrayMapper extends Mapper { + + /** + * Update gray configuration information. The default sql: UPDATE config_info_gray SET content=?, md5 = ?, + * src_ip=?,src_user=?,gmt_modified=?,app_name=?,gray_rule=? WHERE data_id=? AND group_id=? AND tenant_id=? AND + * gray_name=? AND (md5=? or md5 is null or md5='') + * + * @param context sql paramMap + * @return The sql of updating gray configuration information. + */ + default MapperResult updateConfigInfo4GrayCas(MapperContext context) { + Object content = context.getUpdateParameter(FieldConstant.CONTENT); + Object md5 = context.getUpdateParameter(FieldConstant.MD5); + Object srcIp = context.getUpdateParameter(FieldConstant.SRC_IP); + Object srcUser = context.getUpdateParameter(FieldConstant.SRC_USER); + Object gmtModified = context.getUpdateParameter(FieldConstant.GMT_MODIFIED); + Object appName = context.getUpdateParameter(FieldConstant.APP_NAME); + + Object dataId = context.getWhereParameter(FieldConstant.DATA_ID); + Object groupId = context.getWhereParameter(FieldConstant.GROUP_ID); + Object tenantId = context.getWhereParameter(FieldConstant.TENANT_ID); + Object grayName = context.getWhereParameter(FieldConstant.GRAY_NAME); + Object grayRule = context.getWhereParameter(FieldConstant.GRAY_RULE); + Object oldMd5 = context.getWhereParameter(FieldConstant.MD5); + String sql = "UPDATE config_info_gray SET content = ?, md5 = ?, src_ip = ?,src_user = ?,gmt_modified = " + + getFunction("NOW()") + ",app_name = ?, gray_rule = ?" + + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND gray_name = ? AND (md5 = ? OR md5 IS NULL OR md5 = '')"; + return new MapperResult(sql, + CollectionUtils.list(content, md5, srcIp, srcUser, appName, grayRule, dataId, groupId, + tenantId, grayName, oldMd5)); + } + + /** + * Query change config.
The default sql: SELECT data_id, group_id, tenant_id, app_name, content, + * gmt_modified,encrypted_data_key FROM config_info WHERE gmt_modified >=? AND gmt_modified <= ? + * + * @param context sql paramMap + * @return The sql of querying change config. + */ + default MapperResult findChangeConfig(MapperContext context) { + String sql = + "SELECT id, data_id, group_id, tenant_id, app_name,content,gray_name,gray_rule,md5, gmt_modified, encrypted_data_key " + + "FROM config_info_gray WHERE " + "gmt_modified >= ? and id > ? order by id limit ? "; + return new MapperResult(sql, CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME), + context.getWhereParameter(FieldConstant.LAST_MAX_ID), + context.getWhereParameter(FieldConstant.PAGE_SIZE))); + } + + /** + * Query all gray config info for dump task. The default sql: SELECT + * t.id,data_id,group_id,tenant_id,gray_name,app_name,content,md5,gmt_modified FROM ( SELECT id FROM + * config_info_gray ORDER BY id LIMIT startRow,pageSize ) g, config_info_gray t WHERE g.id = t.id + * + * @param context The start index. + * @return The sql of querying all gray config info for dump task. + */ + MapperResult findAllConfigInfoGrayForDumpAllFetchRows(MapperContext context); + + /** + * 获取返回表名. + * + * @return 表名 + */ + default String getTableName() { + return TableConstant.CONFIG_INFO_GRAY; + } +} diff --git a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java index de6a2f60953..96dd13d0602 100644 --- a/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java +++ b/plugin/datasource/src/main/java/com/alibaba/nacos/plugin/datasource/mapper/HistoryConfigInfoMapper.java @@ -17,12 +17,14 @@ package com.alibaba.nacos.plugin.datasource.mapper; import com.alibaba.nacos.common.utils.CollectionUtils; +import com.alibaba.nacos.common.utils.StringUtils; import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; import com.alibaba.nacos.plugin.datasource.constants.TableConstant; import com.alibaba.nacos.plugin.datasource.model.MapperContext; import com.alibaba.nacos.plugin.datasource.model.MapperResult; import java.util.Collections; +import java.util.List; /** * The history config info mapper. @@ -61,9 +63,11 @@ default MapperResult findConfigHistoryCountByTime(MapperContext context) { */ default MapperResult findDeletedConfig(MapperContext context) { return new MapperResult( - "SELECT data_id, group_id, tenant_id,gmt_modified,nid FROM his_config_info WHERE op_type = 'D' AND " - + "gmt_modified >= ? and nid > ? order by nid limit ? ", - CollectionUtils.list(context.getWhereParameter(FieldConstant.START_TIME), + "SELECT id, nid, data_id, group_id, app_name, content, md5, gmt_create, gmt_modified, src_user, src_ip, op_type, tenant_id, " + + "publish_type, gray_name, ext_info, encrypted_data_key FROM his_config_info WHERE op_type = 'D' AND " + + "publish_type = ? and gmt_modified >= ? and nid > ? order by nid limit ? ", + CollectionUtils.list(context.getWhereParameter(FieldConstant.PUBLISH_TYPE), + context.getWhereParameter(FieldConstant.START_TIME), context.getWhereParameter(FieldConstant.LAST_MAX_ID), context.getWhereParameter(FieldConstant.PAGE_SIZE))); } @@ -78,7 +82,8 @@ default MapperResult findDeletedConfig(MapperContext context) { */ default MapperResult findConfigHistoryFetchRows(MapperContext context) { return new MapperResult( - "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gmt_create,gmt_modified FROM his_config_info " + "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,publish_type,gray_name,op_type," + + "gmt_create,gmt_modified FROM his_config_info " + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC", CollectionUtils.list(context.getWhereParameter(FieldConstant.DATA_ID), context.getWhereParameter(FieldConstant.GROUP_ID), @@ -105,8 +110,8 @@ default MapperResult findConfigHistoryFetchRows(MapperContext context) { */ default MapperResult detailPreviousConfigHistory(MapperContext context) { return new MapperResult( - "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create,gmt_modified,encrypted_data_key " - + "FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)", + "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,publish_type,gray_name,ext_info,gmt_create" + + ",gmt_modified,encrypted_data_key FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)", Collections.singletonList(context.getWhereParameter(FieldConstant.ID))); } @@ -118,4 +123,33 @@ default MapperResult detailPreviousConfigHistory(MapperContext context) { default String getTableName() { return TableConstant.HIS_CONFIG_INFO; } + + /** + * Get updated history config detail of the history config. The default sql: SELECT + * nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create,gmt_modified FROM + * his_config_info WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND publish_type = ? AND gray_name = ? + * AND nid > ? ORDER BY nid LIMIT 1 + * + * @param context sql paramMap + * @return The sql of getting the next history config detail of the history config. + */ + default MapperResult getNextHistoryInfo(MapperContext context) { + String sql = "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,publish_type," + + "gray_name,ext_info,gmt_create,gmt_modified,encrypted_data_key FROM his_config_info " + + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? AND publish_type = ? " + + (StringUtils.isBlank(context.getContextParameter(FieldConstant.GRAY_NAME)) ? "" : "AND gray_name = ? ") + + "AND nid > ? ORDER BY nid LIMIT 1"; + + List paramList = CollectionUtils.list( + context.getWhereParameter(FieldConstant.DATA_ID), + context.getWhereParameter(FieldConstant.GROUP_ID), + context.getWhereParameter(FieldConstant.TENANT_ID), + context.getWhereParameter(FieldConstant.PUBLISH_TYPE), + context.getWhereParameter(FieldConstant.NID)); + if (!StringUtils.isEmpty(context.getContextParameter(FieldConstant.GRAY_NAME))) { + paramList.add(4, context.getWhereParameter(FieldConstant.GRAY_NAME)); + } + + return new MapperResult(sql, paramList); + } } diff --git a/plugin/datasource/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.datasource.mapper.Mapper b/plugin/datasource/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.datasource.mapper.Mapper index f0d6adeb57f..0a7fa58522f 100644 --- a/plugin/datasource/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.datasource.mapper.Mapper +++ b/plugin/datasource/src/main/resources/META-INF/services/com.alibaba.nacos.plugin.datasource.mapper.Mapper @@ -14,20 +14,20 @@ # limitations under the License. # -com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoAggrMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoBetaMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoTagMapperByMySql +com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoGrayMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigTagsRelationMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.HistoryConfigInfoMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.TenantInfoMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.TenantCapacityMapperByMySql com.alibaba.nacos.plugin.datasource.impl.mysql.GroupCapacityMapperByMysql -com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoAggrMapperByDerby com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoBetaMapperByDerby com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoMapperByDerby com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoTagMapperByDerby +com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoGrayMapperByDerby com.alibaba.nacos.plugin.datasource.impl.derby.ConfigInfoTagsRelationMapperByDerby com.alibaba.nacos.plugin.datasource.impl.derby.HistoryConfigInfoMapperByDerby com.alibaba.nacos.plugin.datasource.impl.derby.TenantInfoMapperByDerby diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/MapperManagerTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/MapperManagerTest.java index eff15277ce5..ab8bc7b764c 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/MapperManagerTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/MapperManagerTest.java @@ -18,7 +18,7 @@ import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; import com.alibaba.nacos.plugin.datasource.impl.mysql.AbstractMapperByMysql; -import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoAggrMapper; +import com.alibaba.nacos.plugin.datasource.mapper.ConfigInfoGrayMapper; import com.alibaba.nacos.plugin.datasource.mapper.Mapper; import com.alibaba.nacos.plugin.datasource.mapper.TestMapper; import org.junit.jupiter.api.Test; @@ -55,7 +55,7 @@ void testJoin() { public String getTableName() { return "test"; } - + @Override public String getDataSource() { return DataSourceConstant.MYSQL; @@ -78,7 +78,7 @@ void testFindMapper() { void testEnableDataSourceLogJoin() { MapperManager.join(new TestMapper()); MapperManager instance = MapperManager.instance(true); - ConfigInfoAggrMapper mapper = instance.findMapper(DataSourceConstant.MYSQL, "enable_data_source_log_test"); + ConfigInfoGrayMapper mapper = instance.findMapper(DataSourceConstant.MYSQL, "enable_data_source_log_test"); assertNotNull(mapper); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoAggrMapperByDerbyTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoAggrMapperByDerbyTest.java deleted file mode 100644 index e0abf043c1e..00000000000 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoAggrMapperByDerbyTest.java +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 1999-2022 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.plugin.datasource.impl.derby; - -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; -import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; -import com.alibaba.nacos.plugin.datasource.constants.TableConstant; -import com.alibaba.nacos.plugin.datasource.model.MapperContext; -import com.alibaba.nacos.plugin.datasource.model.MapperResult; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class ConfigInfoAggrMapperByDerbyTest { - - private ConfigInfoAggrMapperByDerby configInfoAggrMapperByDerby; - - @BeforeEach - void setUp() throws Exception { - this.configInfoAggrMapperByDerby = new ConfigInfoAggrMapperByDerby(); - } - - @Test - void testBatchRemoveAggr() { - List datumList = Arrays.asList("1", "2", "3", "4", "5"); - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - List argList = CollectionUtils.list(dataId, groupId, tenantId); - argList.addAll(datumList); - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATUM_ID, datumList); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - - MapperResult result = configInfoAggrMapperByDerby.batchRemoveAggr(context); - String sql = result.getSql(); - List paramList = result.getParamList(); - - assertEquals(sql, - "DELETE FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? " + "AND datum_id IN (?, ?, ?, ?, ?)"); - assertEquals(paramList, argList); - } - - @Test - void testAggrConfigInfoCount() { - List datumIds = Arrays.asList("1", "2", "3", "4", "5"); - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - List argList = CollectionUtils.list(dataId, groupId, tenantId); - argList.addAll(datumIds); - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATUM_ID, datumIds); - context.putWhereParameter(FieldConstant.IS_IN, true); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - - MapperResult mapperResult = configInfoAggrMapperByDerby.aggrConfigInfoCount(context); - String sql = mapperResult.getSql(); - List paramList = mapperResult.getParamList(); - - assertEquals(sql, "SELECT count(*) FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? " - + "AND datum_id IN (?, ?, ?, ?, ?)"); - assertEquals(paramList, argList); - } - - @Test - void testFindConfigInfoAggrIsOrdered() { - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - - MapperResult mapperResult = configInfoAggrMapperByDerby.findConfigInfoAggrIsOrdered(context); - String sql = mapperResult.getSql(); - List paramList = mapperResult.getParamList(); - - assertEquals(sql, "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM " - + "config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY datum_id"); - assertEquals(paramList, CollectionUtils.list(dataId, groupId, tenantId)); - } - - @Test - void testFindConfigInfoAggrByPageFetchRows() { - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - Integer startRow = 0; - Integer pageSize = 5; - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - context.setStartRow(startRow); - context.setPageSize(pageSize); - - MapperResult mapperResult = configInfoAggrMapperByDerby.findConfigInfoAggrByPageFetchRows(context); - String sql = mapperResult.getSql(); - List paramList = mapperResult.getParamList(); - assertEquals(sql, "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM config_info_aggr WHERE " - + "data_id=? AND group_id=? AND tenant_id=? ORDER BY datum_id OFFSET 0 ROWS FETCH NEXT 5 ROWS ONLY"); - assertEquals(paramList, CollectionUtils.list(dataId, groupId, tenantId)); - } - - @Test - void testFindAllAggrGroupByDistinct() { - MapperResult sql = configInfoAggrMapperByDerby.findAllAggrGroupByDistinct(null); - assertEquals("SELECT DISTINCT data_id, group_id, tenant_id FROM config_info_aggr", sql.getSql()); - } - - @Test - void testGetTableName() { - String tableName = configInfoAggrMapperByDerby.getTableName(); - assertEquals(TableConstant.CONFIG_INFO_AGGR, tableName); - } - - @Test - void testGetDataSource() { - String dataSource = configInfoAggrMapperByDerby.getDataSource(); - assertEquals(DataSourceConstant.DERBY, dataSource); - } -} diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoMapperByDerbyTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoMapperByDerbyTest.java index 4856c1dfc5e..1dfd204c691 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoMapperByDerbyTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/ConfigInfoMapperByDerbyTest.java @@ -114,7 +114,7 @@ void testConfigInfoLikeTenantCount() { void testGetTenantIdList() { MapperResult mapperResult = configInfoMapperByDerby.getTenantIdList(context); assertEquals(mapperResult.getSql(), - "SELECT tenant_id FROM config_info WHERE tenant_id != '' GROUP BY tenant_id OFFSET " + startRow + " ROWS FETCH NEXT " + "SELECT tenant_id FROM config_info WHERE tenant_id != 'public' GROUP BY tenant_id OFFSET " + startRow + " ROWS FETCH NEXT " + pageSize + " ROWS ONLY"); assertArrayEquals(mapperResult.getParamList().toArray(), emptyObjs); } @@ -123,7 +123,7 @@ void testGetTenantIdList() { void testGetGroupIdList() { MapperResult mapperResult = configInfoMapperByDerby.getGroupIdList(context); assertEquals(mapperResult.getSql(), - "SELECT group_id FROM config_info WHERE tenant_id ='' GROUP BY group_id OFFSET " + startRow + " ROWS FETCH NEXT " + pageSize + "SELECT group_id FROM config_info WHERE tenant_id ='public' GROUP BY group_id OFFSET " + startRow + " ROWS FETCH NEXT " + pageSize + " ROWS ONLY"); assertArrayEquals(mapperResult.getParamList().toArray(), emptyObjs); } @@ -221,7 +221,7 @@ void testFindAllConfigInfo4Export() { @Test void testFindConfigInfoBaseLikeCountRows() { MapperResult mapperResult = configInfoMapperByDerby.findConfigInfoBaseLikeCountRows(context); - assertEquals("SELECT count(*) FROM config_info WHERE 1=1 AND tenant_id='' ", mapperResult.getSql()); + assertEquals("SELECT count(*) FROM config_info WHERE 1=1 AND tenant_id='public' ", mapperResult.getSql()); assertArrayEquals(mapperResult.getParamList().toArray(), emptyObjs); } @@ -229,7 +229,7 @@ void testFindConfigInfoBaseLikeCountRows() { void testFindConfigInfoBaseLikeFetchRows() { MapperResult mapperResult = configInfoMapperByDerby.findConfigInfoBaseLikeFetchRows(context); assertEquals(mapperResult.getSql(), - "SELECT id,data_id,group_id,tenant_id,content FROM config_info WHERE 1=1 AND tenant_id='' " + "OFFSET " + startRow + "SELECT id,data_id,group_id,tenant_id,content FROM config_info WHERE 1=1 AND tenant_id='public' " + "OFFSET " + startRow + " ROWS FETCH NEXT " + pageSize + " ROWS ONLY"); assertArrayEquals(mapperResult.getParamList().toArray(), emptyObjs); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/GroupCapacityMapperByDerbyTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/GroupCapacityMapperByDerbyTest.java index e6a94b3e24d..288d9790b19 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/GroupCapacityMapperByDerbyTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/GroupCapacityMapperByDerbyTest.java @@ -116,7 +116,7 @@ void testInsertIntoSelectByWhere() { MapperResult mapperResult = groupCapacityMapperByDerby.insertIntoSelectByWhere(context); assertEquals(mapperResult.getSql(), "INSERT INTO group_capacity (group_id, quota, usage, max_size, max_aggr_count, max_aggr_size, gmt_create," - + " gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE group_id=? AND tenant_id = ''"); + + " gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE group_id=? AND tenant_id = 'public'"); assertArrayEquals(new Object[] {group, quota, maxSize, maxAggrCount, maxAggrSize, createTime, modified, group}, mapperResult.getParamList().toArray()); } @@ -167,7 +167,7 @@ void testUpdateUsage() { void testUpdateUsageByWhere() { MapperResult mapperResult = groupCapacityMapperByDerby.updateUsageByWhere(context); assertEquals(mapperResult.getSql(), - "UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info WHERE group_id=? AND tenant_id = '')," + "UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info WHERE group_id=? AND tenant_id = 'public')," + " gmt_modified = ? WHERE group_id= ?"); assertArrayEquals(new Object[] {groupId, modified, groupId}, mapperResult.getParamList().toArray()); diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java index 434a4d583fc..8b95de9aa27 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/derby/HistoryConfigInfoMapperByDerbyTest.java @@ -43,6 +43,8 @@ class HistoryConfigInfoMapperByDerbyTest { Timestamp endTime = new Timestamp(System.currentTimeMillis()); + String publishType = "formal"; + MapperContext context; private HistoryConfigInfoMapperByDerby historyConfigInfoMapperByDerby; @@ -56,6 +58,7 @@ void setUp() throws Exception { context.putWhereParameter(FieldConstant.LIMIT_SIZE, limitSize); context.putWhereParameter(FieldConstant.LAST_MAX_ID, lastMaxId); context.putWhereParameter(FieldConstant.PAGE_SIZE, pageSize); + context.putWhereParameter(FieldConstant.PUBLISH_TYPE, publishType); } @@ -63,7 +66,7 @@ void setUp() throws Exception { void testRemoveConfigHistory() { MapperResult mapperResult = historyConfigInfoMapperByDerby.removeConfigHistory(context); assertEquals(mapperResult.getSql(), - "DELETE FROM his_config_info WHERE id IN( SELECT id FROM his_config_info WHERE gmt_modified < ? " + "DELETE FROM his_config_info WHERE nid IN( SELECT nid FROM his_config_info WHERE gmt_modified < ? " + "OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY)"); assertArrayEquals(new Object[] {startTime, limitSize}, mapperResult.getParamList().toArray()); } @@ -78,10 +81,13 @@ void testFindConfigHistoryCountByTime() { @Test void testFindDeletedConfig() { MapperResult mapperResult = historyConfigInfoMapperByDerby.findDeletedConfig(context); - assertEquals(mapperResult.getSql(), "SELECT data_id, group_id, tenant_id,gmt_modified,nid FROM his_config_info WHERE op_type = 'D' " - + "AND gmt_modified >= ? and nid > ? order by nid OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY"); - - assertArrayEquals(new Object[] {startTime, lastMaxId, pageSize}, mapperResult.getParamList().toArray()); + assertEquals( + "SELECT id, nid, data_id, group_id, app_name, content, md5, gmt_create, gmt_modified, src_user, src_ip," + + " op_type, tenant_id, publish_type,gray_name, ext_info, encrypted_data_key FROM his_config_info WHERE op_type = 'D' AND " + + "publish_type = ? and gmt_modified >= ? and nid > ? order by nid OFFSET 0 ROWS FETCH NEXT ? ROWS ONLY", + mapperResult.getSql()); + assertArrayEquals(new Object[] {publishType, startTime, lastMaxId, pageSize}, + mapperResult.getParamList().toArray()); } @Test @@ -96,7 +102,8 @@ void testFindConfigHistoryFetchRows() { context.putWhereParameter(FieldConstant.DATA_ID, dataId); MapperResult mapperResult = historyConfigInfoMapperByDerby.findConfigHistoryFetchRows(context); assertEquals(mapperResult.getSql(), - "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gmt_create,gmt_modified FROM his_config_info " + "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,publish_type,gray_name," + + "op_type,gmt_create,gmt_modified FROM his_config_info " + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC"); assertArrayEquals(new Object[] {dataId, groupId, tenantId}, mapperResult.getParamList().toArray()); } @@ -106,8 +113,10 @@ void testDetailPreviousConfigHistory() { Object id = "1"; context.putWhereParameter(FieldConstant.ID, id); MapperResult mapperResult = historyConfigInfoMapperByDerby.detailPreviousConfigHistory(context); - assertEquals(mapperResult.getSql(), "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create," - + "gmt_modified,encrypted_data_key FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); + assertEquals(mapperResult.getSql(), + "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,publish_type" + + ",gray_name,ext_info,gmt_create,gmt_modified,encrypted_data_key " + + "FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); assertArrayEquals(new Object[] {id}, mapperResult.getParamList().toArray()); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoAggrMapperByMySqlTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoAggrMapperByMySqlTest.java deleted file mode 100644 index a55d20fd88b..00000000000 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoAggrMapperByMySqlTest.java +++ /dev/null @@ -1,154 +0,0 @@ -/* - * Copyright 1999-2022 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.plugin.datasource.impl.mysql; - -import com.alibaba.nacos.common.utils.CollectionUtils; -import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; -import com.alibaba.nacos.plugin.datasource.constants.FieldConstant; -import com.alibaba.nacos.plugin.datasource.constants.TableConstant; -import com.alibaba.nacos.plugin.datasource.model.MapperContext; -import com.alibaba.nacos.plugin.datasource.model.MapperResult; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -class ConfigInfoAggrMapperByMySqlTest { - - private ConfigInfoAggrMapperByMySql configInfoAggrMapperByMySql; - - @BeforeEach - void setUp() throws Exception { - configInfoAggrMapperByMySql = new ConfigInfoAggrMapperByMySql(); - } - - @Test - void testBatchRemoveAggr() { - List datumList = Arrays.asList("1", "2", "3", "4", "5"); - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - - List argList = CollectionUtils.list(dataId, groupId, tenantId); - argList.addAll(datumList); - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATUM_ID, datumList); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - - MapperResult mapperResult = configInfoAggrMapperByMySql.batchRemoveAggr(context); - String sql = mapperResult.getSql(); - List paramList = mapperResult.getParamList(); - - assertEquals(sql, - "DELETE FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? " + "AND datum_id IN (?, ?, ?, ?, ?)"); - - assertEquals(paramList, argList); - } - - @Test - void testAggrConfigInfoCount() { - List datumIds = Arrays.asList("1", "2", "3", "4", "5"); - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - - List argList = CollectionUtils.list(dataId, groupId, tenantId); - argList.addAll(datumIds); - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATUM_ID, datumIds); - context.putWhereParameter(FieldConstant.IS_IN, true); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - MapperResult mapperResult = configInfoAggrMapperByMySql.aggrConfigInfoCount(context); - String sql = mapperResult.getSql(); - List paramList = mapperResult.getParamList(); - - assertEquals(sql, "SELECT count(*) FROM config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? " - + "AND datum_id IN (?, ?, ?, ?, ?)"); - assertEquals(paramList, argList); - } - - @Test - void testFindConfigInfoAggrIsOrdered() { - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - - MapperResult mapperResult = configInfoAggrMapperByMySql.findConfigInfoAggrIsOrdered(context); - String sql = mapperResult.getSql(); - List paramList = mapperResult.getParamList(); - - assertEquals(sql, "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM " - + "config_info_aggr WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY datum_id"); - assertEquals(paramList, Arrays.asList(dataId, groupId, tenantId)); - } - - @Test - void testFindConfigInfoAggrByPageFetchRows() { - String dataId = "data-id"; - String groupId = "group-id"; - String tenantId = "tenant-id"; - Integer startRow = 0; - Integer pageSize = 5; - - MapperContext context = new MapperContext(); - context.putWhereParameter(FieldConstant.DATA_ID, dataId); - context.putWhereParameter(FieldConstant.GROUP_ID, groupId); - context.putWhereParameter(FieldConstant.TENANT_ID, tenantId); - context.setStartRow(startRow); - context.setPageSize(pageSize); - - MapperResult mapperResult = configInfoAggrMapperByMySql.findConfigInfoAggrByPageFetchRows(context); - String sql = mapperResult.getSql(); - List paramList = mapperResult.getParamList(); - - assertEquals(sql, "SELECT data_id,group_id,tenant_id,datum_id,app_name,content FROM config_info_aggr WHERE " - + "data_id= ? AND group_id= ? AND tenant_id= ? ORDER BY datum_id LIMIT 0,5"); - assertEquals(paramList, Arrays.asList(dataId, groupId, tenantId)); - } - - @Test - void testFindAllAggrGroupByDistinct() { - MapperResult mapperResult = configInfoAggrMapperByMySql.findAllAggrGroupByDistinct(null); - assertEquals("SELECT DISTINCT data_id, group_id, tenant_id FROM config_info_aggr", mapperResult.getSql()); - } - - @Test - void testGetTableName() { - String tableName = configInfoAggrMapperByMySql.getTableName(); - assertEquals(TableConstant.CONFIG_INFO_AGGR, tableName); - } - - @Test - void testGetDataSource() { - String dataSource = configInfoAggrMapperByMySql.getDataSource(); - assertEquals(DataSourceConstant.MYSQL, dataSource); - } -} diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySqlTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySqlTest.java index 91d1facd6ec..d8ad5f6f3a0 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySqlTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/ConfigInfoMapperByMySqlTest.java @@ -125,7 +125,7 @@ void testGetTenantIdList() { void testGetGroupIdList() { MapperResult mapperResult = configInfoMapperByMySql.getGroupIdList(context); assertEquals(mapperResult.getSql(), - "SELECT group_id FROM config_info WHERE tenant_id ='' GROUP BY group_id LIMIT " + startRow + "," + pageSize); + "SELECT group_id FROM config_info WHERE tenant_id ='public' GROUP BY group_id LIMIT " + startRow + "," + pageSize); assertArrayEquals(mapperResult.getParamList().toArray(), emptyObjs); } @@ -221,7 +221,7 @@ void testFindAllConfigInfo4Export() { @Test void testFindConfigInfoBaseLikeCountRows() { MapperResult mapperResult = configInfoMapperByMySql.findConfigInfoBaseLikeCountRows(context); - assertEquals("SELECT count(*) FROM config_info WHERE 1=1 AND tenant_id='' ", mapperResult.getSql()); + assertEquals("SELECT count(*) FROM config_info WHERE 1=1 AND tenant_id='public' ", mapperResult.getSql()); assertArrayEquals(mapperResult.getParamList().toArray(), emptyObjs); } @@ -229,7 +229,7 @@ void testFindConfigInfoBaseLikeCountRows() { void testFindConfigInfoBaseLikeFetchRows() { MapperResult mapperResult = configInfoMapperByMySql.findConfigInfoBaseLikeFetchRows(context); assertEquals(mapperResult.getSql(), - "SELECT id,data_id,group_id,tenant_id,content FROM config_info WHERE 1=1 AND tenant_id='' LIMIT " + startRow + "," + "SELECT id,data_id,group_id,tenant_id,content FROM config_info WHERE 1=1 AND tenant_id='public' LIMIT " + startRow + "," + pageSize); assertArrayEquals(mapperResult.getParamList().toArray(), emptyObjs); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/GroupCapacityMapperByMysqlTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/GroupCapacityMapperByMysqlTest.java index fed0ce32ed8..11f812e3512 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/GroupCapacityMapperByMysqlTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/GroupCapacityMapperByMysqlTest.java @@ -116,7 +116,7 @@ void testInsertIntoSelectByWhere() { MapperResult mapperResult = groupCapacityMapperByMysql.insertIntoSelectByWhere(context); assertEquals(mapperResult.getSql(), "INSERT INTO group_capacity (group_id, quota, usage, max_size, max_aggr_count, max_aggr_size, gmt_create," - + " gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE group_id=? AND tenant_id = ''"); + + " gmt_modified) SELECT ?, ?, count(*), ?, ?, ?, ?, ? FROM config_info WHERE group_id=? AND tenant_id = 'public'"); assertArrayEquals(new Object[] {group, quota, maxSize, maxAggrCount, maxAggrSize, createTime, modified, group}, mapperResult.getParamList().toArray()); } @@ -167,7 +167,7 @@ void testUpdateUsage() { void testUpdateUsageByWhere() { MapperResult mapperResult = groupCapacityMapperByMysql.updateUsageByWhere(context); assertEquals(mapperResult.getSql(), - "UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info WHERE group_id=? AND tenant_id = '')," + "UPDATE group_capacity SET usage = (SELECT count(*) FROM config_info WHERE group_id=? AND tenant_id = 'public')," + " gmt_modified = ? WHERE group_id= ?"); assertArrayEquals(new Object[] {groupId, modified, groupId}, mapperResult.getParamList().toArray()); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java index c69efda736f..7dd64a4cde2 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/impl/mysql/HistoryConfigInfoMapperByMySqlTest.java @@ -43,6 +43,8 @@ class HistoryConfigInfoMapperByMySqlTest { Timestamp endTime = new Timestamp(System.currentTimeMillis()); + String publishType = "formal"; + MapperContext context; private HistoryConfigInfoMapperByMySql historyConfigInfoMapperByMySql; @@ -56,6 +58,7 @@ void setUp() throws Exception { context.putWhereParameter(FieldConstant.LIMIT_SIZE, limitSize); context.putWhereParameter(FieldConstant.LAST_MAX_ID, lastMaxId); context.putWhereParameter(FieldConstant.PAGE_SIZE, pageSize); + context.putWhereParameter(FieldConstant.PUBLISH_TYPE, publishType); } @Test @@ -75,10 +78,14 @@ void testFindConfigHistoryCountByTime() { @Test void testFindDeletedConfig() { MapperResult mapperResult = historyConfigInfoMapperByMySql.findDeletedConfig(context); - assertEquals(mapperResult.getSql(), "SELECT data_id, group_id, tenant_id,gmt_modified,nid FROM his_config_info " - + "WHERE op_type = 'D' AND gmt_modified >= ? and nid > ? order by nid limit ? "); + assertEquals( + "SELECT id, nid, data_id, group_id, app_name, content, md5, gmt_create, gmt_modified, src_user, src_ip," + + " op_type, tenant_id, publish_type, gray_name, ext_info, encrypted_data_key FROM his_config_info WHERE op_type = 'D' AND " + + "publish_type = ? and gmt_modified >= ? and nid > ? order by nid limit ? ", + mapperResult.getSql()); - assertArrayEquals(new Object[] {startTime, lastMaxId, pageSize}, mapperResult.getParamList().toArray()); + assertArrayEquals(new Object[] {publishType, startTime, lastMaxId, pageSize}, + mapperResult.getParamList().toArray()); } @Test @@ -93,7 +100,8 @@ void testFindConfigHistoryFetchRows() { context.putWhereParameter(FieldConstant.DATA_ID, dataId); MapperResult mapperResult = historyConfigInfoMapperByMySql.findConfigHistoryFetchRows(context); assertEquals(mapperResult.getSql(), - "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,op_type,gmt_create,gmt_modified FROM his_config_info " + "SELECT nid,data_id,group_id,tenant_id,app_name,src_ip,src_user,publish_type,gray_name," + + "op_type,gmt_create,gmt_modified FROM his_config_info " + "WHERE data_id = ? AND group_id = ? AND tenant_id = ? ORDER BY nid DESC"); assertArrayEquals(new Object[] {dataId, groupId, tenantId}, mapperResult.getParamList().toArray()); } @@ -103,8 +111,10 @@ void testDetailPreviousConfigHistory() { Object id = "1"; context.putWhereParameter(FieldConstant.ID, id); MapperResult mapperResult = historyConfigInfoMapperByMySql.detailPreviousConfigHistory(context); - assertEquals(mapperResult.getSql(), "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,gmt_create," - + "gmt_modified,encrypted_data_key FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); + assertEquals(mapperResult.getSql(), + "SELECT nid,data_id,group_id,tenant_id,app_name,content,md5,src_user,src_ip,op_type,publish_type" + + ",gray_name,ext_info,gmt_create,gmt_modified,encrypted_data_key " + + "FROM his_config_info WHERE nid = (SELECT max(nid) FROM his_config_info WHERE id = ?)"); assertArrayEquals(new Object[] {id}, mapperResult.getParamList().toArray()); } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapperTest.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapperTest.java index 899cb01adf1..d2531e40a38 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapperTest.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/AbstractMapperTest.java @@ -49,9 +49,15 @@ public String getFunction(String functionName) { } }; } - + + @Test + void testSelectSingleField() { + String sql = abstractMapper.select(Arrays.asList("id"), Arrays.asList("id")); + assertEquals("SELECT id FROM tenant_info WHERE id = ?", sql); + } + @Test - void testSelect() { + public void testSelectMultiField() { String sql = abstractMapper.select(Arrays.asList("id", "name"), Arrays.asList("id")); assertEquals("SELECT id,name FROM tenant_info WHERE id = ?", sql); } @@ -67,11 +73,17 @@ void testUpdate() { String sql = abstractMapper.update(Arrays.asList("id", "name"), Arrays.asList("id")); assertEquals("UPDATE tenant_info SET id = ?,name = ? WHERE id = ?", sql); } - + @Test - void testDelete() { + public void testDeleteSingleField() { String sql = abstractMapper.delete(Arrays.asList("id")); - assertEquals("DELETE FROM tenant_info WHERE id = ? ", sql); + assertEquals("DELETE FROM tenant_info WHERE id = ?", sql); + } + + @Test + public void testDeleteMultiField() { + String sql = abstractMapper.delete(Arrays.asList("id", "name")); + assertEquals("DELETE FROM tenant_info WHERE id = ? AND name = ?", sql); } @Test @@ -89,12 +101,12 @@ void testGetPrimaryKeyGeneratedKeys() { @Test void testSelectAll() { String sql = abstractMapper.select(Arrays.asList("id", "name"), null); - assertEquals("SELECT id,name FROM tenant_info ", sql); + assertEquals("SELECT id,name FROM tenant_info", sql); } @Test void testCountAll() { String sql = abstractMapper.count(null); - assertEquals("SELECT COUNT(*) FROM tenant_info ", sql); + assertEquals("SELECT COUNT(*) FROM tenant_info", sql); } } diff --git a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/TestMapper.java b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/TestMapper.java index 643d242e4da..2ac49c8cf71 100644 --- a/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/TestMapper.java +++ b/plugin/datasource/src/test/java/com/alibaba/nacos/plugin/datasource/mapper/TestMapper.java @@ -18,14 +18,14 @@ import com.alibaba.nacos.plugin.datasource.constants.DataSourceConstant; import com.alibaba.nacos.plugin.datasource.impl.TestInterface; -import com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoAggrMapperByMySql; +import com.alibaba.nacos.plugin.datasource.impl.mysql.ConfigInfoGrayMapperByMySql; /** * Implement interfaces other than Mapper. just for test * * @author mikolls **/ -public class TestMapper extends ConfigInfoAggrMapperByMySql implements TestInterface { +public class TestMapper extends ConfigInfoGrayMapperByMySql implements TestInterface { @Override public String getTableName() { diff --git a/pom.xml b/pom.xml index fae656cf389..668c7daeed7 100644 --- a/pom.xml +++ b/pom.xml @@ -88,7 +88,7 @@ - 2.5.0-SNAPSHOT + 3.0.0-ALPHA.2 UTF-8 UTF-8 @@ -149,11 +149,10 @@ 1.3.14 ${jraft-core.version} 2.0 + 14.0.0 5.10.2 - 6.1.10 - 6.3.1 1.12.8 @@ -627,10 +626,15 @@ prometheus persistence logger-adapter-impl + k8s-sync + bootstrap + server + lock + org.junit.jupiter junit-jupiter @@ -705,6 +709,11 @@ nacos-core ${project.version} + + ${project.groupId} + nacos-lock + ${project.version} + com.alibaba.nacos nacos-persistence @@ -760,6 +769,16 @@ nacos-console ${project.version} + + ${project.groupId} + nacos-server + ${project.version} + + + ${project.groupId} + nacos-bootstrap + ${project.version} + ${project.groupId} nacos-distribution @@ -781,6 +800,11 @@ nacos-istio ${project.version} + + ${project.groupId} + nacos-k8s-sync + ${project.version} + ${project.groupId} nacos-prometheus @@ -1079,6 +1103,18 @@ snakeyaml ${SnakeYaml.version} + + + io.kubernetes + client-java-api + ${kubernetes.client.version} + + + + io.kubernetes + client-java + ${kubernetes.client.version} + diff --git a/prometheus/src/main/java/com/alibaba/nacos/prometheus/conf/PrometheusSecurityConfiguration.java b/prometheus/src/main/java/com/alibaba/nacos/prometheus/conf/PrometheusSecurityConfiguration.java index 3feec32d75c..8f910d1ddd6 100644 --- a/prometheus/src/main/java/com/alibaba/nacos/prometheus/conf/PrometheusSecurityConfiguration.java +++ b/prometheus/src/main/java/com/alibaba/nacos/prometheus/conf/PrometheusSecurityConfiguration.java @@ -16,30 +16,46 @@ package com.alibaba.nacos.prometheus.conf; +import com.alibaba.nacos.common.utils.StringUtils; +import com.alibaba.nacos.core.web.NacosWebBean; +import com.alibaba.nacos.plugin.auth.constant.Constants; import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Condition; +import org.springframework.context.annotation.ConditionContext; +import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; -import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer; +import org.springframework.core.type.AnnotatedTypeMetadata; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.web.SecurityFilterChain; import static com.alibaba.nacos.prometheus.api.ApiConstants.PROMETHEUS_CONTROLLER_NAMESPACE_PATH; import static com.alibaba.nacos.prometheus.api.ApiConstants.PROMETHEUS_CONTROLLER_PATH; import static com.alibaba.nacos.prometheus.api.ApiConstants.PROMETHEUS_CONTROLLER_SERVICE_PATH; - /** * prometheus auth configuration, avoid spring security configuration override. * * @author vividfish */ @Configuration +@NacosWebBean public class PrometheusSecurityConfiguration { @Bean - public WebSecurityCustomizer prometheusWebSecurityCustomizer() { - return web -> { - web.ignoring().requestMatchers(PROMETHEUS_CONTROLLER_PATH); - web.ignoring().requestMatchers(PROMETHEUS_CONTROLLER_NAMESPACE_PATH); - web.ignoring().requestMatchers(PROMETHEUS_CONTROLLER_SERVICE_PATH); - }; + @Conditional(ConditionOnNoAuthPluginType.class) + public SecurityFilterChain prometheusSecurityFilterChain(HttpSecurity http) throws Exception { + return http.authorizeHttpRequests() + .requestMatchers(PROMETHEUS_CONTROLLER_PATH, PROMETHEUS_CONTROLLER_NAMESPACE_PATH, + PROMETHEUS_CONTROLLER_SERVICE_PATH).permitAll().and().getOrBuild(); } + private static class ConditionOnNoAuthPluginType implements Condition { + + @Override + public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) { + String nacosAuthSystemType = context.getEnvironment() + .getProperty(Constants.Auth.NACOS_CORE_AUTH_SYSTEM_TYPE, ""); + return StringUtils.isBlank(nacosAuthSystemType); + } + } } diff --git a/server/pom.xml b/server/pom.xml new file mode 100644 index 00000000000..ff622638b33 --- /dev/null +++ b/server/pom.xml @@ -0,0 +1,56 @@ + + + + + 4.0.0 + + com.alibaba.nacos + nacos-all + ${revision} + ../pom.xml + + + nacos-server + nacos-server ${project.version} + + + + com.alibaba.nacos + nacos-naming + + + com.alibaba.nacos + nacos-config + + + com.alibaba.nacos + nacos-istio + + + com.alibaba.nacos + nacos-prometheus + + + com.alibaba.nacos + nacos-default-plugin-all + ${project.version} + + + + \ No newline at end of file diff --git a/server/src/main/java/com/alibaba/nacos/NacosServerBasicApplication.java b/server/src/main/java/com/alibaba/nacos/NacosServerBasicApplication.java new file mode 100644 index 00000000000..c602b6dab6e --- /dev/null +++ b/server/src/main/java/com/alibaba/nacos/NacosServerBasicApplication.java @@ -0,0 +1,44 @@ +/* + * 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; + +import com.alibaba.nacos.server.NacosWebBeanTypeFilter; +import com.alibaba.nacos.sys.filter.NacosTypeExcludeFilter; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.FilterType; +import org.springframework.scheduling.annotation.EnableScheduling; + +/** + * Nacos Server basic starter class, which load common non-web container beans. + * + * @author xiweng.yy + */ +@SpringBootApplication(exclude = {LdapAutoConfiguration.class}) +@ComponentScan(basePackages = "com.alibaba.nacos", excludeFilters = { + @Filter(type = FilterType.REGEX, pattern = "com\\.alibaba\\.nacos\\.console.*"), + @Filter(type = FilterType.CUSTOM, classes = {NacosTypeExcludeFilter.class, NacosWebBeanTypeFilter.class})}) +@EnableScheduling +public class NacosServerBasicApplication { + + public static void main(String[] args) { + SpringApplication.run(NacosServerBasicApplication.class, args); + } +} diff --git a/server/src/main/java/com/alibaba/nacos/NacosServerWebApplication.java b/server/src/main/java/com/alibaba/nacos/NacosServerWebApplication.java new file mode 100644 index 00000000000..96e94a5e4ec --- /dev/null +++ b/server/src/main/java/com/alibaba/nacos/NacosServerWebApplication.java @@ -0,0 +1,43 @@ +/* + * 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; + +import com.alibaba.nacos.server.NacosNormalBeanTypeFilter; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.ldap.LdapAutoConfiguration; +import org.springframework.context.annotation.ComponentScan; +import org.springframework.context.annotation.ComponentScan.Filter; +import org.springframework.context.annotation.FilterType; +import org.springframework.context.annotation.PropertySource; + +/** + * Nacos Server web starter class, which load non-console web container beans. + * + * @author xiweng.yy + */ +@SpringBootApplication(exclude = LdapAutoConfiguration.class) +@ComponentScan(basePackages = "com.alibaba.nacos", excludeFilters = { + @Filter(type = FilterType.REGEX, pattern = "com\\.alibaba\\.nacos\\.console.*"), + @Filter(type = FilterType.CUSTOM, classes = {NacosNormalBeanTypeFilter.class})}) +@PropertySource("classpath:nacos-server.properties") +public class NacosServerWebApplication { + + public static void main(String[] args) { + SpringApplication.run(NacosServerWebApplication.class, args); + } +} diff --git a/server/src/main/java/com/alibaba/nacos/server/AbstractNacosWebBeanTypeFilter.java b/server/src/main/java/com/alibaba/nacos/server/AbstractNacosWebBeanTypeFilter.java new file mode 100644 index 00000000000..86346859979 --- /dev/null +++ b/server/src/main/java/com/alibaba/nacos/server/AbstractNacosWebBeanTypeFilter.java @@ -0,0 +1,56 @@ +/* + * 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.server; + +import com.alibaba.nacos.core.web.NacosWebBean; +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; +import org.springframework.core.type.filter.TypeFilter; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.RestController; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +/** + * Abstract TypeFilter to filter Nacos Web Bean or not. + * + * @author xiweng.yy + */ +public abstract class AbstractNacosWebBeanTypeFilter implements TypeFilter { + + private static final Set WEB_BEAN_ANNOTATIONS = new HashSet<>(); + + static { + WEB_BEAN_ANNOTATIONS.add(RestController.class.getCanonicalName()); + WEB_BEAN_ANNOTATIONS.add(ControllerAdvice.class.getCanonicalName()); + WEB_BEAN_ANNOTATIONS.add(Controller.class.getCanonicalName()); + WEB_BEAN_ANNOTATIONS.add(NacosWebBean.class.getCanonicalName()); + } + + protected boolean isWebBean(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException { + for (String each : WEB_BEAN_ANNOTATIONS) { + if (metadataReader.getAnnotationMetadata().hasAnnotation(each)) { + return true; + } + } + return false; + } +} diff --git a/server/src/main/java/com/alibaba/nacos/server/NacosNormalBeanTypeFilter.java b/server/src/main/java/com/alibaba/nacos/server/NacosNormalBeanTypeFilter.java new file mode 100644 index 00000000000..6693b03c536 --- /dev/null +++ b/server/src/main/java/com/alibaba/nacos/server/NacosNormalBeanTypeFilter.java @@ -0,0 +1,36 @@ +/* + * 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.server; + +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; + +import java.io.IOException; + +/** + * TypeFilter to filter beans which is Nacos Web. + * + * @author xiweng.yy + */ +public class NacosNormalBeanTypeFilter extends AbstractNacosWebBeanTypeFilter { + + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException { + return !super.isWebBean(metadataReader, metadataReaderFactory); + } +} diff --git a/server/src/main/java/com/alibaba/nacos/server/NacosWebBeanPostProcessorConfiguration.java b/server/src/main/java/com/alibaba/nacos/server/NacosWebBeanPostProcessorConfiguration.java new file mode 100644 index 00000000000..154fbe08fd3 --- /dev/null +++ b/server/src/main/java/com/alibaba/nacos/server/NacosWebBeanPostProcessorConfiguration.java @@ -0,0 +1,50 @@ +/* + * 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.server; + +import com.alibaba.nacos.core.web.NacosWebBean; +import com.alibaba.nacos.sys.env.Constants; +import com.alibaba.nacos.sys.env.NacosDuplicateConfigurationBeanPostProcessor; +import com.alibaba.nacos.sys.env.NacosDuplicateSpringBeanPostProcessor; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Bean Post Processor Configuration for nacos web server. + * + * @author xiweng.yy + */ +@Configuration +@NacosWebBean +@ConditionalOnProperty(value = Constants.NACOS_DUPLICATE_BEAN_ENHANCEMENT_ENABLED, havingValue = "true", matchIfMissing = true) +public class NacosWebBeanPostProcessorConfiguration { + + @Bean + public InstantiationAwareBeanPostProcessor nacosDuplicateSpringBeanPostProcessor( + ConfigurableApplicationContext context) { + return new NacosDuplicateSpringBeanPostProcessor(context); + } + + @Bean + public InstantiationAwareBeanPostProcessor nacosDuplicateConfigurationBeanPostProcessor( + ConfigurableApplicationContext context) { + return new NacosDuplicateConfigurationBeanPostProcessor(context); + } +} diff --git a/server/src/main/java/com/alibaba/nacos/server/NacosWebBeanTypeFilter.java b/server/src/main/java/com/alibaba/nacos/server/NacosWebBeanTypeFilter.java new file mode 100644 index 00000000000..607e4cf3d1b --- /dev/null +++ b/server/src/main/java/com/alibaba/nacos/server/NacosWebBeanTypeFilter.java @@ -0,0 +1,36 @@ +/* + * 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.server; + +import org.springframework.core.type.classreading.MetadataReader; +import org.springframework.core.type.classreading.MetadataReaderFactory; + +import java.io.IOException; + +/** + * TypeFilter to filter beans which is Nacos Web. + * + * @author xiweng.yy + */ +public class NacosWebBeanTypeFilter extends AbstractNacosWebBeanTypeFilter { + + @Override + public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) + throws IOException { + return super.isWebBean(metadataReader, metadataReaderFactory); + } +} diff --git a/server/src/main/resources/nacos-server-web-banner.txt b/server/src/main/resources/nacos-server-web-banner.txt new file mode 100644 index 00000000000..4a78b9dd995 --- /dev/null +++ b/server/src/main/resources/nacos-server-web-banner.txt @@ -0,0 +1,15 @@ + + ,--. + ,--.'| + ,--,: : | Nacos Server API ${application.version} +,`--.'`| ' : ,---. Running in ${nacos.mode} mode, ${nacos.function.mode} function modules +| : : | | ' ,'\ .--.--. Port: ${nacos.server.main.port} +: | \ | : ,--.--. ,---. / / | / / ' Pid: ${pid} +| : ' '; | / \ / \. ; ,. :| : /`./ +' ' ;. ;.--. .-. | / / '' | |: :| : ;_ +| | | \ | \__\/: . .. ' / ' | .; : \ \ `. https://nacos.io +' : | ; .' ," .--.; |' ; :__| : | `----. \ +| | '`--' / / ,. |' | '.'|\ \ / / /`--' / +' : | ; : .' \ : : `----' '--'. / +; |.' | , .-./\ \ / `--'---' +'---' `--`---' `----' diff --git a/server/src/main/resources/nacos-server.properties b/server/src/main/resources/nacos-server.properties new file mode 100644 index 00000000000..f9cc852885c --- /dev/null +++ b/server/src/main/resources/nacos-server.properties @@ -0,0 +1,20 @@ +# +# 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. +# + +### nacos console port: +server.port=${nacos.server.main.port:8080} +server.servlet.contextPath=${nacos.server.contextPath:/nacos} +spring.sql.init.mode=never \ No newline at end of file diff --git a/sys/src/main/java/com/alibaba/nacos/sys/env/AbstractNacosDuplicateBeanPostProcessor.java b/sys/src/main/java/com/alibaba/nacos/sys/env/AbstractNacosDuplicateBeanPostProcessor.java new file mode 100644 index 00000000000..26d1b8bddae --- /dev/null +++ b/sys/src/main/java/com/alibaba/nacos/sys/env/AbstractNacosDuplicateBeanPostProcessor.java @@ -0,0 +1,55 @@ +/* + * 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.sys.env; + +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Abstract Nacos Duplicate Bean Post Processor of {@link InstantiationAwareBeanPostProcessor} to reduce duplicate rebuild bean for spring beans. + * + * @author xiweng.yy + */ +public abstract class AbstractNacosDuplicateBeanPostProcessor implements InstantiationAwareBeanPostProcessor { + + private final ConfigurableApplicationContext coreContext; + + protected AbstractNacosDuplicateBeanPostProcessor(ConfigurableApplicationContext context) { + coreContext = null == context.getParent() ? context : (ConfigurableApplicationContext) context.getParent(); + } + + @Override + public Object postProcessBeforeInstantiation(Class beanClass, String beanName) throws BeansException { + if (!coreContext.containsBean(beanName)) { + return null; + } + BeanDefinition beanDefinition = coreContext.getBeanFactory().getBeanDefinition(beanName); + return isReUsingBean(beanClass, beanName, beanDefinition) ? coreContext.getBean(beanName) : null; + } + + /** + * Judge whether re-use beans from core context. + * + * @param beanClass bean class + * @param beanName bean name + * @param beanDefinition bean definition + * @return {@code true} means re-use beans from core context, otherwise {@code false} means to re-build bean in sub context. + */ + protected abstract boolean isReUsingBean(Class beanClass, String beanName, BeanDefinition beanDefinition); +} diff --git a/sys/src/main/java/com/alibaba/nacos/sys/env/Constants.java b/sys/src/main/java/com/alibaba/nacos/sys/env/Constants.java index 2d4493fe071..96494a28d91 100644 --- a/sys/src/main/java/com/alibaba/nacos/sys/env/Constants.java +++ b/sys/src/main/java/com/alibaba/nacos/sys/env/Constants.java @@ -79,5 +79,16 @@ public interface Constants { String AVAILABLE_PROCESSORS_BASIC = "nacos.core.sys.basic.processors"; + @Deprecated String SUPPORT_UPGRADE_FROM_1X = "nacos.core.support.upgrade.from.1x"; + + String NACOS_DEPLOYMENT_TYPE = "nacos.deployment.type"; + + String NACOS_DEPLOYMENT_TYPE_MERGED = "merged"; + + String NACOS_DEPLOYMENT_TYPE_SERVER = "server"; + + String NACOS_DEPLOYMENT_TYPE_CONSOLE = "console"; + + String NACOS_DUPLICATE_BEAN_ENHANCEMENT_ENABLED = "nacos.sys.duplicate.bean.enhancement.enabled"; } diff --git a/sys/src/main/java/com/alibaba/nacos/sys/env/NacosDuplicateConfigurationBeanPostProcessor.java b/sys/src/main/java/com/alibaba/nacos/sys/env/NacosDuplicateConfigurationBeanPostProcessor.java new file mode 100644 index 00000000000..2fc46378d0e --- /dev/null +++ b/sys/src/main/java/com/alibaba/nacos/sys/env/NacosDuplicateConfigurationBeanPostProcessor.java @@ -0,0 +1,45 @@ +/* + * 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.sys.env; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Configuration; + +/** + * Nacos {@link InstantiationAwareBeanPostProcessor} to reduce duplicate rebuild bean for spring configuration. + * + * @author xiweng.yy + */ +public class NacosDuplicateConfigurationBeanPostProcessor extends AbstractNacosDuplicateBeanPostProcessor { + + public NacosDuplicateConfigurationBeanPostProcessor(ConfigurableApplicationContext context) { + super(context); + } + + @Override + protected boolean isReUsingBean(Class beanClass, String beanName, BeanDefinition beanDefinition) { + return isConfiguration(beanClass); + } + + private boolean isConfiguration(Class beanClass) { + return null != beanClass.getAnnotation(Configuration.class) || null != beanClass.getAnnotation( + AutoConfiguration.class); + } +} diff --git a/sys/src/main/java/com/alibaba/nacos/sys/env/NacosDuplicateSpringBeanPostProcessor.java b/sys/src/main/java/com/alibaba/nacos/sys/env/NacosDuplicateSpringBeanPostProcessor.java new file mode 100644 index 00000000000..f608e593505 --- /dev/null +++ b/sys/src/main/java/com/alibaba/nacos/sys/env/NacosDuplicateSpringBeanPostProcessor.java @@ -0,0 +1,51 @@ +/* + * 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.sys.env; + +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor; +import org.springframework.context.ConfigurableApplicationContext; + +/** + * Nacos {@link InstantiationAwareBeanPostProcessor} to reduce duplicate rebuild bean for spring beans. + * + *

+ * For some important spring beans like spring context beans, if reuse from parent, will cause some problem. So skip. + *

+ * + * @author xiweng.yy + */ +public class NacosDuplicateSpringBeanPostProcessor extends AbstractNacosDuplicateBeanPostProcessor { + + public NacosDuplicateSpringBeanPostProcessor(ConfigurableApplicationContext context) { + super(context); + } + + @Override + protected boolean isReUsingBean(Class beanClass, String beanName, BeanDefinition beanDefinition) { + return !isContextBean(beanClass); + } + + private boolean isContextBean(Class beanClass) { + return isContextClass(beanClass.getCanonicalName()); + } + + private boolean isContextClass(String beanClassName) { + return beanClassName.startsWith("org.springframework.context") + || beanClassName.startsWith("org.springframework.boot.context"); + } +} diff --git a/sys/src/main/java/com/alibaba/nacos/sys/utils/ApplicationUtils.java b/sys/src/main/java/com/alibaba/nacos/sys/utils/ApplicationUtils.java index 11cf934012a..e7820378f1a 100644 --- a/sys/src/main/java/com/alibaba/nacos/sys/utils/ApplicationUtils.java +++ b/sys/src/main/java/com/alibaba/nacos/sys/utils/ApplicationUtils.java @@ -17,22 +17,13 @@ package com.alibaba.nacos.sys.utils; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.ObjectProvider; -import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.MessageSourceResolvable; -import org.springframework.context.NoSuchMessageException; -import org.springframework.core.ResolvableType; import org.springframework.core.io.Resource; import java.io.IOException; -import java.lang.annotation.Annotation; -import java.util.Locale; -import java.util.Map; import java.util.function.Consumer; /** @@ -47,30 +38,6 @@ public class ApplicationUtils implements ApplicationContextInitializer type) { - return applicationContext.getBeanNamesForType(type); - } - - public static String[] getBeanNamesForType(Class type, boolean includeNonSingletons, boolean allowEagerInit) { - return applicationContext.getBeanNamesForType(type, includeNonSingletons, allowEagerInit); - } - - public static Map getBeansOfType(Class type) throws BeansException { - return applicationContext.getBeansOfType(type); - } - - public static Map getBeansOfType(Class type, boolean includeNonSingletons, boolean allowEagerInit) - throws BeansException { - return applicationContext.getBeansOfType(type, includeNonSingletons, allowEagerInit); - } - - public static String[] getBeanNamesForAnnotation(Class annotationType) { - return applicationContext.getBeanNamesForAnnotation(annotationType); - } - - public static Map getBeansWithAnnotation(Class annotationType) - throws BeansException { - return applicationContext.getBeansWithAnnotation(annotationType); - } - - public static A findAnnotationOnBean(String beanName, Class annotationType) - throws NoSuchBeanDefinitionException { - return applicationContext.findAnnotationOnBean(beanName, annotationType); - } - public static Object getBean(String name) throws BeansException { return applicationContext.getBean(name); } @@ -162,58 +74,18 @@ public static T getBean(Class requiredType, Object... args) throws BeansE return applicationContext.getBean(requiredType, args); } - public static ObjectProvider getBeanProvider(Class requiredType) { - return applicationContext.getBeanProvider(requiredType); - } - - public static ObjectProvider getBeanProvider(ResolvableType requiredType) { - return applicationContext.getBeanProvider(requiredType); - } - public static boolean containsBean(String name) { return applicationContext.containsBean(name); } - public static boolean isSingleton(String name) throws NoSuchBeanDefinitionException { - return applicationContext.isSingleton(name); - } - - public static boolean isPrototype(String name) throws NoSuchBeanDefinitionException { - return applicationContext.isPrototype(name); - } - - public static boolean isTypeMatch(String name, ResolvableType typeToMatch) throws NoSuchBeanDefinitionException { - return applicationContext.isTypeMatch(name, typeToMatch); - } - - public static boolean isTypeMatch(String name, Class typeToMatch) throws NoSuchBeanDefinitionException { - return applicationContext.isTypeMatch(name, typeToMatch); - } - public static Class getType(String name) throws NoSuchBeanDefinitionException { return applicationContext.getType(name); } - public static String[] getAliases(String name) { - return applicationContext.getAliases(name); - } - public static void publishEvent(Object event) { applicationContext.publishEvent(event); } - public static String getMessage(String code, Object[] args, String defaultMessage, Locale locale) { - return applicationContext.getMessage(code, args, defaultMessage, locale); - } - - public static String getMessage(String code, Object[] args, Locale locale) throws NoSuchMessageException { - return applicationContext.getMessage(code, args, locale); - } - - public static String getMessage(MessageSourceResolvable resolvable, Locale locale) throws NoSuchMessageException { - return applicationContext.getMessage(resolvable, locale); - } - public static Resource[] getResources(String locationPattern) throws IOException { return applicationContext.getResources(locationPattern); } @@ -236,6 +108,12 @@ public static void injectContext(ConfigurableApplicationContext context) { @Override public void initialize(ConfigurableApplicationContext context) { - applicationContext = context; + if (null == applicationContext) { + // First time be called, set the context directly. + applicationContext = context; + } else if (context.getParent() == applicationContext) { + // Not first time be called, which means sub context initialize, only store the first sub context. + applicationContext = context; + } } } diff --git a/sys/src/test/java/com/alibaba/nacos/sys/env/EnvUtilTest.java b/sys/src/test/java/com/alibaba/nacos/sys/env/EnvUtilTest.java index 97a1ed8a9ba..a618b86828b 100644 --- a/sys/src/test/java/com/alibaba/nacos/sys/env/EnvUtilTest.java +++ b/sys/src/test/java/com/alibaba/nacos/sys/env/EnvUtilTest.java @@ -20,6 +20,7 @@ import com.alibaba.nacos.plugin.environment.CustomEnvironmentPluginManager; import com.alibaba.nacos.plugin.environment.spi.CustomEnvironmentPluginService; import com.alibaba.nacos.sys.utils.DiskUtils; +import com.alibaba.nacos.sys.utils.InetUtils; import org.apache.commons.io.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -197,7 +198,8 @@ void testGetLocalAddress() { System.setProperty(NACOS_SERVER_IP, "1.1.1.1"); System.setProperty(Constants.AUTO_REFRESH_TIME, "100"); try { - assertEquals("1.1.1.1:8848", EnvUtil.getLocalAddress()); + EnvUtil.setLocalAddress(null); + assertEquals(InetUtils.getSelfIP() + ":8848", EnvUtil.getLocalAddress()); EnvUtil.setLocalAddress("testLocalAddress:8848"); assertEquals("testLocalAddress:8848", EnvUtil.getLocalAddress()); } finally { diff --git a/sys/src/test/java/com/alibaba/nacos/sys/env/NacosDuplicateConfigurationBeanPostProcessorTest.java b/sys/src/test/java/com/alibaba/nacos/sys/env/NacosDuplicateConfigurationBeanPostProcessorTest.java new file mode 100644 index 00000000000..737b994f9d9 --- /dev/null +++ b/sys/src/test/java/com/alibaba/nacos/sys/env/NacosDuplicateConfigurationBeanPostProcessorTest.java @@ -0,0 +1,96 @@ +/* + * 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.sys.env; + +import com.alibaba.nacos.sys.env.mock.MockAutoConfiguration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.LifecycleProcessor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NacosDuplicateConfigurationBeanPostProcessorTest { + + @Mock + NacosDuplicateConfigurationBeanPostProcessor processor; + + @Mock + ConfigurableApplicationContext context; + + @Mock + ConfigurableListableBeanFactory beanFactory; + + @Mock + BeanDefinition beanDefinition; + + @BeforeEach + void setUp() { + processor = new NacosDuplicateConfigurationBeanPostProcessor(context); + } + + @Test + void testPostProcessBeforeInstantiationNonExist() { + Class beanClass = LifecycleProcessor.class; + assertNull(processor.postProcessBeforeInstantiation(beanClass, "lifecycleProcessor")); + verify(context, never()).getBean("lifecycleProcessor"); + } + + @Test + void testPostProcessBeforeInstantiationForConfigurationAnnotation() { + String beanName = "com.alibaba.nacos.sys.env.mock.MockAutoConfiguration$MockConfiguration"; + when(context.containsBean(beanName)).thenReturn(true); + when(context.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeanDefinition(beanName)).thenReturn(beanDefinition); + Class beanClass = MockAutoConfiguration.MockConfiguration.class; + MockAutoConfiguration.MockConfiguration existBean = new MockAutoConfiguration.MockConfiguration(); + when(context.getBean(beanName)).thenReturn(existBean); + assertEquals(existBean, processor.postProcessBeforeInstantiation(beanClass, beanName)); + } + + @Test + void testPostProcessBeforeInstantiationForAutoConfigurationAnnotation() { + String beanName = "com.alibaba.nacos.sys.env.mock.MockAutoConfiguration"; + when(context.containsBean(beanName)).thenReturn(true); + when(context.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeanDefinition(beanName)).thenReturn(beanDefinition); + Class beanClass = MockAutoConfiguration.class; + MockAutoConfiguration existBean = new MockAutoConfiguration(); + when(context.getBean(beanName)).thenReturn(existBean); + assertEquals(existBean, processor.postProcessBeforeInstantiation(beanClass, beanName)); + } + + @Test + void testPostProcessBeforeInstantiationForNormalBean() { + when(context.containsBean("testBean")).thenReturn(true); + when(context.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeanDefinition("testBean")).thenReturn(beanDefinition); + Class beanClass = NacosDuplicateConfigurationBeanPostProcessor.class; + assertNull(processor.postProcessBeforeInstantiation(beanClass, "testBean")); + verify(context, never()).getBean("testBean"); + } +} \ No newline at end of file diff --git a/sys/src/test/java/com/alibaba/nacos/sys/env/NacosDuplicateSpringBeanPostProcessorTest.java b/sys/src/test/java/com/alibaba/nacos/sys/env/NacosDuplicateSpringBeanPostProcessorTest.java new file mode 100644 index 00000000000..63e16ed3e44 --- /dev/null +++ b/sys/src/test/java/com/alibaba/nacos/sys/env/NacosDuplicateSpringBeanPostProcessorTest.java @@ -0,0 +1,92 @@ +/* + * 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.sys.env; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.beans.factory.config.BeanDefinition; +import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; +import org.springframework.boot.context.properties.BoundConfigurationProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.LifecycleProcessor; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class NacosDuplicateSpringBeanPostProcessorTest { + + @Mock + NacosDuplicateSpringBeanPostProcessor processor; + + @Mock + ConfigurableApplicationContext context; + + @Mock + ConfigurableListableBeanFactory beanFactory; + + @Mock + BeanDefinition beanDefinition; + + @BeforeEach + void setUp() { + processor = new NacosDuplicateSpringBeanPostProcessor(context); + } + + @Test + void testPostProcessBeforeInstantiationNonExist() { + Class beanClass = LifecycleProcessor.class; + assertNull(processor.postProcessBeforeInstantiation(beanClass, "lifecycleProcessor")); + verify(context, never()).getBean("lifecycleProcessor"); + } + + @Test + void testPostProcessBeforeInstantiationForContextBean() { + when(context.containsBean("lifecycleProcessor")).thenReturn(true); + when(context.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeanDefinition("lifecycleProcessor")).thenReturn(beanDefinition); + Class beanClass = LifecycleProcessor.class; + assertNull(processor.postProcessBeforeInstantiation(beanClass, "lifecycleProcessor")); + verify(context, never()).getBean("lifecycleProcessor"); + } + + @Test + void testPostProcessBeforeInstantiationForBootContextBean() { + when(context.containsBean("boundConfigurationProperties")).thenReturn(true); + when(context.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeanDefinition("boundConfigurationProperties")).thenReturn(beanDefinition); + Class beanClass = BoundConfigurationProperties.class; + assertNull(processor.postProcessBeforeInstantiation(beanClass, "boundConfigurationProperties")); + verify(context, never()).getBean("boundConfigurationProperties"); + } + + @Test + void testPostProcessBeforeInstantiationForNotContextBean() { + when(context.containsBean("testBean")).thenReturn(true); + when(context.getBeanFactory()).thenReturn(beanFactory); + when(beanFactory.getBeanDefinition("testBean")).thenReturn(beanDefinition); + Class beanClass = NacosDuplicateSpringBeanPostProcessorTest.class; + when(context.getBean("testBean")).thenReturn(this); + assertEquals(this, processor.postProcessBeforeInstantiation(beanClass, "testBean")); + } +} \ No newline at end of file diff --git a/sys/src/test/java/com/alibaba/nacos/sys/env/mock/MockAutoConfiguration.java b/sys/src/test/java/com/alibaba/nacos/sys/env/mock/MockAutoConfiguration.java new file mode 100644 index 00000000000..9a6498f6678 --- /dev/null +++ b/sys/src/test/java/com/alibaba/nacos/sys/env/mock/MockAutoConfiguration.java @@ -0,0 +1,29 @@ +/* + * 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.sys.env.mock; + +import org.springframework.boot.autoconfigure.AutoConfiguration; +import org.springframework.context.annotation.Configuration; + +@AutoConfiguration +public class MockAutoConfiguration { + + @Configuration + public static class MockConfiguration { + + } +} diff --git a/sys/src/test/java/com/alibaba/nacos/sys/utils/ApplicationUtilsTest.java b/sys/src/test/java/com/alibaba/nacos/sys/utils/ApplicationUtilsTest.java index 1f75f21fb7f..24621cba048 100644 --- a/sys/src/test/java/com/alibaba/nacos/sys/utils/ApplicationUtilsTest.java +++ b/sys/src/test/java/com/alibaba/nacos/sys/utils/ApplicationUtilsTest.java @@ -23,19 +23,11 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import org.springframework.beans.factory.NoSuchBeanDefinitionException; -import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.config.AutowireCapableBeanFactory; import org.springframework.context.ConfigurableApplicationContext; -import org.springframework.context.MessageSourceResolvable; -import org.springframework.core.ResolvableType; import org.springframework.core.io.Resource; -import org.springframework.stereotype.Service; import java.io.IOException; -import java.lang.reflect.Method; -import java.util.HashMap; -import java.util.Locale; -import java.util.Map; import java.util.function.Consumer; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -65,43 +57,6 @@ void tearDown() { ApplicationUtils.injectContext(null); } - @Test - void testGetId() { - when(context.getId()).thenReturn("testId"); - assertEquals("testId", ApplicationUtils.getId()); - } - - @Test - void testGetApplicationName() { - when(context.getApplicationName()).thenReturn("testName"); - assertEquals("testName", ApplicationUtils.getApplicationName()); - } - - @Test - void testGetDisplayName() { - when(context.getDisplayName()).thenReturn("testDisplayName"); - assertEquals("testDisplayName", ApplicationUtils.getDisplayName()); - } - - @Test - void testGetStartupDate() { - long currentTime = System.currentTimeMillis(); - when(context.getStartupDate()).thenReturn(currentTime); - assertEquals(currentTime, ApplicationUtils.getStartupDate()); - } - - @Test - void testGetParent() { - when(context.getParent()).thenReturn(context); - assertEquals(context, ApplicationUtils.getParent()); - } - - @Test - void testGetAutowireCapableBeanFactory() { - when(context.getAutowireCapableBeanFactory()).thenReturn(factory); - assertEquals(factory, ApplicationUtils.getAutowireCapableBeanFactory()); - } - @Test void testIsStarted() { assertFalse(ApplicationUtils.isStarted()); @@ -109,99 +64,6 @@ void testIsStarted() { assertTrue(ApplicationUtils.isStarted()); } - @Test - void testGetParentBeanFactory() { - when(context.getParentBeanFactory()).thenReturn(factory); - assertEquals(factory, ApplicationUtils.getParentBeanFactory()); - } - - @Test - void testContainsLocalBean() { - when(context.containsLocalBean("testBeanName")).thenReturn(true); - assertTrue(ApplicationUtils.containsLocalBean("testBeanName")); - } - - @Test - void testContainsBeanDefinition() { - when(context.containsBeanDefinition("testBeanName")).thenReturn(true); - assertTrue(ApplicationUtils.containsBeanDefinition("testBeanName")); - } - - @Test - void testGetBeanDefinitionCount() { - when(context.getBeanDefinitionCount()).thenReturn(10); - assertEquals(10, ApplicationUtils.getBeanDefinitionCount()); - } - - @Test - void testGetBeanDefinitionNames() { - String[] names = new String[10]; - when(context.getBeanDefinitionNames()).thenReturn(names); - assertEquals(names, ApplicationUtils.getBeanDefinitionNames()); - } - - @Test - void testGetBeanNamesForType() { - ResolvableType type = ResolvableType.forClass(ConfigurableApplicationContext.class); - String[] names = new String[10]; - when(context.getBeanNamesForType(type)).thenReturn(names); - assertEquals(names, ApplicationUtils.getBeanNamesForType(type)); - } - - @Test - void testGetBeanNamesForTypeWithClass() { - String[] names = new String[10]; - when(context.getBeanNamesForType(ConfigurableApplicationContext.class)).thenReturn(names); - assertEquals(names, ApplicationUtils.getBeanNamesForType(ConfigurableApplicationContext.class)); - } - - @Test - void testGetBeanNamesForTypeMultipleParams() { - String[] names = new String[10]; - when(context.getBeanNamesForType(ConfigurableApplicationContext.class, true, true)).thenReturn(names); - assertEquals(names, ApplicationUtils.getBeanNamesForType(ConfigurableApplicationContext.class, true, true)); - } - - @Test - void testGetBeansOfType() { - Map maps = new HashMap<>(); - maps.put("testBeanName", context); - when(context.getBeansOfType(ConfigurableApplicationContext.class)).thenReturn(maps); - assertEquals(maps, ApplicationUtils.getBeansOfType(ConfigurableApplicationContext.class)); - } - - @Test - void testGetBeansOfTypeMultipleParams() { - Map maps = new HashMap<>(); - maps.put("testBeanName", context); - when(context.getBeansOfType(ConfigurableApplicationContext.class, true, true)).thenReturn(maps); - assertEquals(maps, ApplicationUtils.getBeansOfType(ConfigurableApplicationContext.class, true, true)); - - } - - @Test - void testGetBeanNamesForAnnotation() { - String[] names = new String[10]; - when(context.getBeanNamesForAnnotation(Service.class)).thenReturn(names); - assertEquals(names, ApplicationUtils.getBeanNamesForAnnotation(Service.class)); - } - - @Test - void testGetBeansWithAnnotation() { - Map maps = new HashMap<>(); - maps.put("testBeanName", context); - when(context.getBeansWithAnnotation(Service.class)).thenReturn(maps); - assertEquals(maps, ApplicationUtils.getBeansWithAnnotation(Service.class)); - } - - @Test - void testFindAnnotationOnBean() throws NoSuchMethodException { - Method method = ApplicationUtilsTest.class.getDeclaredMethod("testFindAnnotationOnBean"); - Test annotation = method.getAnnotation(Test.class); - when(context.findAnnotationOnBean("testBeanName", Test.class)).thenReturn(annotation); - assertEquals(annotation, ApplicationUtils.findAnnotationOnBean("testBeanName", Test.class)); - } - @Test void testGetBeanByName() { when(context.getBean("testBeanName")).thenReturn(context); @@ -251,65 +113,18 @@ void testGetBeanIfExistNonExist() { verify(consumer, never()).accept(context); } - @Test - void testGetBeanProviderByClass() { - ObjectProvider provider = mock(ObjectProvider.class); - when(context.getBeanProvider(ConfigurableApplicationContext.class)).thenReturn(provider); - assertEquals(provider, ApplicationUtils.getBeanProvider(ConfigurableApplicationContext.class)); - } - - @Test - void testGetBeanProviderByType() { - ResolvableType type = ResolvableType.forClass(ConfigurableApplicationContext.class); - ObjectProvider provider = mock(ObjectProvider.class); - when(context.getBeanProvider(type)).thenReturn(provider); - assertEquals(provider, ApplicationUtils.getBeanProvider(type)); - } - @Test void testContainsBean() { when(context.containsBean("testBeanName")).thenReturn(true); assertTrue(ApplicationUtils.containsBean("testBeanName")); } - @Test - void isSingleton() { - when(context.isSingleton("testBeanName")).thenReturn(true); - assertTrue(ApplicationUtils.isSingleton("testBeanName")); - } - - @Test - void isPrototype() { - when(context.isPrototype("testBeanName")).thenReturn(true); - assertTrue(ApplicationUtils.isPrototype("testBeanName")); - } - - @Test - void testIsTypeMatchByClass() { - when(context.isTypeMatch("testBeanName", ConfigurableApplicationContext.class)).thenReturn(true); - assertTrue(ApplicationUtils.isTypeMatch("testBeanName", ConfigurableApplicationContext.class)); - } - - @Test - void testIsTypeMatchByType() { - ResolvableType type = ResolvableType.forClass(ConfigurableApplicationContext.class); - when(context.isTypeMatch("testBeanName", type)).thenReturn(true); - assertTrue(ApplicationUtils.isTypeMatch("testBeanName", type)); - } - @Test void testGetType() { when(context.getType("testBeanName")).thenAnswer(invocationOnMock -> ConfigurableApplicationContext.class); assertEquals(ConfigurableApplicationContext.class, ApplicationUtils.getType("testBeanName")); } - @Test - void testGetAliases() { - String[] names = new String[10]; - when(context.getAliases("testBeanName")).thenReturn(names); - assertEquals(names, ApplicationUtils.getAliases("testBeanName")); - } - @Test void testPublishEvent() { Object o = new Object(); @@ -317,25 +132,6 @@ void testPublishEvent() { verify(context).publishEvent(o); } - @Test - void testGetMessageWithDefault() { - when(context.getMessage("test", null, "default", Locale.getDefault())).thenReturn("test"); - assertEquals("test", ApplicationUtils.getMessage("test", null, "default", Locale.getDefault())); - } - - @Test - void testGetMessage() { - when(context.getMessage("test", null, Locale.getDefault())).thenReturn("test"); - assertEquals("test", ApplicationUtils.getMessage("test", null, Locale.getDefault())); - } - - @Test - void testGetMessageByMessageSourceResolvable() { - MessageSourceResolvable resolvable = mock(MessageSourceResolvable.class); - when(context.getMessage(resolvable, Locale.getDefault())).thenReturn("test"); - assertEquals("test", ApplicationUtils.getMessage(resolvable, Locale.getDefault())); - } - @Test void testGetResources() throws IOException { Resource[] resources = new Resource[0]; @@ -359,7 +155,18 @@ void testGetClassLoader() { @Test void initialize() { ApplicationUtils utils = new ApplicationUtils(); + ApplicationUtils.injectContext(null); utils.initialize(context); assertEquals(context, ApplicationUtils.getApplicationContext()); + + ConfigurableApplicationContext subContext = mock(ConfigurableApplicationContext.class); + when(subContext.getParent()).thenReturn(context); + utils.initialize(subContext); + assertEquals(subContext, ApplicationUtils.getApplicationContext()); + + ConfigurableApplicationContext subContext2 = mock(ConfigurableApplicationContext.class); + when(subContext2.getParent()).thenReturn(context); + utils.initialize(subContext2); + assertEquals(subContext, ApplicationUtils.getApplicationContext()); } } \ No newline at end of file diff --git a/test/config-test/src/test/resources/derby-schema.sql b/test/config-test/src/test/resources/derby-schema.sql index f4e93a3aab6..6a2392369ca 100644 --- a/test/config-test/src/test/resources/derby-schema.sql +++ b/test/config-test/src/test/resources/derby-schema.sql @@ -17,46 +17,48 @@ CREATE SCHEMA nacos AUTHORIZATION nacos; CREATE TABLE config_info ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128) DEFAULT NULL, - src_ip varchar(50) DEFAULT NULL, - c_desc varchar(256) DEFAULT NULL, - c_use varchar(64) DEFAULT NULL, - effect varchar(64) DEFAULT NULL, - type varchar(64) DEFAULT NULL, - c_schema LONG VARCHAR DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint configinfo_id_key PRIMARY KEY (id), - constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128) DEFAULT NULL, + src_ip varchar(50) DEFAULT NULL, + c_desc varchar(256) DEFAULT NULL, + c_use varchar(64) DEFAULT NULL, + effect varchar(64) DEFAULT NULL, + type varchar(64) DEFAULT NULL, + c_schema LONG VARCHAR DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint configinfo_id_key PRIMARY KEY (id), + constraint uk_configinfo_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); CREATE INDEX configinfo_dataid_key_idx ON config_info(data_id); CREATE INDEX configinfo_groupid_key_idx ON config_info(group_id); CREATE INDEX configinfo_dataid_group_key_idx ON config_info(data_id, group_id); CREATE TABLE his_config_info ( - id bigint NOT NULL, - nid bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - op_type char(10) DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); + id bigint NOT NULL, + nid bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00.000', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + publish_type varchar(50) DEFAULT 'formal', + ext_info CLOB, + op_type char(10) DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint hisconfiginfo_nid_key PRIMARY KEY (nid)); CREATE INDEX hisconfiginfo_dataid_key_idx ON his_config_info(data_id); CREATE INDEX hisconfiginfo_gmt_create_idx ON his_config_info(gmt_create); @@ -64,164 +66,174 @@ CREATE INDEX hisconfiginfo_gmt_modified_idx ON his_config_info(gmt_modified); CREATE TABLE config_info_beta ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - app_name varchar(128), - content CLOB, - beta_ips varchar(1024), - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - encrypted_data_key LONG VARCHAR DEFAULT NULL, - constraint configinfobeta_id_key PRIMARY KEY (id), - constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + app_name varchar(128), + content CLOB, + beta_ips varchar(1024), + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + encrypted_data_key LONG VARCHAR DEFAULT NULL, + constraint configinfobeta_id_key PRIMARY KEY (id), + constraint uk_configinfobeta_datagrouptenant UNIQUE (data_id,group_id,tenant_id)); CREATE TABLE config_info_tag ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - tag_id varchar(128) NOT NULL, - app_name varchar(128), - content CLOB, - md5 varchar(32) DEFAULT NULL, - gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - src_user varchar(128), - src_ip varchar(50) DEFAULT NULL, - constraint configinfotag_id_key PRIMARY KEY (id), - constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); - -CREATE TABLE config_info_aggr ( - id bigint NOT NULL generated by default as identity, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) default '', - datum_id varchar(255) NOT NULL, - app_name varchar(128), - content CLOB, - gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', - constraint configinfoaggr_id_key PRIMARY KEY (id), - constraint uk_configinfoaggr_datagrouptenantdatum UNIQUE (data_id,group_id,tenant_id,datum_id)); + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + tag_id varchar(128) NOT NULL, + app_name varchar(128), + content CLOB, + md5 varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + src_user varchar(128), + src_ip varchar(50) DEFAULT NULL, + constraint configinfotag_id_key PRIMARY KEY (id), + constraint uk_configinfotag_datagrouptenanttag UNIQUE (data_id,group_id,tenant_id,tag_id)); + +CREATE TABLE config_info_gray ( + id bigint NOT NULL generated by default as identity, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) default '', + gray_name varchar(128) NOT NULL, + gray_rule CLOB, + app_name varchar(128), + src_ip varchar(128), + src_user varchar(128) default '', + content CLOB, + md5 varchar(32) DEFAULT NULL, + encrypted_data_key varchar(32) DEFAULT NULL, + gmt_create timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp NOT NULL DEFAULT '2010-05-05 00:00:00', + constraint configinfogray_id_key PRIMARY KEY (id), + constraint uk_configinfogray_datagrouptenantgrayname UNIQUE (data_id,group_id,tenant_id,gray_name)); +CREATE INDEX config_info_gray_dataid_gmt_modified ON config_info_gray(data_id,gmt_modified); +CREATE INDEX config_info_gray_gmt_modified ON config_info_gray(gmt_modified); CREATE TABLE app_list ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - is_dynamic_collect_disabled smallint DEFAULT 0, - last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', - sub_info_lock_owner varchar(128), - sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', - constraint applist_id_key PRIMARY KEY (id), - constraint uk_appname UNIQUE (app_name)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + is_dynamic_collect_disabled smallint DEFAULT 0, + last_sub_info_collected_time timestamp DEFAULT '1970-01-01 08:00:00.0', + sub_info_lock_owner varchar(128), + sub_info_lock_time timestamp DEFAULT '1970-01-01 08:00:00.0', + constraint applist_id_key PRIMARY KEY (id), + constraint uk_appname UNIQUE (app_name)); CREATE TABLE app_configdata_relation_subs ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint configdatarelationsubs_id_key PRIMARY KEY (id), - constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationsubs_id_key PRIMARY KEY (id), + constraint uk_app_sub_config_datagroup UNIQUE (app_name, data_id, group_id)); CREATE TABLE app_configdata_relation_pubs ( - id bigint NOT NULL generated by default as identity, - app_name varchar(128) NOT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint configdatarelationpubs_id_key PRIMARY KEY (id), - constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); + id bigint NOT NULL generated by default as identity, + app_name varchar(128) NOT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint configdatarelationpubs_id_key PRIMARY KEY (id), + constraint uk_app_pub_config_datagroup UNIQUE (app_name, data_id, group_id)); CREATE TABLE config_tags_relation ( - id bigint NOT NULL, - tag_name varchar(128) NOT NULL, - tag_type varchar(64) DEFAULT NULL, - data_id varchar(255) NOT NULL, - group_id varchar(128) NOT NULL, - tenant_id varchar(128) DEFAULT '', - nid bigint NOT NULL generated by default as identity, - constraint config_tags_id_key PRIMARY KEY (nid), - constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); + id bigint NOT NULL, + tag_name varchar(128) NOT NULL, + tag_type varchar(64) DEFAULT NULL, + data_id varchar(255) NOT NULL, + group_id varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + nid bigint NOT NULL generated by default as identity, + constraint config_tags_id_key PRIMARY KEY (nid), + constraint uk_configtagrelation_configidtag UNIQUE (id, tag_name, tag_type)); CREATE INDEX config_tags_tenant_id_idx ON config_tags_relation(tenant_id); CREATE TABLE group_capacity ( - id bigint NOT NULL generated by default as identity, - group_id varchar(128) DEFAULT '', - quota int DEFAULT 0, - usage int DEFAULT 0, - max_size int DEFAULT 0, - max_aggr_count int DEFAULT 0, - max_aggr_size int DEFAULT 0, - max_history_count int DEFAULT 0, - gmt_create timestamp DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint group_capacity_id_key PRIMARY KEY (id), - constraint uk_group_id UNIQUE (group_id)); + id bigint NOT NULL generated by default as identity, + group_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint group_capacity_id_key PRIMARY KEY (id), + constraint uk_group_id UNIQUE (group_id)); CREATE TABLE tenant_capacity ( - id bigint NOT NULL generated by default as identity, - tenant_id varchar(128) DEFAULT '', - quota int DEFAULT 0, - usage int DEFAULT 0, - max_size int DEFAULT 0, - max_aggr_count int DEFAULT 0, - max_aggr_size int DEFAULT 0, - max_history_count int DEFAULT 0, - gmt_create timestamp DEFAULT '2010-05-05 00:00:00', - gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', - constraint tenant_capacity_id_key PRIMARY KEY (id), - constraint uk_tenant_id UNIQUE (tenant_id)); + id bigint NOT NULL generated by default as identity, + tenant_id varchar(128) DEFAULT '', + quota int DEFAULT 0, + usage int DEFAULT 0, + max_size int DEFAULT 0, + max_aggr_count int DEFAULT 0, + max_aggr_size int DEFAULT 0, + max_history_count int DEFAULT 0, + gmt_create timestamp DEFAULT '2010-05-05 00:00:00', + gmt_modified timestamp DEFAULT '2010-05-05 00:00:00', + constraint tenant_capacity_id_key PRIMARY KEY (id), + constraint uk_tenant_id UNIQUE (tenant_id)); CREATE TABLE tenant_info ( - id bigint NOT NULL generated by default as identity, - kp varchar(128) NOT NULL, - tenant_id varchar(128) DEFAULT '', - tenant_name varchar(128) DEFAULT '', - tenant_desc varchar(256) DEFAULT NULL, - create_source varchar(32) DEFAULT NULL, - gmt_create bigint NOT NULL, - gmt_modified bigint NOT NULL, - constraint tenant_info_id_key PRIMARY KEY (id), - constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); + id bigint NOT NULL generated by default as identity, + kp varchar(128) NOT NULL, + tenant_id varchar(128) DEFAULT '', + tenant_name varchar(128) DEFAULT '', + tenant_desc varchar(256) DEFAULT NULL, + create_source varchar(32) DEFAULT NULL, + gmt_create bigint NOT NULL, + gmt_modified bigint NOT NULL, + constraint tenant_info_id_key PRIMARY KEY (id), + constraint uk_tenant_info_kptenantid UNIQUE (kp,tenant_id)); CREATE INDEX tenant_info_tenant_id_idx ON tenant_info(tenant_id); CREATE TABLE users ( - username varchar(50) NOT NULL PRIMARY KEY, - password varchar(500) NOT NULL, - enabled boolean NOT NULL DEFAULT true + username varchar(50) NOT NULL PRIMARY KEY, + password varchar(500) NOT NULL, + enabled boolean NOT NULL DEFAULT true ); CREATE TABLE roles ( - username varchar(50) NOT NULL, - role varchar(50) NOT NULL, - constraint uk_username_role UNIQUE (username,role) + username varchar(50) NOT NULL, + role varchar(50) NOT NULL, + constraint uk_username_role UNIQUE (username,role) ); CREATE TABLE permissions ( - role varchar(50) NOT NULL, - resource varchar(512) NOT NULL, - action varchar(8) NOT NULL, - constraint uk_role_permission UNIQUE (role,resource,action) + role varchar(50) NOT NULL, + resource varchar(512) NOT NULL, + action varchar(8) NOT NULL, + constraint uk_role_permission UNIQUE (role,resource,action) ); + /******************************************/ /* ipv6 support */ /******************************************/ -ALTER TABLE `config_info_tag` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `his_config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL AFTER `src_user`; +ALTER TABLE config_info_tag ADD src_ip varchar(50) DEFAULT NULL; + +ALTER TABLE his_config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info ADD src_ip varchar(50) DEFAULT NULL ; + +ALTER TABLE config_info_beta ADD src_ip varchar(50) DEFAULT NULL ; -ALTER TABLE `config_info` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; -ALTER TABLE `config_info_beta` -MODIFY COLUMN `src_ip` varchar(50) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL COMMENT 'source ip' AFTER `src_user`; \ No newline at end of file +ALTER TABLE his_config_info ADD publish_type varchar(50) DEFAULT 'formal'; +ALTER TABLE his_config_info ADD ext_info CLOB DEFAULT NULL ; diff --git a/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/MemberLookupCoreITCase.java b/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/MemberLookupCoreITCase.java index 8622d5812c5..9a0e8459e14 100644 --- a/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/MemberLookupCoreITCase.java +++ b/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/MemberLookupCoreITCase.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.core.env.StandardEnvironment; -import org.springframework.mock.web.MockServletContext; import java.io.File; import java.nio.charset.StandardCharsets; @@ -79,7 +78,7 @@ void before() throws Exception { false); try { - memberManager = new ServerMemberManager(new MockServletContext()); + memberManager = new ServerMemberManager(); } catch (Exception e) { e.printStackTrace(); } diff --git a/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/ServerMemberManagerCoreITCase.java b/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/ServerMemberManagerCoreITCase.java index 8a13bb29abe..a5c4be8e91d 100644 --- a/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/ServerMemberManagerCoreITCase.java +++ b/test/core-test/src/test/java/com/alibaba/nacos/test/core/cluster/ServerMemberManagerCoreITCase.java @@ -34,7 +34,6 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; import org.springframework.core.env.StandardEnvironment; -import org.springframework.mock.web.MockServletContext; import java.util.ArrayList; import java.util.Collection; @@ -80,7 +79,7 @@ static void destroyClass() { @BeforeEach void before() throws Exception { - memberManager = new ServerMemberManager(new MockServletContext()); + memberManager = new ServerMemberManager(); } @AfterEach