Merge pull request #10475 from alibaba/asoc2022_issue#8460-2

[ISSUE#8460] Add config change hook plugin.
This commit is contained in:
杨翊 SionYang 2023-05-12 10:47:50 +08:00 committed by GitHub
commit 6cde13be92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1185 additions and 3 deletions

View File

@ -62,6 +62,11 @@
<artifactId>nacos-encryption-plugin</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-config-plugin</artifactId>
</dependency>
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpasyncclient</artifactId>

View File

@ -0,0 +1,404 @@
/*
* 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.aspect;
import com.alibaba.nacos.api.config.remote.request.ConfigPublishRequest;
import com.alibaba.nacos.api.config.remote.request.ConfigRemoveRequest;
import com.alibaba.nacos.api.config.remote.response.ConfigPublishResponse;
import com.alibaba.nacos.api.config.remote.response.ConfigRemoveResponse;
import com.alibaba.nacos.api.remote.request.RequestMeta;
import com.alibaba.nacos.api.remote.response.ResponseCode;
import com.alibaba.nacos.common.model.RestResultUtils;
import com.alibaba.nacos.config.server.configuration.ConfigChangeConfigs;
import com.alibaba.nacos.config.server.model.SameConfigPolicy;
import com.alibaba.nacos.config.server.utils.ConfigExecutor;
import com.alibaba.nacos.config.server.utils.RequestUtil;
import com.alibaba.nacos.config.server.utils.TimeUtils;
import com.alibaba.nacos.plugin.config.ConfigChangePluginManager;
import com.alibaba.nacos.plugin.config.constants.ConfigChangeConstants;
import com.alibaba.nacos.plugin.config.constants.ConfigChangeExecuteTypes;
import com.alibaba.nacos.plugin.config.constants.ConfigChangePointCutTypes;
import com.alibaba.nacos.plugin.config.model.ConfigChangeRequest;
import com.alibaba.nacos.plugin.config.model.ConfigChangeResponse;
import com.alibaba.nacos.plugin.config.spi.ConfigChangePluginService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Comparator;
import java.util.List;
import java.util.Locale;
import java.util.PriorityQueue;
import java.util.Properties;
/**
* Config change pointcut aspect,which config change plugin services will pointcut.
*
* @author liyunfei
*/
@Aspect
@Component
public class ConfigChangeAspect {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigChangeAspect.class);
private static final Integer DEFAULT_BEFORE_QUEUE_CAPACITY = 2;
private static final Integer DEFAULT_AFTER_QUEUE_CAPACITY = 1;
private static final String ENABLED = "enabled";
/**
* Publish or update config through http.
*/
private static final String CLIENT_INTERFACE_PUBLISH_CONFIG =
"execution(* com.alibaba.nacos.config.server.controller.ConfigController.publishConfig(..)) "
+ "&& args(request,response,dataId,group,tenant,content,tag,appName,srcUser,configTags,desc,use,effect,type,..) "
+ "&& @annotation(org.springframework.web.bind.annotation.PostMapping)";
/**
* Publish or update config through rpc.
*/
private static final String CLIENT_INTERFACE_PUBLISH_CONFIG_RPC =
"execution(* com.alibaba.nacos.core.remote.RequestHandler.handleRequest(..)) "
+ "&& target(com.alibaba.nacos.config.server.remote.ConfigPublishRequestHandler) "
+ "&& args(request,meta)";
/**
* Remove config by id through http.
*/
private static final String CLIENT_INTERFACE_REMOVE_CONFIG =
"execution(* com.alibaba.nacos.config.server.controller.ConfigController.deleteConfig(..))"
+ " && args(request,response,dataId,group,tenant,..)";
/**
* Remove config by ids through http.
*/
private static final String CLIENT_INTERFACE_BATCH_REMOVE_CONFIG =
"execution(* com.alibaba.nacos.config.server.controller.ConfigController.deleteConfigs(..))"
+ " && args(request,ids)";
/**
* Remove config through rpc.
*/
@SuppressWarnings("checkstyle:linelength")
private static final String CLIENT_INTERFACE_REMOVE_CONFIG_RPC =
"execution(* com.alibaba.nacos.core.remote.RequestHandler.handleRequest(..)) "
+ " && target(com.alibaba.nacos.config.server.remote.ConfigRemoveRequestHandler)"
+ " && args(request,meta)";
/**
* Import file through http.
*/
private static final String CLIENT_INTERFACE_IMPORT_CONFIG =
"execution(* com.alibaba.nacos.config.server.controller.ConfigController.importAndPublishConfig(..)) "
+ "&& args(request,srcUser,namespace,policy,file)";
private final ConfigChangeConfigs configChangeConfigs;
private ConfigChangePluginManager configChangeManager;
public ConfigChangeAspect(ConfigChangeConfigs configChangeConfigs) {
this.configChangeConfigs = configChangeConfigs;
configChangeManager = ConfigChangePluginManager.getInstance();
}
/**
* Publish or update config.
*/
@Around(CLIENT_INTERFACE_PUBLISH_CONFIG)
Object publishOrUpdateConfigAround(ProceedingJoinPoint pjp, HttpServletRequest request,
HttpServletResponse response, String dataId, String group, String tenant, String content, String tag,
String appName, String srcUser, String configTags, String desc, String use, String effect, String type)
throws Throwable {
final ConfigChangePointCutTypes configChangePointCutType = ConfigChangePointCutTypes.PUBLISH_BY_HTTP;
final PriorityQueue<ConfigChangePluginService> pluginServicePriorityQueue = getPluginServicePriorityQueue(
configChangePointCutType);
// didn't enabled or add relative plugin
if (pluginServicePriorityQueue.isEmpty()) {
return pjp.proceed();
}
ConfigChangeRequest configChangeRequest = new ConfigChangeRequest(configChangePointCutType);
configChangeRequest.setArg("dataId", dataId);
configChangeRequest.setArg("group", group);
configChangeRequest.setArg("tenant", tenant);
configChangeRequest.setArg("content", content);
configChangeRequest.setArg("tag", tag);
configChangeRequest.setArg("requestIpApp", appName);
configChangeRequest.setArg("srcIp", RequestUtil.getRemoteIp(request));
configChangeRequest.setArg("configTags", configTags);
configChangeRequest.setArg("desc", desc);
configChangeRequest.setArg("use", use);
configChangeRequest.setArg("effect", effect);
configChangeRequest.setArg("type", type);
return configChangeServiceHandle(pjp, pluginServicePriorityQueue, configChangeRequest);
}
/**
* Remove config.
*/
@Around(CLIENT_INTERFACE_REMOVE_CONFIG)
Object removeConfigByIdAround(ProceedingJoinPoint pjp, HttpServletRequest request, HttpServletResponse response,
String dataId, String group, String tenant) throws Throwable {
final ConfigChangePointCutTypes configChangePointCutType = ConfigChangePointCutTypes.REMOVE_BY_HTTP;
final PriorityQueue<ConfigChangePluginService> pluginServicePriorityQueue = getPluginServicePriorityQueue(
configChangePointCutType);
// didn't enabled or add relative plugin
if (pluginServicePriorityQueue.isEmpty()) {
return pjp.proceed();
}
ConfigChangeRequest configChangeRequest = new ConfigChangeRequest(configChangePointCutType);
configChangeRequest.setArg("dataId", dataId);
configChangeRequest.setArg("group", group);
configChangeRequest.setArg("tenant", tenant);
configChangeRequest.setArg("srcIp", RequestUtil.getRemoteIp(request));
configChangeRequest.setArg("requestIpApp", RequestUtil.getAppName(request));
configChangeRequest.setArg("use", RequestUtil.getSrcUserName(request));
return configChangeServiceHandle(pjp, pluginServicePriorityQueue, configChangeRequest);
}
/**
* Remove config by ids.
*/
@Around(CLIENT_INTERFACE_BATCH_REMOVE_CONFIG)
public Object removeConfigByIdsAround(ProceedingJoinPoint pjp, HttpServletRequest request, List<Long> ids)
throws Throwable {
final ConfigChangePointCutTypes configChangePointCutType = ConfigChangePointCutTypes.REMOVE_BATCH_HTTP;
final PriorityQueue<ConfigChangePluginService> pluginServicePriorityQueue = getPluginServicePriorityQueue(
configChangePointCutType);
// didn't enabled or add relative plugin
if (pluginServicePriorityQueue.isEmpty()) {
return pjp.proceed();
}
ConfigChangeRequest configChangeRequest = new ConfigChangeRequest(configChangePointCutType);
configChangeRequest.setArg("dataId", ids.toString());
configChangeRequest.setArg("srcIp", RequestUtil.getRemoteIp(request));
configChangeRequest.setArg("requestIpApp", RequestUtil.getAppName(request));
configChangeRequest.setArg("use", RequestUtil.getSrcUserName(request));
return configChangeServiceHandle(pjp, pluginServicePriorityQueue, configChangeRequest);
}
/**
* Import config.
*/
@Around(CLIENT_INTERFACE_IMPORT_CONFIG)
public Object importConfigAround(ProceedingJoinPoint pjp, HttpServletRequest request, String srcUser,
String namespace, SameConfigPolicy policy, MultipartFile file) throws Throwable {
final ConfigChangePointCutTypes configChangePointCutType = ConfigChangePointCutTypes.IMPORT_BY_HTTP;
final PriorityQueue<ConfigChangePluginService> pluginServicePriorityQueue = getPluginServicePriorityQueue(
configChangePointCutType);
// didn't enabled or add relative plugin
if (pluginServicePriorityQueue.isEmpty()) {
return pjp.proceed();
}
ConfigChangeRequest configChangeRequest = new ConfigChangeRequest(configChangePointCutType);
configChangeRequest.setArg("srcUser", srcUser);
configChangeRequest.setArg("namespace", namespace);
configChangeRequest.setArg("policy", policy);
configChangeRequest.setArg("file", file);
configChangeRequest.setArg("srcIp", RequestUtil.getRemoteIp(request));
configChangeRequest.setArg("requestIpApp", RequestUtil.getAppName(request));
configChangeRequest.setArg("use", RequestUtil.getSrcUserName(request));
return configChangeServiceHandle(pjp, pluginServicePriorityQueue, configChangeRequest);
}
/**
* Publish or update config.
*/
@Around(CLIENT_INTERFACE_PUBLISH_CONFIG_RPC)
Object publishConfigAroundRpc(ProceedingJoinPoint pjp, ConfigPublishRequest request, RequestMeta meta)
throws Throwable {
final ConfigChangePointCutTypes configChangePointCutType = ConfigChangePointCutTypes.PUBLISH_BY_RPC;
final PriorityQueue<ConfigChangePluginService> pluginServicePriorityQueue = getPluginServicePriorityQueue(
configChangePointCutType);
// didn't enabled or add relative plugin
if (pluginServicePriorityQueue.isEmpty()) {
return pjp.proceed();
}
ConfigChangeRequest configChangeRequest = new ConfigChangeRequest(configChangePointCutType);
configChangeRequest.setArg("dataId", request.getDataId());
configChangeRequest.setArg("group", request.getGroup());
configChangeRequest.setArg("tenant", request.getTenant());
configChangeRequest.setArg("content", request.getContent());
configChangeRequest.setArg("type", request.getAdditionParam("type"));
configChangeRequest.setArg("tag", request.getAdditionParam("tag"));
configChangeRequest.setArg("configTags", request.getAdditionParam("config_tags"));
configChangeRequest.setArg("desc", request.getAdditionParam("desc"));
configChangeRequest.setArg("effect", request.getAdditionParam("effect"));
configChangeRequest.setArg("appName", request.getAdditionParam("appName"));
configChangeRequest.setArg("srcIp", meta.getClientIp());
configChangeRequest.setArg("requestIpApp", request.getAdditionParam("requestIpApp"));
configChangeRequest.setArg("srcUser", request.getAdditionParam("src_user"));
configChangeRequest.setArg("use", request.getAdditionParam("use"));
return configChangeServiceHandle(pjp, pluginServicePriorityQueue, configChangeRequest);
}
/**
* Remove config.
*/
@Around(CLIENT_INTERFACE_REMOVE_CONFIG_RPC)
Object removeConfigAroundRpc(ProceedingJoinPoint pjp, ConfigRemoveRequest request, RequestMeta meta)
throws Throwable {
final ConfigChangePointCutTypes configChangePointCutType = ConfigChangePointCutTypes.REMOVE_BY_RPC;
final PriorityQueue<ConfigChangePluginService> pluginServicePriorityQueue = getPluginServicePriorityQueue(
configChangePointCutType);
// didn't enabled or add relative plugin
if (pluginServicePriorityQueue.isEmpty()) {
return pjp.proceed();
}
ConfigChangeRequest configChangeRequest = new ConfigChangeRequest(configChangePointCutType);
configChangeRequest.setArg("dataId", request.getDataId());
configChangeRequest.setArg("group", request.getGroup());
configChangeRequest.setArg("tenant", request.getTenant());
configChangeRequest.setArg("appName", request.getHeader("appName"));
configChangeRequest.setArg("srcIp", meta.getClientIp());
configChangeRequest.setArg("requestIpApp", request.getHeader("requestIpApp"));
configChangeRequest.setArg("srcUser", request.getHeader("src_user"));
configChangeRequest.setArg("use", request.getHeader("use"));
return configChangeServiceHandle(pjp, pluginServicePriorityQueue, configChangeRequest);
}
/**
* Execute relevant config change plugin services.
*/
private Object configChangeServiceHandle(ProceedingJoinPoint pjp,
PriorityQueue<ConfigChangePluginService> configChangePluginServicePriorityQueue,
ConfigChangeRequest configChangeRequest) {
configChangeRequest.setArg("modifyTime", TimeUtils.getCurrentTimeStr());
ConfigChangePointCutTypes handleType = configChangeRequest.getRequestType();
ConfigChangeResponse configChangeResponse = new ConfigChangeResponse(handleType);
// default success,when before plugin service verify failed , set false
configChangeResponse.setSuccess(true);
PriorityQueue<ConfigChangePluginService> beforeExecutePriorityQueue = new PriorityQueue<>(
DEFAULT_BEFORE_QUEUE_CAPACITY, Comparator.comparingInt(ConfigChangePluginService::getOrder));
PriorityQueue<ConfigChangePluginService> afterExecutePriorityQueue = new PriorityQueue<>(
DEFAULT_AFTER_QUEUE_CAPACITY, Comparator.comparingInt(ConfigChangePluginService::getOrder));
Object retVal = null;
Object[] args = pjp.getArgs();
configChangeRequest.setArg(ConfigChangeConstants.ORIGINAL_ARGS, args);
for (ConfigChangePluginService ccs : configChangePluginServicePriorityQueue) {
if (!isEnabled(ccs)) {
continue;
}
if (ConfigChangeExecuteTypes.EXECUTE_BEFORE_TYPE.equals(ccs.executeType())) {
beforeExecutePriorityQueue.add(ccs);
} else {
afterExecutePriorityQueue.add(ccs);
}
}
// before plugin service execute
for (ConfigChangePluginService ccs : beforeExecutePriorityQueue) {
final String serviceType = ccs.getServiceType().toLowerCase(Locale.ROOT);
final Properties properties = configChangeConfigs.getPluginProperties(serviceType);
configChangeRequest.setArg("pluginProperties", properties);
ccs.execute(configChangeRequest, configChangeResponse);
if (null != configChangeResponse.getArgs()) {
// update args by filter with whitelist
args = configChangeResponse.getArgs();
}
// prevent execute next before plugins service
if (!configChangeResponse.isSuccess()) {
retVal = wrapErrorResp(configChangeResponse);
break;
}
}
try {
// if validate failed,skipped directly
if (configChangeResponse.isSuccess()) {
retVal = pjp.proceed(args);
}
} catch (Throwable e) {
LOGGER.warn("config change plugin proceed failed {}", e.getMessage());
configChangeResponse.setMsg("config change plugin proceed failed " + e.getMessage());
retVal = wrapErrorResp(configChangeResponse);
}
// after plugin service execute
ConfigExecutor.executeAsyncConfigChangePluginTask(() -> {
for (ConfigChangePluginService ccs : afterExecutePriorityQueue) {
try {
final String serviceType = ccs.getServiceType().toLowerCase(Locale.ROOT);
final Properties properties = configChangeConfigs.getPluginProperties(serviceType);
configChangeRequest.setArg(ConfigChangeConstants.PLUGIN_PROPERTIES, properties);
ccs.execute(configChangeRequest, configChangeResponse);
} catch (Throwable throwable) {
LOGGER.warn("execute async plugin services failed {}", throwable.getMessage());
}
}
});
return retVal;
}
private PriorityQueue<ConfigChangePluginService> getPluginServicePriorityQueue(
ConfigChangePointCutTypes configChangePointCutType) {
PriorityQueue<ConfigChangePluginService> pluginServicePriorityQueue = ConfigChangePluginManager
.findPluginServiceQueueByPointcut(configChangePointCutType);
if (pluginServicePriorityQueue == null) {
return new PriorityQueue<>();
}
for (ConfigChangePluginService each : pluginServicePriorityQueue) {
if (isEnabled(each)) {
return pluginServicePriorityQueue;
}
}
return new PriorityQueue<>();
}
private boolean isEnabled(ConfigChangePluginService configChangePluginService) {
Properties serviceConfigProperties = configChangeConfigs
.getPluginProperties(configChangePluginService.getServiceType());
return Boolean.parseBoolean(serviceConfigProperties.getProperty(ENABLED));
}
private Object wrapErrorResp(ConfigChangeResponse configChangeResponse) {
Object retVal = null;
switch (configChangeResponse.getResponseType()) {
// some of controller did'nt design error msg resp
case IMPORT_BY_HTTP:
case REMOVE_BATCH_HTTP:
case REMOVE_BY_HTTP:
case PUBLISH_BY_HTTP: {
retVal = RestResultUtils.failed(configChangeResponse.getMsg());
break;
}
case PUBLISH_BY_RPC: {
retVal = ConfigPublishResponse
.buildFailResponse(ResponseCode.FAIL.getCode(), configChangeResponse.getMsg());
break;
}
case REMOVE_BY_RPC: {
retVal = ConfigRemoveResponse.buildFailResponse(configChangeResponse.getMsg());
break;
}
default: {
// ignore
}
}
return retVal;
}
}

View File

@ -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.config.server.configuration;
import com.alibaba.nacos.common.event.ServerConfigChangeEvent;
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.config.constants.ConfigChangeConstants;
import com.alibaba.nacos.sys.env.EnvUtil;
import com.alibaba.nacos.sys.utils.PropertiesUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Configuration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* config change plugin configs.
*
* @author liyunfei
**/
@Configuration
public class ConfigChangeConfigs extends Subscriber<ServerConfigChangeEvent> {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigChangeConfigs.class);
private static final String PREFIX = ConfigChangeConstants.NACOS_CORE_CONFIG_PLUGIN_PREFIX;
private Map<String, Properties> configPluginProperties = new HashMap<>();
public ConfigChangeConfigs() {
NotifyCenter.registerSubscriber(this);
refreshPluginProperties();
}
private void refreshPluginProperties() {
try {
Map<String, Properties> newProperties = new HashMap<>(3);
Properties properties = PropertiesUtil.getPropertiesWithPrefix(EnvUtil.getEnvironment(), PREFIX);
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));
}
configPluginProperties = newProperties;
} catch (Exception e) {
LOGGER.warn("[ConfigChangeConfigs]Refresh config plugin properties failed ", e);
}
}
public Properties getPluginProperties(String configPluginType) {
if (!configPluginProperties.containsKey(configPluginType)) {
LOGGER.warn(
"[ConfigChangeConfigs]Can't find config plugin properties for type {}, will use empty properties",
configPluginType);
return new Properties();
}
return configPluginProperties.get(configPluginType);
}
@Override
public void onEvent(ServerConfigChangeEvent event) {
refreshPluginProperties();
}
@Override
public Class<? extends Event> subscribeType() {
return ServerConfigChangeEvent.class;
}
}

View File

@ -475,7 +475,7 @@ public class EmbeddedConfigInfoPersistServiceImpl implements ConfigInfoPersistSe
MapperContext context = new MapperContext();
context.putWhereParameter(FieldConstant.IDS, paramList);
MapperResult result = configInfoMapper.removeConfigInfoByIdsAtomic(context);
EmbeddedStorageContextHolder.addSqlContext(result.getSql(), result.getParamList());
EmbeddedStorageContextHolder.addSqlContext(result.getSql(), result.getParamList().toArray());
}
@Override

View File

@ -46,6 +46,11 @@ public final class ConfigExecutor {
.newScheduledExecutorService(ClassUtils.getCanonicalName(Config.class), 100,
new NameThreadFactory("com.alibaba.nacos.config.AsyncNotifyService"));
private static final ScheduledExecutorService ASYNC_CONFIG_CHANGE_PLUGIN_EXECUTOR = ExecutorFactory.Managed
.newScheduledExecutorService(ClassUtils.getCanonicalName(Config.class),
ThreadUtils.getSuitableThreadCount(),
new NameThreadFactory("com.alibaba.nacos.config.plugin.AsyncService"));
private static final ScheduledExecutorService CONFIG_SUB_SERVICE_EXECUTOR = ExecutorFactory.Managed
.newScheduledExecutorService(ClassUtils.getCanonicalName(Config.class),
ThreadUtils.getSuitableThreadCount(),
@ -76,6 +81,10 @@ public final class ConfigExecutor {
ASYNC_NOTIFY_EXECUTOR.schedule(command, delay, unit);
}
public static void executeAsyncConfigChangePluginTask(Runnable runnable) {
ASYNC_CONFIG_CHANGE_PLUGIN_EXECUTOR.execute(runnable);
}
public static int asyncNotifyQueueSize() {
return ((ScheduledThreadPoolExecutor) ASYNC_NOTIFY_EXECUTOR).getQueue().size();
}

View File

@ -140,6 +140,19 @@ nacos.core.auth.plugin.nacos.token.secret.key=
#nacos.core.auth.ldap.filter.prefix=uid
#nacos.core.auth.ldap.case.sensitive=true
#*************** 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 Related Configurations ***************#
### If turn on the MCP server:

40
plugin/config/pom.xml Normal file
View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>nacos-plugin</artifactId>
<groupId>com.alibaba.nacos</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nacos-config-plugin</artifactId>
<name>nacos-config-plugin ${project.version}</name>
<url>http://nacos.io</url>
<dependencies>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-common</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,140 @@
/*
* 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.config;
import com.alibaba.nacos.common.spi.NacosServiceLoader;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.plugin.config.constants.ConfigChangePointCutTypes;
import com.alibaba.nacos.plugin.config.spi.ConfigChangePluginService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Collection;
import java.util.Comparator;
import java.util.Map;
import java.util.Optional;
import java.util.PriorityQueue;
import java.util.concurrent.ConcurrentHashMap;
/**
* All config change plugin manager.
*
* @author liyunfei
*/
public class ConfigChangePluginManager {
private static final Logger LOGGER = LoggerFactory.getLogger(ConfigChangePluginManager.class);
private static final Integer PLUGIN_SERVICE_COUNT = 2;
private static final Integer POINT_CUT_TYPE_COUNT = ConfigChangePointCutTypes.values().length;
/**
* The relationship of serviceType and {@link ConfigChangePluginService} ,default capacity is the count of plugin
* service.
*/
private static final Map<String, ConfigChangePluginService> CONFIG_CHANGE_PLUGIN_SERVICE_MAP = new ConcurrentHashMap<>(
PLUGIN_SERVICE_COUNT);
/**
* The relationship of config change pointcut type and the queue of {@link ConfigChangePluginService} will pointcut
* it, default capacity is the count of pointcutTypes.
*/
private static Map<ConfigChangePointCutTypes, PriorityQueue<ConfigChangePluginService>> priorityQueueMap = new ConcurrentHashMap<>(
POINT_CUT_TYPE_COUNT);
private static final ConfigChangePluginManager INSTANCE = new ConfigChangePluginManager();
private ConfigChangePluginManager() {
loadConfigChangeServices();
}
/**
* Load all config change plugin services by spi.
*/
private static void loadConfigChangeServices() {
Collection<ConfigChangePluginService> configChangePluginServices = NacosServiceLoader
.load(ConfigChangePluginService.class);
// load all config change plugin by spi
for (ConfigChangePluginService each : configChangePluginServices) {
if (StringUtils.isEmpty(each.getServiceType())) {
LOGGER.warn("[ConfigChangePluginManager] Load {}({}) ConfigChangeServiceName(null/empty) fail. "
+ "Please Add the Plugin Service ConfigChangeServiceName to resolve.",
each.getClass().getName(), each.getClass());
continue;
}
CONFIG_CHANGE_PLUGIN_SERVICE_MAP.put(each.getServiceType(), each);
LOGGER.info("[ConfigChangePluginManager] Load {}({}) ConfigChangeServiceName({}) successfully.",
each.getClass().getName(), each.getClass(), each.getServiceType());
// map the relationship of pointcut and plugin service
addPluginServiceByPointCut(each);
}
}
public static ConfigChangePluginManager getInstance() {
return INSTANCE;
}
/**
* Dynamic get any pluginServiceImpl.
*
* @param serviceType plugin service type.
* @return
*/
public Optional<ConfigChangePluginService> findPluginServiceImpl(String serviceType) {
return Optional.ofNullable(CONFIG_CHANGE_PLUGIN_SERVICE_MAP.get(serviceType));
}
/**
* Dynamic add new ConfigChangeService.
*
* @param configChangePluginService ConfigChangeService.
* @return
*/
public static synchronized boolean join(ConfigChangePluginService configChangePluginService) {
CONFIG_CHANGE_PLUGIN_SERVICE_MAP
.putIfAbsent(configChangePluginService.getServiceType(), configChangePluginService);
addPluginServiceByPointCut(configChangePluginService);
return true;
}
/**
* Get the plugin service queue of the pointcut method.
*
* @param pointcutName pointcut method name,detail see {@link ConfigChangePointCutTypes}.
* @return
*/
public static PriorityQueue<ConfigChangePluginService> findPluginServiceQueueByPointcut(
ConfigChangePointCutTypes pointcutName) {
return priorityQueueMap.getOrDefault(pointcutName, new PriorityQueue<>());
}
private static boolean addPluginServiceByPointCut(ConfigChangePluginService configChangePluginService) {
ConfigChangePointCutTypes[] pointcutNames = configChangePluginService.pointcutMethodNames();
for (ConfigChangePointCutTypes name : pointcutNames) {
PriorityQueue<ConfigChangePluginService> configChangePluginServicePriorityQueue = priorityQueueMap
.get(name);
if (configChangePluginServicePriorityQueue == null) {
configChangePluginServicePriorityQueue = new PriorityQueue<>(PLUGIN_SERVICE_COUNT,
Comparator.comparingInt(ConfigChangePluginService::getOrder));
}
configChangePluginServicePriorityQueue.add(configChangePluginService);
priorityQueueMap.put(name, configChangePluginServicePriorityQueue);
}
return true;
}
}

View File

@ -0,0 +1,36 @@
/*
* 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.plugin.config.constants;
/**
* Config change plugin service constants.
*
* @author liyunfei
*/
public class ConfigChangeConstants {
public static final String NACOS_CORE_CONFIG_PLUGIN_PREFIX = "nacos.core.config.plugin.";
public static final String PLUGIN_PROPERTIES = "pluginProperties";
/**
* The actual config method args.
*/
public static final String ORIGINAL_ARGS = "originalArgs";
}

View File

@ -0,0 +1,37 @@
/*
* 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.plugin.config.constants;
/**
* ConfigChangeExecuteTypes.
*
* @author liyunfei
*/
public enum ConfigChangeExecuteTypes {
/**
* Execute before pointcut.
*/
EXECUTE_BEFORE_TYPE,
/**
* Execute after pointcut.
*/
EXECUTE_AFTER_TYPE;
public boolean equals(ConfigChangeExecuteTypes configChangeExecuteTypes) {
return this.compareTo(configChangeExecuteTypes) == 0;
}
}

View File

@ -0,0 +1,64 @@
/*
* 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.plugin.config.constants;
/**
* Config change type depend on the pointcut method.
*
* @author liyunfei
*/
public enum ConfigChangePointCutTypes {
/**
* Publish or update config through http.
*/
PUBLISH_BY_HTTP("publishOrUpdateByHttp"),
/**
* Publish config through rpc.
*/
PUBLISH_BY_RPC("publishOrUpdateByRpc"),
/**
* Remove by id through http.
*/
REMOVE_BY_HTTP("removeSingleByHttp"),
/**
* Remove through rpc.
*/
REMOVE_BY_RPC("removeSingleByRpc"),
/**
* Import config file through http/console.
*/
IMPORT_BY_HTTP("importFileByHttp"),
/**
* Remove by ids through http.
*/
REMOVE_BATCH_HTTP("removeBatchByHttp");
private final String value;
ConfigChangePointCutTypes(String value) {
this.value = value;
}
public String value() {
return value;
}
public boolean equals(ConfigChangePointCutTypes configChangePointCutTypes) {
return this.compareTo(configChangePointCutTypes) == 0;
}
}

View File

@ -0,0 +1,53 @@
/*
* 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.plugin.config.model;
import com.alibaba.nacos.plugin.config.constants.ConfigChangePointCutTypes;
import java.util.HashMap;
/**
* ConfigChangeRequest.
*
* @author liyunfei
*/
public class ConfigChangeRequest {
private ConfigChangePointCutTypes requestType;
private HashMap<String, Object> requestArgs = new HashMap<>(8);
public ConfigChangeRequest(ConfigChangePointCutTypes requestType) {
this.requestType = requestType;
}
public ConfigChangePointCutTypes getRequestType() {
return requestType;
}
public void setArg(String key, Object value) {
requestArgs.putIfAbsent(key, value);
}
public Object getArg(String key) {
return requestArgs.getOrDefault(key, null);
}
public HashMap<String, Object> getRequestArgs() {
return requestArgs;
}
}

View File

@ -0,0 +1,81 @@
/*
* 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.plugin.config.model;
import com.alibaba.nacos.plugin.config.constants.ConfigChangePointCutTypes;
/**
* ConfigChangeResponse.
*
* @author liyunfei
*/
public class ConfigChangeResponse {
private ConfigChangePointCutTypes responseType;
private boolean isSuccess;
private Object retVal;
private String msg;
private Object[] args;
public ConfigChangeResponse(ConfigChangePointCutTypes responseType) {
this.responseType = responseType;
}
public ConfigChangePointCutTypes getResponseType() {
return responseType;
}
public void setResponseType(ConfigChangePointCutTypes responseType) {
this.responseType = responseType;
}
public boolean isSuccess() {
return isSuccess;
}
public void setSuccess(boolean success) {
isSuccess = success;
}
public Object getRetVal() {
return retVal;
}
public void setRetVal(Object retVal) {
this.retVal = retVal;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public Object[] getArgs() {
return args;
}
public void setArgs(Object[] args) {
this.args = args;
}
}

View File

@ -0,0 +1,74 @@
/*
* 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.config.spi;
import com.alibaba.nacos.plugin.config.constants.ConfigChangeConstants;
import com.alibaba.nacos.plugin.config.constants.ConfigChangeExecuteTypes;
import com.alibaba.nacos.plugin.config.constants.ConfigChangePointCutTypes;
import com.alibaba.nacos.plugin.config.model.ConfigChangeRequest;
import com.alibaba.nacos.plugin.config.model.ConfigChangeResponse;
/**
* ConfigChangePluginService.
*
* @author liyunfei
*/
public interface ConfigChangePluginService {
/**
* execute config change plugin service.
*
* @param configChangeRequest ConfigChangeRequest
* @param configChangeResponse ConfigChangeResponse
*/
void execute(ConfigChangeRequest configChangeRequest, ConfigChangeResponse configChangeResponse);
/**
* execute type {@link ConfigChangeExecuteTypes}.
*
* @return type
*/
ConfigChangeExecuteTypes executeType();
/**
* what kind of plugin service,such as webhook,whiteList and other,need keep a way with the constants config of you
* enum in {@link ConfigChangeConstants}.
*
* @return service type
*/
String getServiceType();
/**
* when pointcut the same method,according to order to load plugin service. order is lower,prior is higher.
*
* @return order
*/
int getOrder();
/**
* the ConfigChangeTypes {@link ConfigChangePointCutTypes} of need to pointcut.
*
* <p>
* ConfigChangeTypes mean the relevant pointcut method.
* </p>
*
* @return array of pointcut the methods
*/
ConfigChangePointCutTypes[] pointcutMethodNames();
}

View File

@ -0,0 +1,131 @@
/*
* 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.config;
import com.alibaba.nacos.plugin.config.constants.ConfigChangeExecuteTypes;
import com.alibaba.nacos.plugin.config.constants.ConfigChangePointCutTypes;
import com.alibaba.nacos.plugin.config.model.ConfigChangeRequest;
import com.alibaba.nacos.plugin.config.model.ConfigChangeResponse;
import com.alibaba.nacos.plugin.config.spi.ConfigChangePluginService;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import java.util.Optional;
import java.util.PriorityQueue;
/**
* ConfigChangePluginManagerTests.
*
* @author liyunfei
**/
public class ConfigChangePluginManagerTests {
@Test
public void testInstance() {
ConfigChangePluginManager instance = ConfigChangePluginManager.getInstance();
Assert.assertNotNull(instance);
}
@Before
public void initPluginServices() {
ConfigChangePluginManager.join(new ConfigChangePluginService() {
@Override
public void execute(ConfigChangeRequest configChangeRequest, ConfigChangeResponse configChangeResponse) {
// ignore
}
@Override
public ConfigChangeExecuteTypes executeType() {
return ConfigChangeExecuteTypes.EXECUTE_BEFORE_TYPE;
}
@Override
public String getServiceType() {
return "test1";
}
@Override
public int getOrder() {
return 0;
}
@Override
public ConfigChangePointCutTypes[] pointcutMethodNames() {
return new ConfigChangePointCutTypes[]{ConfigChangePointCutTypes.PUBLISH_BY_HTTP, ConfigChangePointCutTypes.PUBLISH_BY_RPC};
}
});
ConfigChangePluginManager.join(new ConfigChangePluginService() {
@Override
public void execute(ConfigChangeRequest configChangeRequest, ConfigChangeResponse configChangeResponse) {
// ignore
}
@Override
public ConfigChangeExecuteTypes executeType() {
return ConfigChangeExecuteTypes.EXECUTE_BEFORE_TYPE;
}
@Override
public String getServiceType() {
return "test2";
}
@Override
public int getOrder() {
return 200;
}
@Override
public ConfigChangePointCutTypes[] pointcutMethodNames() {
return new ConfigChangePointCutTypes[]{ConfigChangePointCutTypes.IMPORT_BY_HTTP, ConfigChangePointCutTypes.PUBLISH_BY_RPC};
}
});
}
@Test
public void testFindPluginServiceQueueByPointcut() {
PriorityQueue<ConfigChangePluginService> configChangePluginServicePriorityQueue = ConfigChangePluginManager
.findPluginServiceQueueByPointcut(ConfigChangePointCutTypes.PUBLISH_BY_HTTP);
Assert.assertEquals(1, configChangePluginServicePriorityQueue.size());
configChangePluginServicePriorityQueue = ConfigChangePluginManager
.findPluginServiceQueueByPointcut(ConfigChangePointCutTypes.PUBLISH_BY_RPC);
Assert.assertEquals(2, configChangePluginServicePriorityQueue.size());
configChangePluginServicePriorityQueue = ConfigChangePluginManager
.findPluginServiceQueueByPointcut(ConfigChangePointCutTypes.IMPORT_BY_HTTP);
Assert.assertEquals(1, configChangePluginServicePriorityQueue.size());
configChangePluginServicePriorityQueue = ConfigChangePluginManager
.findPluginServiceQueueByPointcut(ConfigChangePointCutTypes.REMOVE_BATCH_HTTP);
Assert.assertEquals(0, configChangePluginServicePriorityQueue.size());
configChangePluginServicePriorityQueue = ConfigChangePluginManager
.findPluginServiceQueueByPointcut(ConfigChangePointCutTypes.REMOVE_BY_RPC);
Assert.assertEquals(0, configChangePluginServicePriorityQueue.size());
configChangePluginServicePriorityQueue = ConfigChangePluginManager
.findPluginServiceQueueByPointcut(ConfigChangePointCutTypes.REMOVE_BY_HTTP);
Assert.assertEquals(0, configChangePluginServicePriorityQueue.size());
}
@Test
public void testFindPluginServiceByServiceType() {
Optional<ConfigChangePluginService> configChangePluginServiceOptional = ConfigChangePluginManager
.getInstance().findPluginServiceImpl("test1");
Assert.assertTrue(configChangePluginServiceOptional.isPresent());
configChangePluginServiceOptional = ConfigChangePluginManager.getInstance().findPluginServiceImpl("test2");
Assert.assertTrue(configChangePluginServiceOptional.isPresent());
configChangePluginServiceOptional = ConfigChangePluginManager.getInstance().findPluginServiceImpl("test3");
Assert.assertFalse(configChangePluginServiceOptional.isPresent());
}
}

View File

@ -36,6 +36,7 @@
<module>datasource</module>
<module>environment</module>
<module>control</module>
<module>config</module>
</modules>
<packaging>pom</packaging>

View File

@ -734,6 +734,11 @@
<artifactId>nacos-encryption-plugin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba.nacos</groupId>
<artifactId>nacos-config-plugin</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>nacos-control-plugin</artifactId>