From 739c947af6b078e2b247831dc137d2c86e3cc91d Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Sun, 14 Jul 2024 15:51:26 +0800 Subject: [PATCH 01/34] feature: support raft config center(server) --- .gitignore | 4 + .../seata/common/ConfigurationKeys.java | 23 + .../org/apache/seata/common/Constants.java | 25 + .../apache/seata/common/DefaultValues.java | 4 + .../common/metadata/MetadataResponse.java | 11 +- config/pom.xml | 1 + config/seata-config-all/pom.xml | 5 + config/seata-config-core/pom.xml | 6 + .../org/apache/seata/config/ConfigType.java | 6 +- .../store/AbstractConfigStoreManager.java | 117 +++++ .../config/store/ConfigStoreManager.java | 54 +++ .../store/ConfigStoreManagerProvider.java | 28 ++ .../rocksdb/RocksDBConfigStoreManager.java | 331 ++++++++++++++ .../RocksDBConfigStoreManagerProvider.java | 30 ++ .../config/store/rocksdb/RocksDBFactory.java | 92 ++++ .../config/store/rocksdb/RocksDBOptions.java | 69 +++ ...ta.config.store.ConfigStoreManagerProvider | 1 + .../config/store/rocksdb/RocksDBTest.java | 103 +++++ config/seata-config-raft/pom.xml | 40 ++ .../config/raft/RaftConfigurationClient.java | 98 ++++ .../raft/RaftConfigurationProvider.java | 38 ++ .../config/raft/RaftConfigurationServer.java | 182 ++++++++ ....apache.seata.config.ConfigurationProvider | 1 + .../raft/RaftConfigurationServerTest.java | 29 ++ ....apache.seata.config.ConfigurationProvider | 1 + .../SeataCoreEnvironmentPostProcessor.java | 34 +- .../boot/autoconfigure/StarterConstants.java | 2 +- .../config/ConfigStoreProperties.java | 71 +++ .../config/test/StorePropertiesTest.java | 21 + .../seata/server/ServerApplication.java | 4 + .../server/cluster/raft/RaftConfigServer.java | 120 +++++ .../cluster/raft/RaftConfigServerManager.java | 235 ++++++++++ .../cluster/raft/RaftConfigStateMachine.java | 431 ++++++++++++++++++ .../cluster/raft/RaftServerManager.java | 4 +- .../config/AbstractRaftConfigMsgExecute.java | 28 ++ .../config/ConfigOperationExecute.java | 65 +++ .../execute/config/ConfigOperationType.java | 30 ++ .../ConfigOperationRequestProcessor.java | 67 +++ .../PutNodeInfoRequestProcessor.java | 25 +- .../request/ConfigOperationRequest.java | 98 ++++ .../response/ConfigOperationResponse.java | 80 ++++ .../cluster/raft/snapshot/RaftSnapshot.java | 7 +- .../snapshot/config/ConfigSnapshotFile.java | 107 +++++ .../ConfigLeaderMetadataSnapshotFile.java | 89 ++++ .../metadata/LeaderMetadataSnapshotFile.java | 1 + .../sync/msg/RaftConfigOperationSyncMsg.java | 45 ++ .../raft/sync/msg/RaftSyncMsgType.java | 4 +- .../raft/sync/msg/closure/ConfigClosure.java | 63 +++ .../raft/sync/msg/dto/ConfigOperationDTO.java | 98 ++++ .../cluster/raft/util/RaftConfigTaskUtil.java | 78 ++++ .../server/controller/ClusterController.java | 140 +++++- .../listener/SeataPropertiesLoader.java | 3 + 52 files changed, 3202 insertions(+), 47 deletions(-) create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java create mode 100644 config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider create mode 100644 config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java create mode 100644 config/seata-config-raft/pom.xml create mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java create mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java create mode 100644 config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java create mode 100644 config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider create mode 100644 config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java create mode 100644 config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java diff --git a/.gitignore b/.gitignore index 0e9cc23b50f..755b1b3ed71 100644 --- a/.gitignore +++ b/.gitignore @@ -46,6 +46,10 @@ dependency-reduced-pom.xml /distribution/sessionStore/ /distribution/*/sessionStore/ /file_store/ +/configStore/ +/config/configStore/ +/distribution/configStore/ +/distribution/*/configStore/ # system ignore .DS_Store diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index e1c0f11f395..db7e121ed91 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -1011,4 +1011,27 @@ public interface ConfigurationKeys { * The constant ROCKET_MQ_MSG_TIMEOUT */ String ROCKET_MQ_MSG_TIMEOUT = SERVER_PREFIX + "rocketmqMsgTimeout"; + + String CONFIG_STORE_PREFIX = FILE_ROOT_PREFIX_CONFIG + "db" + FILE_CONFIG_SPLIT_CHAR; + + /** + * The constant CONFIG_STORE_TYPE + */ + String CONFIG_STORE_TYPE = CONFIG_STORE_PREFIX + "type"; + + /** + * The constant CONFIG_STORE_DIR + */ + String CONFIG_STORE_DIR = CONFIG_STORE_PREFIX + "dir"; + + /** + * The constant CONFIG_STORE_DESTROY_ON_SHUTDOWN + */ + String CONFIG_STORE_DESTROY_ON_SHUTDOWN = CONFIG_STORE_PREFIX + "destroyOnShutdown"; + + /** + * The constant CONFIG_STORE_GROUP + */ + String CONFIG_STORE_GROUP = CONFIG_STORE_PREFIX + "group"; + } diff --git a/common/src/main/java/org/apache/seata/common/Constants.java b/common/src/main/java/org/apache/seata/common/Constants.java index 484bebd0e45..38d1ca87a40 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -219,4 +219,29 @@ public interface Constants { * The constant JACKSON_JSON_TEXT_PREFIX */ String JACKSON_JSON_TEXT_PREFIX = "{\"@class\":"; + + /** + * The constant APPLICATION_TYPE_KEY + */ + String APPLICATION_TYPE_KEY = "application.type"; + + /** + * The constant APPLICATION_TYPE_SERVER + */ + String APPLICATION_TYPE_SERVER = "server"; + + /** + * The constant APPLICATION_TYPE_CLIENT + */ + String APPLICATION_TYPE_CLIENT = "client"; + + /** + * The constant DEFAULT_DB_CONFIG_KEY in raft configuration + */ + String DEFAULT_STORE_GROUP = "SEATA_GROUP"; + + /** + * The constant RAFT_CONFIG_GROUP + */ + String RAFT_CONFIG_GROUP = "config"; } diff --git a/common/src/main/java/org/apache/seata/common/DefaultValues.java b/common/src/main/java/org/apache/seata/common/DefaultValues.java index 0c2dd0f7b89..40a1d494bed 100644 --- a/common/src/main/java/org/apache/seata/common/DefaultValues.java +++ b/common/src/main/java/org/apache/seata/common/DefaultValues.java @@ -314,4 +314,8 @@ public interface DefaultValues { String DRUID_LOCATION = "lib/sqlparser/druid.jar"; int DEFAULT_ROCKET_MQ_MSG_TIMEOUT = 60 * 1000; + + String DEFAULT_DB_STORE_FILE_DIR = "configStore"; + + String DEFAULT_DB_TYPE = "rocksdb"; } diff --git a/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java b/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java index 609402947fb..0432cc21613 100644 --- a/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java +++ b/common/src/main/java/org/apache/seata/common/metadata/MetadataResponse.java @@ -25,6 +25,8 @@ public class MetadataResponse { String storeMode; + String configMode; + long term; public List getNodes() { @@ -50,5 +52,12 @@ public long getTerm() { public void setTerm(long term) { this.term = term; } - + + public String getConfigMode() { + return configMode; + } + + public void setConfigMode(String configMode) { + this.configMode = configMode; + } } diff --git a/config/pom.xml b/config/pom.xml index 3ed784fb152..a0d76aa6e9c 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -41,5 +41,6 @@ seata-config-etcd3 seata-config-consul seata-config-spring-cloud + seata-config-raft diff --git a/config/seata-config-all/pom.xml b/config/seata-config-all/pom.xml index 0eceb6a2e82..1818bffcd3c 100644 --- a/config/seata-config-all/pom.xml +++ b/config/seata-config-all/pom.xml @@ -60,6 +60,11 @@ seata-config-spring-cloud ${project.version} + + ${project.groupId} + seata-config-raft + ${project.version} + diff --git a/config/seata-config-core/pom.xml b/config/seata-config-core/pom.xml index 896a7070fe3..dd622265dc1 100644 --- a/config/seata-config-core/pom.xml +++ b/config/seata-config-core/pom.xml @@ -43,6 +43,12 @@ org.yaml snakeyaml + + org.rocksdb + rocksdbjni + 8.8.1 + + diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java index 869d2d19c40..dc2a7f01b37 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigType.java @@ -52,7 +52,11 @@ public enum ConfigType { /** * Custom config type */ - Custom; + Custom, + /** + * Raft config type + */ + Raft; /** * Gets type. diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java new file mode 100644 index 00000000000..bac436b0e3e --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java @@ -0,0 +1,117 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store; + +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.ConfigType; +import org.apache.seata.config.processor.ConfigDataType; +import org.apache.seata.config.processor.ConfigProcessor; +import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Properties; + +/** + * The type Abstract config store manager. + * + */ +public abstract class AbstractConfigStoreManager implements ConfigStoreManager { + private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigStoreManager.class); + @Override + public String get(String group, String key) { + return null; + } + + @Override + public Map getAll(String group) { + return new HashMap<>(); + } + + @Override + public Boolean put(String group, String key, Object value) { + return Boolean.FALSE; + } + + @Override + public Boolean delete(String group, String key) { + return Boolean.FALSE; + } + + @Override + public Boolean putAll(String group, Map configMap) { + return Boolean.FALSE; + } + + @Override + public Boolean deleteAll(String group) { + return Boolean.FALSE; + } + + @Override + public Boolean isEmpty(String group) { + return Boolean.TRUE; + } + + @Override + public Map getConfigMap() { + return new HashMap<>(); + } + + @Override + public Boolean putConfigMap(Map configMap) { + return Boolean.FALSE; + } + + @Override + public Boolean clearData() { + return Boolean.FALSE; + } + + @Override + public void destroy() {} + + public abstract void shutdown(); + + protected static String convertConfig2Str(Map configs) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : configs.entrySet()) { + sb.append(entry.getKey()).append("=").append(entry.getValue().toString()).append("\n"); + } + return sb.toString(); + } + + protected static Map convertConfigStr2Map(String configStr) { + if (StringUtils.isEmpty(configStr)) { + return new HashMap<>(); + } + Map configs = new HashMap<>(); + try { + Properties properties = ConfigProcessor.processConfig(configStr, ConfigDataType.properties.name()); + properties.forEach((k, v) -> configs.put(k.toString(), v)); + return configs; + } catch (IOException e) { + LOGGER.error("convert config properties error", e); + return new HashMap<>(); + } + } + +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java new file mode 100644 index 00000000000..8b8d39c6412 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java @@ -0,0 +1,54 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store; + +import org.apache.seata.config.ConfigurationChangeListener; + +import java.util.Map; + +/** + * The interface Local config store manager. + * + */ +public interface ConfigStoreManager { + String get(String group, String key); + + Map getAll(String group); + + Boolean put(String group, String key, Object value); + + Boolean delete(String group, String key); + + Boolean putAll(String group, Map configMap); + + Boolean deleteAll(String group); + + Boolean isEmpty(String group); + + Map getConfigMap(); + + Boolean putConfigMap(Map configMap); + + Boolean clearData(); + void destroy(); + + default void addConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; + + default void removeConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; + + +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java new file mode 100644 index 00000000000..15aededefd5 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerProvider.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store; + +/** + * the interface configStoreManager provider + */ +public interface ConfigStoreManagerProvider { + /** + * provide a AbstractConfigStoreManager implementation instance + * @return ConfigStoreManager + */ + ConfigStoreManager provide(); +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java new file mode 100644 index 00000000000..10d64c8b778 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -0,0 +1,331 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store.rocksdb; + + +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.*; +import org.apache.seata.config.store.AbstractConfigStoreManager; +import org.rocksdb.*; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.locks.ReentrantReadWriteLock; + + +import static org.apache.seata.common.ConfigurationKeys.*; +import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; + + +/** + * The RocksDB config store manager + * + */ +public class RocksDBConfigStoreManager extends AbstractConfigStoreManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBConfigStoreManager.class); + private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + private static final String DB_PATH = RocksDBOptions.getDBPath(); + private static final String DEFAULT_GROUP = DEFAULT_STORE_GROUP; + private static String CURRENT_GROUP; + private static final String NAME_KEY = "name"; + private static final String FILE_TYPE = "file"; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final Options DB_OPTIONS = RocksDBOptions.getOptions(); + private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap> CONFIG_LISTENERS_MAP = new ConcurrentHashMap<>( + MAP_INITIAL_CAPACITY); + + //====================================NON COMMON FILED=================================== + private static volatile RocksDBConfigStoreManager instance; + private final RocksDB rocksdb; + private static final List prefixList = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, + STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX); + + public static RocksDBConfigStoreManager getInstance() { + if (instance == null) { + synchronized (RocksDBConfigStoreManager.class) { + if (instance == null) { + instance = new RocksDBConfigStoreManager(); + } + } + } + return instance; + } + + public RocksDBConfigStoreManager() { + super(); + this.rocksdb = RocksDBFactory.getInstance(DB_PATH, DB_OPTIONS); + CURRENT_GROUP = FILE_CONFIG.getConfig(CONFIG_STORE_GROUP, DEFAULT_GROUP); + maybeNeedLoadOriginConfig(); + LOGGER.info("RocksDBConfigStoreManager initialized successfully"); + } + + /** + * load origin config if first startup + */ + private void maybeNeedLoadOriginConfig() { + if (isEmpty(CURRENT_GROUP)){ + Map configs = new HashMap<>(); + Map seataConfigs = new HashMap<>(); + String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, + ConfigurationKeys.FILE_ROOT_CONFIG, FILE_TYPE, NAME_KEY); + String name = FILE_CONFIG.getConfig(pathDataId); + // create FileConfiguration for read file.conf + Optional originFileInstance = Optional.ofNullable(new FileConfiguration(name)); + originFileInstance + .ifPresent(fileConfiguration -> configs.putAll(fileConfiguration.getFileConfig().getAllConfig())); + configs.forEach((k, v) -> { + if (v instanceof String) { + if (StringUtils.isEmpty((String)v)) { + return; + } + } + // filter all seata related configs + if (prefixList.stream().anyMatch(k::startsWith)) { + seataConfigs.put(k, v); + } + }); + putAll(CURRENT_GROUP, seataConfigs); + } + } + + + private Map getConfigMap(String group) throws RocksDBException{ + lock.readLock().lock(); + try { + group = StringUtils.isEmpty(group)? CURRENT_GROUP : group; + byte[] value = rocksdb.get(group.getBytes(DEFAULT_CHARSET)); + String configStr = value != null ? new String(value, DEFAULT_CHARSET) : null; + return convertConfigStr2Map(configStr); + }finally { + lock.readLock().unlock(); + } + } + + + @Override + public String get(String group, String key) { + lock.readLock().lock(); + try { + Map configMap = getConfigMap(group); + return configMap.get(key) != null ? configMap.get(key).toString() : null; + }catch (RocksDBException e) { + LOGGER.error("Failed to get value for key: " + key, e); + }finally { + lock.readLock().unlock(); + } + return null; + } + + @Override + public Map getAll(String group) { + try { + return getConfigMap(group); + }catch (RocksDBException e) { + LOGGER.error("Failed to get all configs", e); + } + return null; + } + + @Override + public Boolean put(String group, String key, Object value) { + lock.writeLock().lock(); + try { + Map configMap = getConfigMap(group); + configMap.put(key, value); + String configStr = convertConfig2Str(configMap); + rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + // LOGGER.info("put {} = {} in group: {}", key, value, group); + return true; + }catch (RocksDBException e){ + LOGGER.error("Failed to put value for key: " + key, e); + }finally { + lock.writeLock().unlock(); + } + return false; + } + + @Override + public Boolean delete(String group, String key) { + lock.writeLock().lock(); + try { + Map configMap = getConfigMap(group); + configMap.remove(key); + String configStr = convertConfig2Str(configMap); + rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + // LOGGER.info("delete {} in group: {}", key, group); + return true; + }catch (RocksDBException e){ + LOGGER.error("Failed to delete value for key: " + key, e); + }finally { + lock.writeLock().unlock(); + } + return false; + } + + @Override + public Boolean putAll(String group, Map configMap) { + lock.writeLock().lock(); + try{ + String configStr = convertConfig2Str(configMap); + rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + return true; + }catch (RocksDBException e){ + LOGGER.error("Failed to put all configs", e); + }finally { + lock.writeLock().unlock(); + } + return false; + } + + @Override + public Boolean deleteAll(String group) { + lock.writeLock().lock(); + try { + rocksdb.delete(group.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(group, new ConfigurationChangeEvent(group, null)); + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to clear all configs", e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + @Override + public Map getConfigMap() { + lock.readLock().lock(); + HashMap configMap = new HashMap<>(); + try (RocksIterator iterator = rocksdb.newIterator()) { + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + String key = new String(iterator.key(), DEFAULT_CHARSET); + String value = new String(iterator.value(), DEFAULT_CHARSET); + configMap.put(key, value); + } + return configMap; + } finally { + lock.readLock().unlock(); + } + } + + @Override + public Boolean putConfigMap(Map configMap) { + lock.writeLock().lock(); + try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { + for (Map.Entry entry : configMap.entrySet()) { + batch.put(entry.getKey().getBytes(DEFAULT_CHARSET), entry.getValue().toString().getBytes(DEFAULT_CHARSET)); + } + rocksdb.write(writeOptions, batch); + for (Map.Entry entry : configMap.entrySet()) { + String group = entry.getKey(); + String configStr = (String) entry.getValue(); + notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + } + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to put values for multiple keys", e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + @Override + public Boolean clearData() { + lock.writeLock().lock(); + try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions(); + RocksIterator iterator = rocksdb.newIterator()) { + Set groupSet = new HashSet<>(); + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + batch.delete(iterator.key()); + groupSet.add(new String(iterator.key(), DEFAULT_CHARSET)); + } + rocksdb.write(writeOptions, batch); + for (String group : groupSet) { + notifyConfigChange(group, new ConfigurationChangeEvent(group, null)); + } + return true; + } catch (RocksDBException e) { + LOGGER.error("Failed to clear the database", e); + } finally { + lock.writeLock().unlock(); + } + return false; + } + + @Override + public Boolean isEmpty(String group) { + return CollectionUtils.isEmpty(getAll(group)); + } + + // todo server关闭时需要被调用 + @Override + public void shutdown() { + RocksDBFactory.close(); + if (RocksDBOptions.getDBDestroyOnShutdown()) { + destroy(); + } + LOGGER.info("RocksDBConfigStoreManager has shutdown"); + } + + @Override + public void destroy() { + RocksDBFactory.destroy(DB_PATH, DB_OPTIONS); + LOGGER.info("DB destroyed, the db path is: {}.", DB_PATH); + } + + + @Override + public void addConfigListener(String group, String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, k -> ConcurrentHashMap.newKeySet()) + .add(listener); + } + + @Override + public void removeConfigListener(String group, String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configChangeListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + configChangeListeners.remove(listener); + } + } + + + private void notifyConfigChange(String dataId, ConfigurationChangeEvent event) { + Set configChangeListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + configChangeListeners.forEach(listener -> listener.onChangeEvent(event)); + } + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java new file mode 100644 index 00000000000..b9b32a05992 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManagerProvider.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store.rocksdb; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerProvider; + + +@LoadLevel(name = "Rocksdb", order = 1) +public class RocksDBConfigStoreManagerProvider implements ConfigStoreManagerProvider { + @Override + public ConfigStoreManager provide() { + return RocksDBConfigStoreManager.getInstance(); + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java new file mode 100644 index 00000000000..c670776f44e --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java @@ -0,0 +1,92 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store.rocksdb; + +import org.rocksdb.Options; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; + +/** + * The RocksDB Factory + * + */ +public class RocksDBFactory { + private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBFactory.class); + + private static volatile RocksDB instance = null; + + static { + RocksDB.loadLibrary(); + } + + public static RocksDB getInstance(String dbPath, Options options) { + if (instance == null) { + synchronized (RocksDBFactory.class) { + if (instance == null) { + instance = build(dbPath,options); + } + } + } + return instance; + } + + private static RocksDB build(String dbPath, Options options) { + try { + checkPath(dbPath); + return RocksDB.open(options, dbPath); + }catch (RocksDBException | IOException e){ + LOGGER.error("RocksDB open error: {}", e.getMessage(), e); + return null; + } + } + + + public static synchronized void close() { + if (instance != null) { + instance.close(); + instance = null; + } + } + + public static synchronized void destroy(String dbPath, Options options) { + close(); + try { + RocksDB.destroyDB(dbPath, options); + }catch (RocksDBException e){ + LOGGER.error("RocksDB destroy error: {}",e.getMessage(),e); + } + } + + private static void checkPath(String dbPath) throws IOException { + File directory = new File(dbPath); + String message; + if (directory.exists()) { + if (!directory.isDirectory()) { + message = "File " + directory + " exists and is not a directory. Unable to create directory."; + throw new IOException(message); + } + } else if (!directory.mkdirs() && !directory.isDirectory()) { + message = "Unable to create directory " + directory; + throw new IOException(message); + } + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java new file mode 100644 index 00000000000..d6077aaefdc --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java @@ -0,0 +1,69 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store.rocksdb; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; +import org.rocksdb.Options; + +import static java.io.File.separator; +import static org.apache.seata.common.ConfigurationKeys.*; +import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + + +/** + * The RocksDB options builder + * + */ +public class RocksDBOptions { + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + + public static final String ROCKSDB_SUFFIX = "rocksdb"; + + private static volatile Options options = null; + public static Options getOptions() { + if (options == null){ + synchronized (RocksDBOptions.class){ + if (options == null){ + options = buildOptions(); + } + } + } + return options; + } + + public static String getDBPath() { + String dir = FILE_CONFIG.getConfig(CONFIG_STORE_DIR); + String group = FILE_CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + return String.join(separator, dir, group, ROCKSDB_SUFFIX); + } + + public static boolean getDBDestroyOnShutdown() { + return FILE_CONFIG.getBoolean(CONFIG_STORE_DESTROY_ON_SHUTDOWN, false); + } + + private static Options buildOptions() { + Options options = new Options(); + options.setCreateIfMissing(true); // 不存在则创建 + options.setKeepLogFileNum(1); // 只保留最新的一个日志文件 + options.setLogFileTimeToRoll(0); // 禁止基于时间的日志滚动 + options.setMaxLogFileSize(0); // 禁止基于大小的日志滚动 + return options; + } + +} diff --git a/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider b/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider new file mode 100644 index 00000000000..400274694d2 --- /dev/null +++ b/config/seata-config-core/src/main/resources/META-INF/services/org.apache.seata.config.store.ConfigStoreManagerProvider @@ -0,0 +1 @@ +org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManagerProvider \ No newline at end of file diff --git a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java new file mode 100644 index 00000000000..7d1d2a52f14 --- /dev/null +++ b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java @@ -0,0 +1,103 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store.rocksdb; + +import org.apache.seata.config.ConfigurationFactory; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.rocksdb.RocksDB; +import org.rocksdb.RocksDBException; + +import java.util.HashMap; + + +import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; + + +class RocksDBTest { + private static RocksDBConfigStoreManager configStoreManager; + + private static String group = DEFAULT_STORE_GROUP; + @BeforeAll + static void setUp() { + configStoreManager = RocksDBConfigStoreManager.getInstance(); + } + + @AfterAll + static void tearDown() { + if (configStoreManager != null) { + configStoreManager.destroy(); + } + } + + @Test + void getConfigStoreManagerTest() { + Assertions.assertNotNull(configStoreManager); + } + + + @Test + void crudTest() { + configStoreManager.deleteAll(group); + String key = "aaa"; + String value = "bbb"; + String updateValue = "ccc"; + Assertions.assertTrue(configStoreManager.put(group, key, value)); + Assertions.assertEquals(value, configStoreManager.get(group, key)); + Assertions.assertTrue(configStoreManager.put(group, key, updateValue)); + Assertions.assertEquals(updateValue, configStoreManager.get(group, key)); + Assertions.assertTrue(configStoreManager.delete(group, key)); + Assertions.assertNull(configStoreManager.get(group, key)); + + } + + @Test + void uploadConfigTest() { + configStoreManager.deleteAll(group); + HashMap uploadConfigs = new HashMap<>(); + uploadConfigs.put("aaa","111"); + uploadConfigs.put("bbb","222"); + Assertions.assertTrue(configStoreManager.putAll(group, uploadConfigs)); + Assertions.assertEquals(uploadConfigs, configStoreManager.getAll(group)); + configStoreManager.deleteAll(group); + Assertions.assertTrue(configStoreManager.isEmpty(group)); + } + + + @Test + void multiGroupTest() { + configStoreManager.deleteAll(group); + String group1 = "group1"; + String group2 = "group2"; + String key = "aaa"; + String value1 = "aaa"; + String value2 = "bbb"; + // put and get + Assertions.assertTrue(configStoreManager.put(group1, key, value1)); + Assertions.assertTrue(configStoreManager.put(group2, key, value2)); + Assertions.assertEquals(value1, configStoreManager.get(group1, key)); + Assertions.assertEquals(value2, configStoreManager.get(group2, key)); + + // delete + Assertions.assertTrue(configStoreManager.delete(group1, key)); + Assertions.assertTrue(configStoreManager.delete(group2, key)); + Assertions.assertNull(configStoreManager.get(group1, key)); + Assertions.assertNull(configStoreManager.get(group2, key)); + } +} diff --git a/config/seata-config-raft/pom.xml b/config/seata-config-raft/pom.xml new file mode 100644 index 00000000000..bf3faca1528 --- /dev/null +++ b/config/seata-config-raft/pom.xml @@ -0,0 +1,40 @@ + + + + + org.apache.seata + seata-config + ${revision} + + 4.0.0 + seata-config-raft + seata-config-raft ${project.version} + config-raft for Seata built with Maven + + + + org.apache.seata + seata-config-core + ${project.version} + + + + \ No newline at end of file diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java new file mode 100644 index 00000000000..dafead98208 --- /dev/null +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.raft; + +import org.apache.seata.config.AbstractConfiguration; +import org.apache.seata.config.ConfigurationChangeListener; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Set; + +/** + * The type Raft configuration of client. + * + */ +public class RaftConfigurationClient extends AbstractConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigurationClient.class); + private static volatile RaftConfigurationClient instance; + + public static RaftConfigurationClient getInstance() { + if (instance == null) { + synchronized (RaftConfigurationClient.class) { + if (instance == null) { + instance = new RaftConfigurationClient(); + } + } + } + return instance; + } + + private RaftConfigurationClient() { + initClientConfig(); + } + + private static void initClientConfig() { + // acquire configs from server + // 0.发送/cluster获取raft集群 + // 1.向raft集群发送getAll请求 + // 2.等待Raft日志提交,leader从rocksdb中读取全部配置返回(保证一致性) + // 3.加载到seataConfig + // 4.定期轮询配置变更 + } + + @Override + public String getTypeName() { + return null; + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + return false; + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + return null; + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + return false; + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + return false; + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + + } + + @Override + public Set getConfigListeners(String dataId) { + return null; + } + +} diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java new file mode 100644 index 00000000000..beaa2f5adf7 --- /dev/null +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.raft; + +import org.apache.seata.common.loader.LoadLevel; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationProvider; + +import static org.apache.seata.common.Constants.*; + +@LoadLevel(name = "Raft", order = 1) +public class RaftConfigurationProvider implements ConfigurationProvider { + @Override + public Configuration provide() { + String applicationType = System.getProperty(APPLICATION_TYPE_KEY); + if (APPLICATION_TYPE_SERVER.equals(applicationType)){ + return RaftConfigurationServer.getInstance(); + }else if (APPLICATION_TYPE_CLIENT.equals(applicationType)){ + return RaftConfigurationClient.getInstance(); + }else{ + throw new IllegalArgumentException(String.format("Unknown application type: %s, it must be server or client", applicationType)); + } + } +} diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java new file mode 100644 index 00000000000..7238c9a4c5f --- /dev/null +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java @@ -0,0 +1,182 @@ +package org.apache.seata.config.raft; + +import org.apache.seata.common.exception.NotSupportYetException; +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.*; +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerProvider; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static org.apache.seata.common.ConfigurationKeys.*; +import static org.apache.seata.common.Constants.*; +import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; + + +public class RaftConfigurationServer extends AbstractConfiguration { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigurationServer.class); + private static volatile RaftConfigurationServer instance; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static ConfigStoreManager configStoreManager; + private static String CURRENT_GROUP; // current group of configuration + private static final String CONFIG_TYPE = "raft"; + private static volatile Properties seataConfig = new Properties(); + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap> CONFIG_LISTENERS_MAP + = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + + private static void initServerConfig() { + String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); + configStoreManager = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); + CURRENT_GROUP = FILE_CONFIG.getConfig(CONFIG_STORE_GROUP, DEFAULT_STORE_GROUP); + // load config from store + Map configMap = configStoreManager.getAll(CURRENT_GROUP); + seataConfig.putAll(configMap); + // build listener + ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_GROUP, null); + configStoreManager.addConfigListener(CURRENT_GROUP, CURRENT_GROUP, storeListener); + } + + + public static RaftConfigurationServer getInstance() { + if (instance == null) { + synchronized (RaftConfigurationServer.class) { + if (instance == null) { + instance = new RaftConfigurationServer(); + } + } + } + return instance; + } + + private RaftConfigurationServer() { + initServerConfig(); + } + + @Override + public String getTypeName() { + return CONFIG_TYPE; + } + + @Override + public boolean putConfig(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support operation putConfig"); + } + + @Override + public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { + String value = seataConfig.getProperty(dataId); + if (value == null) { + value = configStoreManager.get(CURRENT_GROUP, dataId); + } + return value == null ? defaultValue : value; + } + + @Override + public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { + throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); + } + + @Override + public boolean removeConfig(String dataId, long timeoutMills) { + throw new NotSupportYetException("not support operation removeConfig"); + } + + @Override + public void addConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + ConfigStoreListener storeListener = new ConfigStoreListener(dataId, listener); + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) + .put(listener, storeListener); + configStoreManager.addConfigListener(CURRENT_GROUP, dataId, storeListener); + } + + @Override + public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configChangeListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + for (ConfigurationChangeListener entry : configChangeListeners) { + if (listener.equals(entry)) { + ConfigStoreListener storeListener = null; + Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (configListeners != null) { + storeListener = configListeners.get(listener); + configListeners.remove(entry); + } + if (storeListener != null) { + configStoreManager.removeConfigListener(CURRENT_GROUP, dataId, storeListener); + } + break; + } + } + } + } + + @Override + public Set getConfigListeners(String dataId) { + ConcurrentMap configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configListeners)){ + return configListeners.keySet(); + } else { + return null; + } + } + + + /** + * the type config change listener for raft config store + */ + private static class ConfigStoreListener implements ConfigurationChangeListener { + private final String dataId; + private final ConfigurationChangeListener listener; + + public ConfigStoreListener(String dataId, ConfigurationChangeListener listener) { + this.dataId = dataId; + this.listener = listener; + } + + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + if (CURRENT_GROUP.equals(event.getDataId())) { + Properties seataConfigNew = new Properties(); + seataConfigNew.putAll(configStoreManager.getAll(CURRENT_GROUP)); + + //Get all the monitored dataids and judge whether it has been modified + for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { + String listenedDataId = entry.getKey(); + String propertyOld = seataConfig.getProperty(listenedDataId, ""); + String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); + if (!propertyOld.equals(propertyNew)) { + ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() + .setDataId(listenedDataId) + .setNewValue(propertyNew) + .setNamespace(CURRENT_GROUP) + .setChangeType(ConfigurationChangeType.MODIFY); + + ConcurrentMap configListeners = entry.getValue(); + for (ConfigurationChangeListener configListener : configListeners.keySet()) { + configListener.onProcessEvent(newEvent); + } + } + } + seataConfig = seataConfigNew; + return; + } + // todo + // 如果不是当前分组的配置变更,则通知相应client端,配置变更了 + // Compatible with old writing + listener.onProcessEvent(event); + } + } +} diff --git a/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider b/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider new file mode 100644 index 00000000000..4d6771d0219 --- /dev/null +++ b/config/seata-config-raft/src/main/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +org.apache.seata.config.raft.RaftConfigurationProvider \ No newline at end of file diff --git a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java new file mode 100644 index 00000000000..fb69b38e1ff --- /dev/null +++ b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java @@ -0,0 +1,29 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.raft; + +import org.junit.jupiter.api.Test; + +/** + * The type Raft configuration of server test + * + */ +public class RaftConfigurationServerTest { + + + +} diff --git a/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider b/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider new file mode 100644 index 00000000000..4d6771d0219 --- /dev/null +++ b/config/seata-config-raft/src/test/resources/META-INF/services/org.apache.seata.config.ConfigurationProvider @@ -0,0 +1 @@ +org.apache.seata.config.raft.RaftConfigurationProvider \ No newline at end of file diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java index df9478809d5..aedaef11878 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java @@ -21,14 +21,7 @@ import org.apache.seata.spring.boot.autoconfigure.properties.ShutdownProperties; import org.apache.seata.spring.boot.autoconfigure.properties.ThreadFactoryProperties; import org.apache.seata.spring.boot.autoconfigure.properties.TransportProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigApolloProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigConsulProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigCustomProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigEtcd3Properties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigFileProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigNacosProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigProperties; -import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigZooKeeperProperties; +import org.apache.seata.spring.boot.autoconfigure.properties.config.*; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryConsulProperties; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryCustomProperties; import org.apache.seata.spring.boot.autoconfigure.properties.registry.RegistryEtcd3Properties; @@ -44,29 +37,7 @@ import org.springframework.core.Ordered; import org.springframework.core.env.ConfigurableEnvironment; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_APOLLO_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_CONSUL_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_CUSTOM_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_ETCD3_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_FILE_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_NACOS_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_ZK_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.LOG_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.PROPERTY_BEAN_MAP; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_CONSUL_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_CUSTOM_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_ETCD3_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_EUREKA_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_NACOS_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_RAFT_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_REDIS_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_SOFA_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_ZK_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.SHUTDOWN_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.THREAD_FACTORY_PREFIX; -import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.TRANSPORT_PREFIX; +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.*; public class SeataCoreEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered { @@ -95,6 +66,7 @@ public static void init() { PROPERTY_BEAN_MAP.put(CONFIG_APOLLO_PREFIX, ConfigApolloProperties.class); PROPERTY_BEAN_MAP.put(CONFIG_ETCD3_PREFIX, ConfigEtcd3Properties.class); PROPERTY_BEAN_MAP.put(CONFIG_CUSTOM_PREFIX, ConfigCustomProperties.class); + PROPERTY_BEAN_MAP.put(CONFIG_STORE_PREFIX, ConfigStoreProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_CONSUL_PREFIX, RegistryConsulProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_ETCD3_PREFIX, RegistryEtcd3Properties.class); diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java index f5fe4fe6721..63dabed0d26 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java @@ -65,7 +65,7 @@ public interface StarterConstants { String CONFIG_ZK_PREFIX = CONFIG_PREFIX + ".zk"; String CONFIG_FILE_PREFIX = CONFIG_PREFIX + ".file"; String CONFIG_CUSTOM_PREFIX = CONFIG_PREFIX + ".custom"; - + String CONFIG_STORE_PREFIX = CONFIG_PREFIX + ".db"; String SERVER_PREFIX = SEATA_PREFIX + ".server"; String SERVER_UNDO_PREFIX = SERVER_PREFIX + ".undo"; diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java new file mode 100644 index 00000000000..b68a43d8c3a --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java @@ -0,0 +1,71 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.spring.boot.autoconfigure.properties.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_STORE_PREFIX; + + +@Component +@ConfigurationProperties(prefix = CONFIG_STORE_PREFIX) +public class ConfigStoreProperties { + /** + * rocksdb, (leveldb, caffeine) + */ + private String type = "rocksdb"; + private String dir = "configStore"; + private boolean destroyOnShutdown = false; + private String group = "SEATA_GROUP"; + + public String getType() { + return type; + } + + public ConfigStoreProperties setType(String type) { + this.type = type; + return this; + } + + public String getDir() { + return dir; + } + + public ConfigStoreProperties setDir(String dir) { + this.dir = dir; + return this; + } + + public boolean isDestroyOnShutdown() { + return destroyOnShutdown; + } + + public ConfigStoreProperties setDestroyOnShutdown(boolean destroyOnShutdown) { + this.destroyOnShutdown = destroyOnShutdown; + return this; + } + + public String getGroup() { + return group; + } + + public ConfigStoreProperties setGroup(String group) { + this.group = group; + return this; + } +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java new file mode 100644 index 00000000000..900dbefc2e7 --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.spring.boot.autoconfigure.properties.config.test; + + +public class StorePropertiesTest { +} diff --git a/server/src/main/java/org/apache/seata/server/ServerApplication.java b/server/src/main/java/org/apache/seata/server/ServerApplication.java index 952187137e3..9c5e1ccf86b 100644 --- a/server/src/main/java/org/apache/seata/server/ServerApplication.java +++ b/server/src/main/java/org/apache/seata/server/ServerApplication.java @@ -21,11 +21,15 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import static org.apache.seata.common.Constants.APPLICATION_TYPE_KEY; +import static org.apache.seata.common.Constants.APPLICATION_TYPE_SERVER; + /** */ @SpringBootApplication(scanBasePackages = {"org.apache.seata"}) public class ServerApplication { public static void main(String[] args) throws IOException { + System.setProperty(APPLICATION_TYPE_KEY, APPLICATION_TYPE_SERVER); // run the spring-boot application SpringApplication.run(ServerApplication.class, args); } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java new file mode 100644 index 00000000000..b90e02a57ca --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServer.java @@ -0,0 +1,120 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft; + + +import com.alipay.sofa.jraft.Node; +import com.alipay.sofa.jraft.RaftGroupService; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.codahale.metrics.Slf4jReporter; +import org.apache.commons.io.FileUtils; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.core.rpc.Disposable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.Closeable; +import java.io.File; +import java.io.IOException; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_ENABLED; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_REPORTER_INITIAL_DELAY; + +public class RaftConfigServer implements Disposable, Closeable { + private final Logger logger = LoggerFactory.getLogger(getClass()); + private final RaftConfigStateMachine raftStateMachine; + private final String groupId; + private final String groupPath; + private final NodeOptions nodeOptions; + private final PeerId serverId; + private final RpcServer rpcServer; + private RaftGroupService raftGroupService; + private Node node; + + public RaftConfigServer(final String dataPath, final String groupId, final PeerId serverId, final NodeOptions nodeOptions, final RpcServer rpcServer) + throws IOException { + this.groupId = groupId; + this.groupPath = dataPath + File.separator + groupId; + // Initialize the state machine + this.raftStateMachine = new RaftConfigStateMachine(groupId); + this.nodeOptions = nodeOptions; + this.serverId = serverId; + this.rpcServer = rpcServer; + } + + public void start() throws IOException { + // Initialization path + FileUtils.forceMkdir(new File(groupPath)); + // Set the state machine to startup parameters + nodeOptions.setFsm(this.raftStateMachine); + // Set the storage path + // Log, must + nodeOptions.setLogUri(groupPath + File.separator + "log"); + // Meta information, must + nodeOptions.setRaftMetaUri(groupPath + File.separator + "raft_meta"); + // Snapshot, optional, is generally recommended + nodeOptions.setSnapshotUri(groupPath + File.separator + "snapshot"); + boolean reporterEnabled = ConfigurationFactory.CURRENT_FILE_INSTANCE.getBoolean(SERVER_RAFT_REPORTER_ENABLED, false); + nodeOptions.setEnableMetrics(reporterEnabled); + // Initialize the raft Group service framework + this.raftGroupService = new RaftGroupService(groupId, serverId, nodeOptions, rpcServer, true); + this.node = this.raftGroupService.start(false); + RouteTable.getInstance().updateConfiguration(groupId, node.getOptions().getInitialConf()); + if (reporterEnabled) { + final Slf4jReporter reporter = Slf4jReporter.forRegistry(node.getNodeMetrics().getMetricRegistry()) + .outputTo(logger).convertRatesTo(TimeUnit.SECONDS) + .convertDurationsTo(TimeUnit.MILLISECONDS).build(); + reporter.start(ConfigurationFactory.CURRENT_FILE_INSTANCE.getInt(SERVER_RAFT_REPORTER_INITIAL_DELAY, 60), + TimeUnit.MINUTES); + } + } + + public Node getNode() { + return this.node; + } + + + public RaftConfigStateMachine getRaftStateMachine() { + return raftStateMachine; + } + + public PeerId getServerId() { + return serverId; + } + + @Override + public void close() { + destroy(); + } + + @Override + public void destroy() { + Optional.ofNullable(raftGroupService).ifPresent(r -> { + r.shutdown(); + try { + r.join(); + } catch (InterruptedException e) { + logger.warn("Interrupted when RaftServer destroying", e); + } + }); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java new file mode 100644 index 00000000000..6aad5c5e2e6 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java @@ -0,0 +1,235 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft; + +import com.alipay.remoting.serialization.SerializerManager; +import com.alipay.sofa.jraft.CliService; +import com.alipay.sofa.jraft.RaftServiceFactory; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.option.CliOptions; +import com.alipay.sofa.jraft.option.NodeOptions; +import com.alipay.sofa.jraft.option.RaftOptions; +import com.alipay.sofa.jraft.rpc.CliClientService; +import com.alipay.sofa.jraft.rpc.RaftRpcServerFactory; +import com.alipay.sofa.jraft.rpc.RpcServer; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.XID; +import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.ConfigType; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.store.rocksdb.RocksDBOptions; +import org.apache.seata.core.serializer.SerializerType; +import org.apache.seata.discovery.registry.FileRegistryServiceImpl; +import org.apache.seata.discovery.registry.MultiRegistryFactory; +import org.apache.seata.discovery.registry.RegistryService; +import org.apache.seata.server.cluster.raft.processor.ConfigOperationRequestProcessor; +import org.apache.seata.server.cluster.raft.processor.PutNodeInfoRequestProcessor; +import org.apache.seata.server.cluster.raft.serializer.JacksonBoltSerializer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +import static java.io.File.separator; +import static org.apache.seata.common.ConfigurationKeys.*; +import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_ELECTION_TIMEOUT_MS; +import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; +import static org.apache.seata.common.DefaultValues.*; + +/** + * The type to manager raft server of config center + */ +public class RaftConfigServerManager { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigServerManager.class); + private static final AtomicBoolean INIT = new AtomicBoolean(false); + private static final org.apache.seata.config.Configuration CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static RpcServer rpcServer; + private static RaftConfigServer raftServer; + private static volatile boolean RAFT_MODE; + private static String group = RAFT_CONFIG_GROUP; + + public static CliService getCliServiceInstance() { + return RaftConfigServerManager.SingletonHandler.CLI_SERVICE; + } + + public static CliClientService getCliClientServiceInstance() { + return RaftConfigServerManager.SingletonHandler.CLI_CLIENT_SERVICE; + } + + public static void init() { + if (INIT.compareAndSet(false, true)) { + String initConfStr = CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); + String configTypeName = CONFIG.getConfig(org.apache.seata.config.ConfigurationKeys.FILE_ROOT_CONFIG + + org.apache.seata.config.ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + org.apache.seata.config.ConfigurationKeys.FILE_ROOT_TYPE); + RAFT_MODE = ConfigType.Raft.name().equalsIgnoreCase(configTypeName); + if (!RAFT_MODE){ + return; + } + if (StringUtils.isBlank(initConfStr)) { + if (RAFT_MODE) { + throw new IllegalArgumentException( + "Raft config mode must config: " + ConfigurationKeys.SERVER_RAFT_SERVER_ADDR); + } + return; + } + final Configuration initConf = new Configuration(); + if (!initConf.parse(initConfStr)) { + throw new IllegalArgumentException("fail to parse initConf:" + initConfStr); + } + int port = Integer.parseInt(System.getProperty(SERVER_RAFT_PORT_CAMEL, "0")); + PeerId serverId = null; + // XID may be null when configuration center is not initialized. + String host = XID.getIpAddress() == null? NetUtil.getLocalIp() : XID.getIpAddress(); + if (port <= 0) { + // Highly available deployments require different nodes + for (PeerId peer : initConf.getPeers()) { + if (StringUtils.equals(peer.getIp(), host)) { + if (serverId != null) { + throw new IllegalArgumentException( + "server.raft.cluster has duplicate ip, For local debugging, use -Dserver.raftPort to specify the raft port"); + } + serverId = peer; + } + } + } else { + // Local debugging use + serverId = new PeerId(host, port); + } + final String dataPath = CONFIG.getConfig(CONFIG_STORE_DIR, DEFAULT_DB_STORE_FILE_DIR) + + separator + "raft" + separator + serverId.getPort(); + try { + // Here you have raft RPC and business RPC using the same RPC server, and you can usually do this + // separately + SerializerManager.addSerializer(SerializerType.JACKSON.getCode(), new JacksonBoltSerializer()); + rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()); + raftServer = new RaftConfigServer(dataPath, group, serverId, initNodeOptions(initConf), rpcServer); + } catch (IOException e) { + throw new IllegalArgumentException("fail init raft cluster:" + e.getMessage(), e); + } + } + } + public static void start() { + try { + if (raftServer != null) { + raftServer.start(); + } + } catch (IOException e) { + LOGGER.error("start seata server raft cluster error, group: {} ", group, e); + throw new RuntimeException(e); + } + LOGGER.info("started seata server raft cluster, group: {} ", group); + + if (rpcServer != null) { + rpcServer.registerProcessor(new PutNodeInfoRequestProcessor()); + rpcServer.registerProcessor(new ConfigOperationRequestProcessor()); + if (!rpcServer.init(null)) { + throw new RuntimeException("start raft node fail!"); + } + } + } + + // todo 需要调用 + public static void destroy() { + raftServer.close(); + LOGGER.info("closed seata server raft cluster, group: {} ", group); + Optional.ofNullable(rpcServer).ifPresent(RpcServer::shutdown); + raftServer = null; + rpcServer = null; + INIT.set(false); + } + + public static RaftConfigServer getRaftServer() { + return raftServer; + } + + public static RpcServer getRpcServer() { + return rpcServer; + } + + public static boolean isLeader() { + AtomicReference stateMachine = new AtomicReference<>(); + Optional.ofNullable(raftServer).ifPresent(raftConfigServer -> { + stateMachine.set(raftConfigServer.getRaftStateMachine()); + }); + RaftConfigStateMachine raftStateMachine = stateMachine.get(); + return raftStateMachine != null && raftStateMachine.isLeader(); + } + + public static PeerId getLeader() { + + RouteTable routeTable = RouteTable.getInstance(); + try { + routeTable.refreshLeader(getCliClientServiceInstance(), RAFT_CONFIG_GROUP , 1000); + return routeTable.selectLeader(RAFT_CONFIG_GROUP); + } catch (Exception e) { + LOGGER.error("there is an exception to getting the leader address: {}", e.getMessage(), e); + } + return null; + + } + private static RaftOptions initRaftOptions() { + RaftOptions raftOptions = new RaftOptions(); + raftOptions.setApplyBatch(CONFIG.getInt(SERVER_RAFT_APPLY_BATCH, raftOptions.getApplyBatch())); + raftOptions.setMaxAppendBufferSize( + CONFIG.getInt(SERVER_RAFT_MAX_APPEND_BUFFER_SIZE, raftOptions.getMaxAppendBufferSize())); + raftOptions.setDisruptorBufferSize( + CONFIG.getInt(SERVER_RAFT_DISRUPTOR_BUFFER_SIZE, raftOptions.getDisruptorBufferSize())); + raftOptions.setMaxReplicatorInflightMsgs( + CONFIG.getInt(SERVER_RAFT_MAX_REPLICATOR_INFLIGHT_MSGS, raftOptions.getMaxReplicatorInflightMsgs())); + raftOptions.setSync(CONFIG.getBoolean(SERVER_RAFT_SYNC, raftOptions.isSync())); + return raftOptions; + } + + private static NodeOptions initNodeOptions(Configuration initConf) { + NodeOptions nodeOptions = new NodeOptions(); + // enable the CLI service. + nodeOptions.setDisableCli(false); + // snapshot should be made every 600 seconds + int snapshotInterval = CONFIG.getInt(SERVER_RAFT_SNAPSHOT_INTERVAL, 60 * 10); + nodeOptions.setSnapshotIntervalSecs(snapshotInterval); + nodeOptions.setRaftOptions(initRaftOptions()); + // set the election timeout to 1 second + nodeOptions + .setElectionTimeoutMs(CONFIG.getInt(SERVER_RAFT_ELECTION_TIMEOUT_MS, DEFAULT_SERVER_RAFT_ELECTION_TIMEOUT_MS)); + // set up the initial cluster configuration + nodeOptions.setInitialConf(initConf); + return nodeOptions; + } + public static String getGroup() { + return group; + } + private static class SingletonHandler { + private static final CliService CLI_SERVICE = RaftServiceFactory.createAndInitCliService(new CliOptions()); + private static final CliClientService CLI_CLIENT_SERVICE = new CliClientServiceImpl(); + + static { + CLI_CLIENT_SERVICE.init(new CliOptions()); + } + + } + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java new file mode 100644 index 00000000000..eb209d7bdce --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java @@ -0,0 +1,431 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Iterator; +import com.alipay.sofa.jraft.RouteTable; +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.conf.Configuration; +import com.alipay.sofa.jraft.core.StateMachineAdapter; +import com.alipay.sofa.jraft.entity.LeaderChangeContext; +import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.rpc.InvokeContext; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.seata.common.XID; +import org.apache.seata.common.holder.ObjectHolder; +import org.apache.seata.common.metadata.ClusterRole; +import org.apache.seata.common.metadata.Node; +import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.NetUtil; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.core.serializer.SerializerType; +import org.apache.seata.server.cluster.listener.ClusterChangeEvent; +import org.apache.seata.server.cluster.raft.context.SeataClusterContext; +import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationExecute; +import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.processor.response.PutNodeMetadataResponse; +import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import org.apache.seata.server.cluster.raft.snapshot.config.ConfigSnapshotFile; +import org.apache.seata.server.cluster.raft.snapshot.metadata.ConfigLeaderMetadataSnapshotFile; +import org.apache.seata.server.cluster.raft.snapshot.metadata.LeaderMetadataSnapshotFile; +import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import org.apache.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; +import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType; +import org.apache.seata.server.cluster.raft.sync.msg.closure.ConfigClosure; +import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import org.apache.seata.server.cluster.raft.util.RaftConfigTaskUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.core.env.Environment; + +import java.nio.ByteBuffer; +import java.util.*; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; +import java.util.stream.Collectors; + +import static org.apache.seata.common.Constants.*; +import static org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMsgType.*; + +/** + * The type raft config state machine. + */ +public class RaftConfigStateMachine extends StateMachineAdapter { + private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigStateMachine.class); + + private final String group; + + private final List snapshotFiles = new ArrayList<>(); + + private static final Map> EXECUTES = new HashMap<>(); + + private volatile RaftClusterMetadata raftClusterMetadata = new RaftClusterMetadata(); + + private final Lock lock = new ReentrantLock(); + + private static final ScheduledThreadPoolExecutor RESYNC_METADATA_POOL = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("reSyncMetadataPool", 1, true)); + + /** + * Leader term + */ + private final AtomicLong leaderTerm = new AtomicLong(-1); + + /** + * current term + */ + private final AtomicLong currentTerm = new AtomicLong(-1); + + private final AtomicBoolean initSync = new AtomicBoolean(false); + + private ScheduledFuture scheduledFuture; + + public boolean isLeader() { + return this.leaderTerm.get() > 0; + } + + public RaftConfigStateMachine(String group) { + this.group = group; + + EXECUTES.put(REFRESH_CLUSTER_METADATA, syncMsg -> { + refreshClusterMetadata(syncMsg); + return null; + }); + registryStoreSnapshotFile(new ConfigLeaderMetadataSnapshotFile(group)); + registryStoreSnapshotFile(new ConfigSnapshotFile(group)); + EXECUTES.put(CONFIG_OPERATION, new ConfigOperationExecute()); + EXECUTES.put(REFRESH_CLUSTER_METADATA, syncMsg -> { + refreshClusterMetadata(syncMsg); + return null; + }); + this.scheduledFuture = + RESYNC_METADATA_POOL.scheduleAtFixedRate(() -> syncCurrentNodeInfo(group), 10, 10, TimeUnit.SECONDS); + } + + @Override + public void onApply(Iterator iterator) { + while (iterator.hasNext()) { + Closure done = iterator.done(); + if (done != null) { + // leader does not need to be serialized, just execute the task directly + if (done instanceof ConfigClosure) { + ConfigClosure configClosure = (ConfigClosure) done; + RaftBaseMsg msg = configClosure.getRaftBaseMsg(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("sync msg: {}", msg); + } + ConfigOperationResponse response = (ConfigOperationResponse) onExecuteRaft(msg); + configClosure.getResponse().setSuccess(response.isSuccess()); + configClosure.getResponse().setResult(response.getResult()); + configClosure.getResponse().setErrMsg(response.getErrMsg()); + configClosure.run(Status.OK()); + } else { + // If it's not a ConfigClosure, just run it with OK status + done.run(Status.OK()); + } + } else { + ByteBuffer byteBuffer = iterator.getData(); + // if data is empty, it is only a heartbeat event and can be ignored + if (byteBuffer != null && byteBuffer.hasRemaining()) { + RaftBaseMsg msg = (RaftBaseMsg) RaftSyncMessageSerializer.decode(byteBuffer.array()).getBody(); + // follower executes the corresponding task + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("sync msg: {}", msg); + } + onExecuteRaft(msg); + } + } + iterator.next(); + } + } + + @Override + public void onSnapshotSave(final SnapshotWriter writer, final Closure done) { + long current = System.currentTimeMillis(); + for (StoreSnapshotFile snapshotFile : snapshotFiles) { + Status status = snapshotFile.save(writer); + if (!status.isOk()) { + done.run(status); + return; + } + } + LOGGER.info("groupId: {}, onSnapshotSave cost: {} ms.", group, System.currentTimeMillis() - current); + done.run(Status.OK()); + } + + @Override + public boolean onSnapshotLoad(final SnapshotReader reader) { + if (isLeader()) { + if (LOGGER.isWarnEnabled()) { + LOGGER.warn("Leader is not supposed to load snapshot"); + } + return false; + } + long current = System.currentTimeMillis(); + for (StoreSnapshotFile snapshotFile : snapshotFiles) { + if (!snapshotFile.load(reader)) { + return false; + } + } + LOGGER.info("groupId: {}, onSnapshotLoad cost: {} ms.", group, System.currentTimeMillis() - current); + return true; + } + @Override + public void onLeaderStart(final long term) { + this.leaderTerm.set(term); + LOGGER.info("groupId: {}, onLeaderStart: term={}.", group, term); + this.currentTerm.set(term); + syncMetadata(); + } + + @Override + public void onLeaderStop(final Status status) { + this.leaderTerm.set(-1); + LOGGER.info("groupId: {}, onLeaderStop: status={}.", group, status); + } + + @Override + public void onStopFollowing(final LeaderChangeContext ctx) { + LOGGER.info("groupId: {}, onStopFollowing: {}.", group, ctx); + } + + @Override + public void onStartFollowing(final LeaderChangeContext ctx) { + LOGGER.info("groupId: {}, onStartFollowing: {}.", group, ctx); + this.currentTerm.set(ctx.getTerm()); + CompletableFuture.runAsync(() -> syncCurrentNodeInfo(ctx.getLeaderId()), RESYNC_METADATA_POOL); + } + + @Override + public void onConfigurationCommitted(Configuration conf) { + LOGGER.info("groupId: {}, onConfigurationCommitted: {}.", group, conf); + RouteTable.getInstance().updateConfiguration(group, conf); + if (isLeader()) { + lock.lock(); + try { + List newFollowers = conf.getPeers(); + Set newLearners = conf.getLearners(); + List currentFollowers = raftClusterMetadata.getFollowers(); + if (CollectionUtils.isNotEmpty(newFollowers)) { + raftClusterMetadata.setFollowers(currentFollowers.stream() + .filter(node -> contains(node, newFollowers)).collect(Collectors.toList())); + } + if (CollectionUtils.isNotEmpty(newLearners)) { + raftClusterMetadata.setLearner(raftClusterMetadata.getLearner().stream() + .filter(node -> contains(node, newLearners)).collect(Collectors.toList())); + } + syncMetadata(); + } finally { + lock.unlock(); + } + } + } + + private boolean contains(Node node, Collection list) { + if (node.getInternal() == null) { + return true; + } + PeerId nodePeer = new PeerId(node.getInternal().getHost(), node.getInternal().getPort()); + return list.contains(nodePeer); + } + + public void syncMetadata() { + if (isLeader()) { + SeataClusterContext.bindGroup(group); + try { + RaftClusterMetadataMsg raftClusterMetadataMsg = + new RaftClusterMetadataMsg(changeOrInitRaftClusterMetadata()); + RaftConfigTaskUtil.createTask(status -> refreshClusterMetadata(raftClusterMetadataMsg), + raftClusterMetadataMsg, null); + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } finally { + SeataClusterContext.unbindGroup(); + } + } + } + + private Object onExecuteRaft(RaftBaseMsg msg) { + RaftMsgExecute execute = EXECUTES.get(msg.getMsgType()); + if (execute == null) { + throw new RuntimeException( + "the state machine does not allow events that cannot be executed, please feedback the information to the Seata community !!! msg: " + + msg); + } + try { + return execute.execute(msg); + } catch (Throwable e) { + LOGGER.error("Message synchronization failure: {}, msgType: {}", e.getMessage(), msg.getMsgType(), e); + throw new RuntimeException(e); + } + } + + public AtomicLong getCurrentTerm() { + return currentTerm; + } + + public void registryStoreSnapshotFile(StoreSnapshotFile storeSnapshotFile) { + snapshotFiles.add(storeSnapshotFile); + } + + public RaftClusterMetadata getRaftLeaderMetadata() { + return raftClusterMetadata; + } + + public void setRaftLeaderMetadata(RaftClusterMetadata raftClusterMetadata) { + this.raftClusterMetadata = raftClusterMetadata; + } + + public RaftClusterMetadata changeOrInitRaftClusterMetadata() { + raftClusterMetadata.setTerm(this.currentTerm.get()); + Node leaderNode = raftClusterMetadata.getLeader(); + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + PeerId cureentPeerId = raftServer.getServerId(); + // After the re-election, the leader information may be different from the latest leader, and you need to replace the leader information + if (leaderNode == null || (leaderNode.getInternal() != null + && !cureentPeerId.equals(new PeerId(leaderNode.getInternal().getHost(), leaderNode.getInternal().getPort())))) { + Node leader = + raftClusterMetadata.createNode(XID.getIpAddress() == null ? NetUtil.getLocalIp() : XID.getIpAddress(), XID.getPort() <= 0? 8091 : XID.getPort(), raftServer.getServerId().getPort(), + Integer.parseInt( + ((Environment) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) + .getProperty("server.port", String.valueOf(7091))), + group, Collections.emptyMap()); + leader.setRole(ClusterRole.LEADER); + raftClusterMetadata.setLeader(leader); + } + return raftClusterMetadata; + } + + public void refreshClusterMetadata(RaftBaseMsg syncMsg) { + // Directly receive messages from the leader and update the cluster metadata + raftClusterMetadata = ((RaftClusterMetadataMsg)syncMsg).getRaftClusterMetadata(); + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null) { + ((ApplicationEventPublisher)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterChangeEvent(this, group, raftClusterMetadata.getTerm(), this.isLeader())); + LOGGER.info("groupId: {}, refresh cluster metadata: {}", group, raftClusterMetadata); + } + + } + + private void syncCurrentNodeInfo(String group) { + if (initSync.get()) { + return; + } + try { + RouteTable.getInstance().refreshLeader(RaftConfigServerManager.getCliClientServiceInstance(), group, 1000); + PeerId peerId = RouteTable.getInstance().selectLeader(group); + if (peerId != null) { + syncCurrentNodeInfo(peerId); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + } + + private void syncCurrentNodeInfo(PeerId leaderPeerId) { + try { + // Ensure that the current leader must be version 2.1 or later to synchronize the operation + Node leader = raftClusterMetadata.getLeader(); + if (leader != null && StringUtils.isNotBlank(leader.getVersion()) && initSync.compareAndSet(false, true)) { + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + PeerId cureentPeerId = raftServer.getServerId(); + Node node = raftClusterMetadata.createNode(XID.getIpAddress() == null ? NetUtil.getLocalIp() : XID.getIpAddress(), XID.getPort() <= 0? 8091 : XID.getPort(), cureentPeerId.getPort(), + Integer.parseInt( + ((Environment)ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_CONFIGURABLE_ENVIRONMENT)) + .getProperty("server.port", String.valueOf(7091))), + group, Collections.emptyMap()); + InvokeContext invokeContext = new InvokeContext(); + PutNodeMetadataRequest putNodeInfoRequest = new PutNodeMetadataRequest(node); + Configuration configuration = RouteTable.getInstance().getConfiguration(group); + node.setRole( + configuration.getPeers().contains(cureentPeerId) ? ClusterRole.FOLLOWER : ClusterRole.LEARNER); + invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, + SerializerType.JACKSON.getCode()); + CliClientServiceImpl cliClientService = + (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); + // The previous leader may be an old snapshot or log playback, which is not accurate, and you + // need to get the leader again + cliClientService.getRpcClient().invokeAsync(leaderPeerId.getEndpoint(), putNodeInfoRequest, + invokeContext, (result, err) -> { + if (err == null) { + PutNodeMetadataResponse putNodeMetadataResponse = (PutNodeMetadataResponse)result; + if (putNodeMetadataResponse.isSuccess()) { + scheduledFuture.cancel(true); + LOGGER.info("sync node info to leader: {}, result: {}", leaderPeerId, result); + } else { + initSync.compareAndSet(true, false); + LOGGER.info( + "sync node info to leader: {}, result: {}, retry will be made at the time of the re-election or after 10 seconds", + leaderPeerId, result); + } + } else { + initSync.compareAndSet(true, false); + LOGGER.error("sync node info to leader: {}, error: {}", leaderPeerId, err.getMessage(), + err); + } + }, 30000); + } + } catch (Exception e) { + LOGGER.error(e.getMessage(), e); + } + } + + public void changeNodeMetadata(Node node) { + lock.lock(); + try { + List list = node.getRole() == ClusterRole.FOLLOWER ? raftClusterMetadata.getFollowers() + : raftClusterMetadata.getLearner(); + // If the node currently exists, modify it + for (Node follower : list) { + Node.Endpoint endpoint = follower.getInternal(); + if (endpoint != null) { + // change old follower node metadata + if (endpoint.getHost().equals(node.getInternal().getHost()) + && endpoint.getPort() == node.getInternal().getPort()) { + follower.setTransaction(node.getTransaction()); + follower.setControl(node.getControl()); + follower.setGroup(group); + follower.setMetadata(node.getMetadata()); + follower.setVersion(node.getVersion()); + follower.setRole(node.getRole()); + return; + } + } + } + // add new node node metadata + list.add(node); + syncMetadata(); + } finally { + lock.unlock(); + } + } + + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java index b891bea8484..1bef8f86507 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftServerManager.java @@ -132,7 +132,7 @@ public static void init() { try { // Here you have raft RPC and business RPC using the same RPC server, and you can usually do this // separately - rpcServer = RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()); + rpcServer = RaftConfigServerManager.getRpcServer() == null? RaftRpcServerFactory.createRaftRpcServer(serverId.getEndpoint()) : RaftConfigServerManager.getRpcServer(); RaftServer raftServer = new RaftServer(dataPath, group, serverId, initNodeOptions(initConf), rpcServer); // as the foundation for multi raft group in the future RAFT_SERVER_MAP.put(group, raftServer); @@ -152,7 +152,7 @@ public static void start() { } LOGGER.info("started seata server raft cluster, group: {} ", group); }); - if (rpcServer != null) { + if (rpcServer != null && RaftConfigServerManager.getRpcServer() == null) { rpcServer.registerProcessor(new PutNodeInfoRequestProcessor()); SerializerManager.addSerializer(SerializerType.JACKSON.getCode(), new JacksonBoltSerializer()); if (!rpcServer.init(null)) { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java new file mode 100644 index 00000000000..71dfa098ce6 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/AbstractRaftConfigMsgExecute.java @@ -0,0 +1,28 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.execute.config; + +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; +import org.apache.seata.server.cluster.raft.execute.RaftMsgExecute; + + +public abstract class AbstractRaftConfigMsgExecute implements RaftMsgExecute { + + protected ConfigStoreManager configStoreManager = RocksDBConfigStoreManager.getInstance(); + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java new file mode 100644 index 00000000000..1d676cf0221 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.execute.config; + +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; +import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; + +import java.util.Map; + + +public class ConfigOperationExecute extends AbstractRaftConfigMsgExecute { + @Override + public Object execute(RaftBaseMsg syncMsg) throws Throwable { + RaftConfigOperationSyncMsg configSyncMsg = (RaftConfigOperationSyncMsg) syncMsg; + ConfigOperationDTO configOperation = configSyncMsg.getConfigOperation(); + switch (configOperation.getOptType()) { + case PUT: + return put(configOperation); + case DELETE: + return delete(configOperation); + case GET: + return get(configOperation); + case GET_ALL: + return getAll(configOperation); + default: + return ConfigOperationResponse.fail("unknown operation type"); + } + } + + private ConfigOperationResponse get(ConfigOperationDTO configOperation) { + String result = configStoreManager.get(configOperation.getGroup(), configOperation.getKey()); + return ConfigOperationResponse.success(result); + } + + private ConfigOperationResponse put(ConfigOperationDTO configOperation) { + Boolean success = configStoreManager.put(configOperation.getGroup(), configOperation.getKey(), configOperation.getValue()); + return success? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); + } + + private ConfigOperationResponse delete(ConfigOperationDTO configOperation) { + Boolean success = configStoreManager.delete(configOperation.getGroup(), configOperation.getKey()); + return success? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); + } + + private ConfigOperationResponse getAll(ConfigOperationDTO configOperation) { + Map configMap = configStoreManager.getAll(configOperation.getGroup()); + return ConfigOperationResponse.success(configMap); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java new file mode 100644 index 00000000000..1e9d2bbb3d4 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationType.java @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.execute.config; + +public enum ConfigOperationType { + GET("get"), PUT("put"), DELETE("delete"), GET_ALL("getAll"); + private final String type; + + ConfigOperationType(String type) { + this.type = type; + } + + public String getType() { + return type; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java new file mode 100644 index 00000000000..e59887fcd62 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/ConfigOperationRequestProcessor.java @@ -0,0 +1,67 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.processor; + +import com.alipay.sofa.jraft.rpc.RpcContext; +import com.alipay.sofa.jraft.rpc.RpcProcessor; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; +import org.apache.seata.server.cluster.raft.sync.msg.closure.ConfigClosure; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; +import org.apache.seata.server.cluster.raft.util.RaftConfigTaskUtil; + + +public class ConfigOperationRequestProcessor implements RpcProcessor { + private static final String NOT_LEADER = "not leader"; + @Override + public void handleRequest(RpcContext rpcCtx, ConfigOperationRequest request) { + if (RaftConfigServerManager.isLeader()){ + onExecute(rpcCtx, request); + }else{ + rpcCtx.sendResponse(ConfigOperationResponse.fail(NOT_LEADER)); + } + } + + private void onExecute(RpcContext rpcCtx, ConfigOperationRequest request) { + ConfigOperationDTO operationDTO = ConfigOperationDTO.convertConfigRequest2Dto(request); + RaftConfigOperationSyncMsg syncMsg = new RaftConfigOperationSyncMsg(operationDTO); + ConfigOperationResponse response = new ConfigOperationResponse(); + ConfigClosure closure = new ConfigClosure(); + closure.setRaftBaseMsg(syncMsg); + closure.setResponse(response); + closure.setDone(status -> { + if (!status.isOk()){ + response.setSuccess(false); + response.setErrMsg(status.getErrorMsg()); + } + rpcCtx.sendResponse(response); + }); + try { + RaftConfigTaskUtil.createTask(closure, syncMsg, null); + } catch (TransactionException e) { + throw new RuntimeException(e); + } + } + + @Override + public String interest() { + return ConfigOperationRequest.class.getName(); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java index de90cbd8c5d..7084205114f 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/PutNodeInfoRequestProcessor.java @@ -19,12 +19,12 @@ import com.alipay.sofa.jraft.rpc.RpcContext; import com.alipay.sofa.jraft.rpc.RpcProcessor; import org.apache.seata.common.metadata.Node; -import org.apache.seata.server.cluster.raft.RaftServer; -import org.apache.seata.server.cluster.raft.RaftServerManager; -import org.apache.seata.server.cluster.raft.RaftStateMachine; +import org.apache.seata.server.cluster.raft.*; import org.apache.seata.server.cluster.raft.processor.request.PutNodeMetadataRequest; import org.apache.seata.server.cluster.raft.processor.response.PutNodeMetadataResponse; +import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; + public class PutNodeInfoRequestProcessor implements RpcProcessor { public PutNodeInfoRequestProcessor() { @@ -35,6 +35,25 @@ public PutNodeInfoRequestProcessor() { public void handleRequest(RpcContext rpcCtx, PutNodeMetadataRequest request) { Node node = request.getNode(); String group = node.getGroup(); + if (RaftConfigServerManager.getGroup().equals(group)){ + changeConfigGroupRequest(group, node, rpcCtx, request); + }else{ + changeNormalGroupRequest(group, node, rpcCtx, request); + } + } + + private static void changeConfigGroupRequest(String group, Node node, RpcContext rpcCtx, PutNodeMetadataRequest request){ + if (RaftConfigServerManager.isLeader()) { + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + RaftConfigStateMachine raftStateMachine = raftServer.getRaftStateMachine(); + raftStateMachine.changeNodeMetadata(node); + rpcCtx.sendResponse(new PutNodeMetadataResponse(true)); + } else { + rpcCtx.sendResponse(new PutNodeMetadataResponse(false)); + } + } + + private static void changeNormalGroupRequest(String group, Node node, RpcContext rpcCtx, PutNodeMetadataRequest request){ if (RaftServerManager.isLeader(group)) { RaftServer raftServer = RaftServerManager.getRaftServer(group); RaftStateMachine raftStateMachine = raftServer.getRaftStateMachine(); diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java new file mode 100644 index 00000000000..56f59a0c928 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.processor.request; + +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; + +import java.io.Serializable; + + +public class ConfigOperationRequest implements Serializable { + private static final long serialVersionUID = -1149573667621259458L; + private ConfigOperationType optType; + private String group; + private String key; + private String value; + + public ConfigOperationRequest() { + } + + public ConfigOperationRequest(ConfigOperationType optType, String group) { + this.optType = optType; + this.group = group; + } + + public ConfigOperationRequest(ConfigOperationType optType, String group, String key) { + this.optType = optType; + this.group = group; + this.key = key; + } + + public ConfigOperationRequest(ConfigOperationType optType, String group, String key, String value) { + this.optType = optType; + this.group = group; + this.key = key; + this.value = value; + } + + public static ConfigOperationRequest buildGetRequest(String group, String key) { + return new ConfigOperationRequest(ConfigOperationType.GET, group, key); + } + + public static ConfigOperationRequest buildPutRequest(String group, String key, String value) { + return new ConfigOperationRequest(ConfigOperationType.PUT, group, key, value); + } + + public static ConfigOperationRequest buildDeleteRequest(String group, String key) { + return new ConfigOperationRequest(ConfigOperationType.DELETE, group, key); + } + + public static ConfigOperationRequest buildGetAllRequest(String group) { + return new ConfigOperationRequest(ConfigOperationType.GET_ALL, group); + } + + public ConfigOperationType getOptType() { + return optType; + } + public void setOptType(ConfigOperationType optType) { + this.optType = optType; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java new file mode 100644 index 00000000000..379a48b3b3a --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/response/ConfigOperationResponse.java @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.processor.response; + + +import java.io.Serializable; + + +public class ConfigOperationResponse implements Serializable { + private static final long serialVersionUID = -1439073440621259777L; + + private Object result; + private boolean success; + private String errMsg; + + public ConfigOperationResponse() { + } + + public Object getResult() { + return result; + } + + public void setResult(Object result) { + this.result = result; + } + + public boolean isSuccess() { + return success; + } + + public void setSuccess(boolean success) { + this.success = success; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + public static ConfigOperationResponse success(){ + ConfigOperationResponse response = new ConfigOperationResponse(); + response.setSuccess(true); + return response; + } + + public static ConfigOperationResponse success(Object result){ + ConfigOperationResponse response = success(); + response.setResult(result); + return response; + } + + public static ConfigOperationResponse fail(){ + ConfigOperationResponse response = new ConfigOperationResponse(); + response.setSuccess(false); + return response; + } + + public static ConfigOperationResponse fail(String errMsg){ + ConfigOperationResponse response = fail(); + response.setErrMsg(errMsg); + return response; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java index e8c1cc68f31..ec5695bd309 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/RaftSnapshot.java @@ -131,8 +131,11 @@ public enum SnapshotType { /** * leader metadata snapshot */ - leader_metadata("leader_metadata"); - + leader_metadata("leader_metadata"), + /** + * config snapshot + */ + config("config"); final String type; SnapshotType(String type) { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java new file mode 100644 index 00000000000..50cedd5f819 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java @@ -0,0 +1,107 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.snapshot.config; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.config.store.ConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManagerProvider; +import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; +import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; +import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; +import java.util.Map; +import java.util.Objects; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_TYPE; +import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; + + +public class ConfigSnapshotFile implements Serializable, StoreSnapshotFile { + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigSnapshotFile.class); + + private static final long serialVersionUID = 1452307567830545914L; + + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + + private static ConfigStoreManager configStoreManager; + String group; + + String fileName = "config"; + + public ConfigSnapshotFile(String group) { + this.group = group; + String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); + configStoreManager = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); + } + + @Override + public Status save(SnapshotWriter writer) { + Map configMap = configStoreManager.getConfigMap(); + RaftSnapshot raftSnapshot = new RaftSnapshot(); + raftSnapshot.setBody(configMap); + raftSnapshot.setType(RaftSnapshot.SnapshotType.config); + LOGGER.info("groupId: {}, config size: {}", group, configMap.size()); + String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); + try { + if (save(raftSnapshot, path)) { + if (writer.addFile(fileName)) { + return Status.OK(); + } else { + return new Status(RaftError.EIO, "Fail to add file to writer"); + } + } + } catch (IOException e) { + LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); + } + return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); + } + + @Override + public boolean load(SnapshotReader reader) { + if (reader.getFileMeta(fileName) == null) { + LOGGER.error("Fail to find data file in {}", reader.getPath()); + return false; + } + String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); + try { + LOGGER.info("on snapshot load start index: {}", reader.load().getLastIncludedIndex()); + Map configMap = (Map)load(path); + ConfigStoreManager configStoreManager = RocksDBConfigStoreManager.getInstance(); + configStoreManager.clearData(); + configStoreManager.putConfigMap(configMap); + if (LOGGER.isInfoEnabled()) { + LOGGER.info("on snapshot load end index: {}", reader.load().getLastIncludedIndex()); + } + return true; + } catch (final Exception e) { + LOGGER.error("fail to load snapshot from {}", path, e); + return false; + } + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java new file mode 100644 index 00000000000..7c01534ee10 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/ConfigLeaderMetadataSnapshotFile.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.snapshot.metadata; + +import com.alipay.sofa.jraft.Status; +import com.alipay.sofa.jraft.error.RaftError; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotReader; +import com.alipay.sofa.jraft.storage.snapshot.SnapshotWriter; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; +import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; +import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.io.IOException; +import java.io.Serializable; + + +public class ConfigLeaderMetadataSnapshotFile implements Serializable, StoreSnapshotFile { + private static final long serialVersionUID = 43235664615355354L; + + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigLeaderMetadataSnapshotFile.class); + + private final String group; + + private final String fileName = "leader_metadata"; + + public ConfigLeaderMetadataSnapshotFile(String group) { + this.group = group; + } + + + @Override + public Status save(SnapshotWriter writer) { + RaftSnapshot raftSnapshot = new RaftSnapshot(); + RaftClusterMetadata raftClusterMetadata = + RaftConfigServerManager.getRaftServer().getRaftStateMachine().getRaftLeaderMetadata(); + raftSnapshot.setBody(raftClusterMetadata); + raftSnapshot.setType(RaftSnapshot.SnapshotType.leader_metadata); + String path = new StringBuilder(writer.getPath()).append(File.separator).append(fileName).toString(); + try { + if (save(raftSnapshot, path)) { + if (writer.addFile(fileName)) { + return Status.OK(); + } else { + return new Status(RaftError.EIO, "Fail to add file to writer"); + } + } + } catch (IOException e) { + LOGGER.error("Fail to save groupId: {} snapshot {}", group, path, e); + } + return new Status(RaftError.EIO, "Fail to save groupId: " + group + " snapshot %s", path); + } + + @Override + public boolean load(SnapshotReader reader) { + if (reader.getFileMeta(fileName) == null) { + LOGGER.error("Fail to find data file in {}", reader.getPath()); + return false; + } + String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); + try { + RaftClusterMetadata raftClusterMetadata = (RaftClusterMetadata)load(path); + RaftConfigServerManager.getRaftServer().getRaftStateMachine() + .setRaftLeaderMetadata(raftClusterMetadata); + return true; + } catch (final Exception e) { + LOGGER.error("fail to load snapshot from {}", path, e); + return false; + } + } +} + diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/LeaderMetadataSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/LeaderMetadataSnapshotFile.java index 1092093ae9f..ae0e6fe2d05 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/LeaderMetadataSnapshotFile.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/metadata/LeaderMetadataSnapshotFile.java @@ -45,6 +45,7 @@ public LeaderMetadataSnapshotFile(String group) { this.group = group; } + @Override public Status save(SnapshotWriter writer) { RaftSnapshot raftSnapshot = new RaftSnapshot(); diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java new file mode 100644 index 00000000000..23eae8401cb --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftConfigOperationSyncMsg.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.sync.msg; + +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; + + +public class RaftConfigOperationSyncMsg extends RaftBaseMsg{ + + private static final long serialVersionUID = -3344345671349834321L; + private ConfigOperationDTO configOperation; + + public RaftConfigOperationSyncMsg(ConfigOperationDTO configOperation) { + this.msgType = RaftSyncMsgType.CONFIG_OPERATION; + this.configOperation = configOperation; + } + + public RaftConfigOperationSyncMsg() { + } + + public ConfigOperationDTO getConfigOperation() { + return configOperation; + } + + @Override + public String toString() { + return StringUtils.toString(this); + } + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java index f74cdb88c52..869fe011991 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/RaftSyncMsgType.java @@ -55,5 +55,7 @@ public enum RaftSyncMsgType { /** * refresh cluster metadata */ - REFRESH_CLUSTER_METADATA; + REFRESH_CLUSTER_METADATA, + + CONFIG_OPERATION; } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java new file mode 100644 index 00000000000..ade72303882 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/closure/ConfigClosure.java @@ -0,0 +1,63 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.sync.msg.closure; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.Status; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; +import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; + +/** + * The type of closure for configuration sync in raft + */ +public class ConfigClosure implements Closure { + + private RaftBaseMsg raftBaseMsg; + private ConfigOperationResponse response; + private Closure done; + + @Override + public void run(Status status) { + if (done != null) { + done.run(status); + } + } + + public RaftBaseMsg getRaftBaseMsg() { + return raftBaseMsg; + } + + public void setRaftBaseMsg(RaftBaseMsg raftBaseMsg) { + this.raftBaseMsg = raftBaseMsg; + } + + public ConfigOperationResponse getResponse() { + return response; + } + + public void setResponse(ConfigOperationResponse response) { + this.response = response; + } + + public Closure getDone() { + return done; + } + + public void setDone(Closure done) { + this.done = done; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java new file mode 100644 index 00000000000..02402ba4767 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java @@ -0,0 +1,98 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.sync.msg.dto; + +import org.apache.seata.config.ConfigurationFactory; +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; +import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; + +import java.io.Serializable; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_GROUP; +import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; + + +public class ConfigOperationDTO implements Serializable { + private static final long serialVersionUID = -1237293571963636954L; + + private ConfigOperationType optType; + private String group = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(CONFIG_STORE_GROUP, DEFAULT_STORE_GROUP);; + private String key; + private Object value; + + public ConfigOperationDTO() { + } + + public ConfigOperationDTO(ConfigOperationType optType, String group, String key, Object value) { + this.optType = optType; + this.group = group; + this.key = key; + this.value = value; + } + + public ConfigOperationDTO(ConfigOperationType optType, String group, String key){ + this.optType = optType; + this.group = group; + this.key = key; + } + public ConfigOperationDTO(ConfigOperationType optType, String key){ + this.optType = optType; + this.key = key; + } + public ConfigOperationDTO(ConfigOperationType optType, String key, Object value) { + this.optType = optType; + this.key = key; + this.value = value; + } + + + public ConfigOperationType getOptType() { + return optType; + } + + public void setOptType(ConfigOperationType optType) { + this.optType = optType; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public static ConfigOperationDTO convertConfigRequest2Dto(ConfigOperationRequest request) { + return new ConfigOperationDTO(request.getOptType(), request.getGroup(), request.getKey(), request.getValue()); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java b/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java new file mode 100644 index 00000000000..02b7a4e9231 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/util/RaftConfigTaskUtil.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.raft.util; + +import com.alipay.sofa.jraft.Closure; +import com.alipay.sofa.jraft.entity.Task; +import org.apache.seata.core.exception.GlobalTransactionException; +import org.apache.seata.core.exception.TransactionException; +import org.apache.seata.core.exception.TransactionExceptionCode; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; +import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; +import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMessage; + +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +/** + */ +public class RaftConfigTaskUtil { + public static boolean createTask(Closure done, Object data, CompletableFuture completableFuture) + throws TransactionException { + final Task task = new Task(); + if (data != null) { + RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); + raftSyncMessage.setBody(data); + try { + task.setData(ByteBuffer.wrap(RaftSyncMessageSerializer.encode(raftSyncMessage))); + } catch (IOException e) { + throw new TransactionException(e); + } + } + task.setDone(done == null ? status -> { + } : done); + RaftConfigServerManager.getRaftServer().getNode().apply(task); + if (completableFuture != null) { + return futureGet(completableFuture); + } + return true; + } + + public static boolean createTask(Closure done, CompletableFuture completableFuture) + throws TransactionException { + return createTask(done, null, completableFuture); + } + + public static boolean futureGet(CompletableFuture completableFuture) throws TransactionException { + try { + return completableFuture.get(); + } catch (InterruptedException e) { + throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, + "Fail to store global session: " + e.getMessage()); + } catch (ExecutionException e) { + if (e.getCause() instanceof TransactionException) { + throw (TransactionException)e.getCause(); + } else { + throw new GlobalTransactionException(TransactionExceptionCode.FailedWriteSession, + "Fail to store global session: " + e.getMessage()); + } + } + } + +} diff --git a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java index f3ed31c594f..67527f8a96f 100644 --- a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java +++ b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java @@ -28,28 +28,32 @@ import com.alipay.sofa.jraft.RouteTable; import com.alipay.sofa.jraft.conf.Configuration; import com.alipay.sofa.jraft.entity.PeerId; +import com.alipay.sofa.jraft.rpc.InvokeContext; +import com.alipay.sofa.jraft.rpc.impl.cli.CliClientServiceImpl; import org.apache.seata.common.ConfigurationKeys; import org.apache.seata.common.metadata.MetadataResponse; import org.apache.seata.common.metadata.Node; import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.console.result.Result; +import org.apache.seata.core.serializer.SerializerType; import org.apache.seata.server.cluster.manager.ClusterWatcherManager; +import org.apache.seata.server.cluster.raft.RaftConfigServer; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; import org.apache.seata.server.cluster.raft.RaftServer; import org.apache.seata.server.cluster.raft.RaftServerManager; +import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; +import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; import org.apache.seata.server.cluster.watch.Watcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.context.ApplicationContext; -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 org.springframework.web.bind.annotation.*; import static org.apache.seata.common.ConfigurationKeys.STORE_MODE; +import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; /** @@ -89,6 +93,21 @@ public Result changeCluster(@RequestParam String raftClusterStr) { return result; } + @PostMapping("/changeConfigCluster") + public Result changeConfigCluster(@RequestParam String raftClusterStr) { + Result result = new Result<>(); + final Configuration newConf = new Configuration(); + if (!newConf.parse(raftClusterStr)) { + result.setMessage("fail to parse initConf:" + raftClusterStr); + } else { + String group = RaftConfigServerManager.getGroup(); + RaftConfigServerManager.getCliServiceInstance().changePeers(group, + RouteTable.getInstance().getConfiguration(group), newConf); + RouteTable.getInstance().updateConfiguration(group, newConf); + } + return result; + } + @GetMapping("/cluster") public MetadataResponse cluster(String group) { MetadataResponse metadataResponse = new MetadataResponse(); @@ -122,6 +141,117 @@ public MetadataResponse cluster(String group) { return metadataResponse; } + @GetMapping("/config/cluster") + public MetadataResponse configCluster() { + MetadataResponse metadataResponse = new MetadataResponse(); + RaftConfigServer raftServer = RaftConfigServerManager.getRaftServer(); + if (raftServer != null) { + String configType = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(ConfigurationKeys.FILE_ROOT_CONFIG + + ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR + ConfigurationKeys.FILE_ROOT_TYPE); + metadataResponse.setConfigMode(configType); + RouteTable routeTable = RouteTable.getInstance(); + try { + routeTable.refreshLeader(RaftConfigServerManager.getCliClientServiceInstance(), RAFT_CONFIG_GROUP , 1000); + PeerId leader = routeTable.selectLeader(RAFT_CONFIG_GROUP); + if (leader != null) { + Set nodes = new HashSet<>(); + RaftClusterMetadata raftClusterMetadata = raftServer.getRaftStateMachine().getRaftLeaderMetadata(); + Node leaderNode = raftServer.getRaftStateMachine().getRaftLeaderMetadata().getLeader(); + leaderNode.setGroup(RAFT_CONFIG_GROUP); + nodes.add(leaderNode); + nodes.addAll(raftClusterMetadata.getLearner()); + nodes.addAll(raftClusterMetadata.getFollowers()); + metadataResponse.setTerm(raftClusterMetadata.getTerm()); + metadataResponse.setNodes(new ArrayList<>(nodes)); + } + } catch (Exception e) { + LOGGER.error("there is an exception to getting the leader address: {}", e.getMessage(), e); + } + } + return metadataResponse; + } + + @GetMapping("/config/get") + public ConfigOperationResponse getConfig(String group, String key) { + ConfigOperationRequest request = ConfigOperationRequest.buildGetRequest(group, key); + + PeerId leader = RaftConfigServerManager.getLeader(); + if (leader == null) { + return ConfigOperationResponse.fail("failed to get leader"); + } + InvokeContext invokeContext = new InvokeContext(); + invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, + SerializerType.JACKSON.getCode()); + CliClientServiceImpl cliClientService = (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); + try { + return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); + } catch (Exception e) { + LOGGER.error("Failed to get value for key: {} in group: {}: ",key, group, e); + return ConfigOperationResponse.fail(e.getMessage()); + } + } + + @PostMapping("/config/put") + public ConfigOperationResponse putConfig(String group, String key, String value) { + ConfigOperationRequest request = ConfigOperationRequest.buildPutRequest(group, key, value); + + PeerId leader = RaftConfigServerManager.getLeader(); + if (leader == null) { + return ConfigOperationResponse.fail("failed to get leader"); + } + CliClientServiceImpl cliClientService = (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); + InvokeContext invokeContext = new InvokeContext(); + invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, + SerializerType.JACKSON.getCode()); + try { + return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); + } catch (Exception e) { + LOGGER.error("Failed to put value: {} for key: {} in group: {}: ", value, key, group, e); + return ConfigOperationResponse.fail(e.getMessage()); + } + } + + @DeleteMapping("/config/delete") + public ConfigOperationResponse deleteConfig(String group, String key) { + ConfigOperationRequest request = ConfigOperationRequest.buildDeleteRequest(group, key); + + PeerId leader = RaftConfigServerManager.getLeader(); + if (leader == null) { + return ConfigOperationResponse.fail("failed to get leader"); + } + CliClientServiceImpl cliClientService = (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); + InvokeContext invokeContext = new InvokeContext(); + invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, + SerializerType.JACKSON.getCode()); + try { + return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); + } catch (Exception e) { + LOGGER.error("Failed to delete key: {} in group: {}: ", key, group, e); + return ConfigOperationResponse.fail(e.getMessage()); + } + } + + @GetMapping("/config/getAll") + public ConfigOperationResponse getAllConfig(String group) { + ConfigOperationRequest request = ConfigOperationRequest.buildGetAllRequest(group); + + PeerId leader = RaftConfigServerManager.getLeader(); + if (leader == null) { + return ConfigOperationResponse.fail("failed to get leader"); + } + InvokeContext invokeContext = new InvokeContext(); + invokeContext.put(com.alipay.remoting.InvokeContext.BOLT_CUSTOM_SERIALIZER, + SerializerType.JACKSON.getCode()); + CliClientServiceImpl cliClientService = (CliClientServiceImpl)RaftConfigServerManager.getCliClientServiceInstance(); + try { + return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); + } catch (Exception e) { + LOGGER.error("Failed to get all configs in group:{}: ", group, e); + return ConfigOperationResponse.fail(e.getMessage()); + } + } + + @PostMapping("/watch") public void watch(HttpServletRequest request, @RequestParam Map groupTerms, @RequestParam(defaultValue = "28000") int timeout) { diff --git a/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java b/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java index 974416b26c5..70b8daca678 100644 --- a/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java +++ b/server/src/main/java/org/apache/seata/server/spring/listener/SeataPropertiesLoader.java @@ -21,6 +21,7 @@ import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.config.FileConfiguration; import org.apache.seata.config.file.FileConfig; +import org.apache.seata.server.cluster.raft.RaftConfigServerManager; import org.apache.seata.server.store.StoreConfig; import org.springframework.context.ApplicationContextInitializer; import org.springframework.context.ConfigurableApplicationContext; @@ -74,6 +75,8 @@ public void initialize(ConfigurableApplicationContext applicationContext) { environment.getPropertySources().addLast(new PropertiesPropertySource("seataOldConfig", properties)); } // Load by priority + RaftConfigServerManager.init(); + RaftConfigServerManager.start(); System.setProperty("sessionMode", StoreConfig.getSessionMode().getName()); System.setProperty("lockMode", StoreConfig.getLockMode().getName()); } From 52e53c972e7433be16b4e4f5d2d8b6d43d9c8cb6 Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Mon, 15 Jul 2024 19:53:13 +0800 Subject: [PATCH 02/34] feature: support raft config center(client) --- .../common/config/ConfigDataResponse.java | 49 ++ .../apache/seata/common/store/StoreMode.java | 5 + .../store/AbstractConfigStoreManager.java | 31 - .../config/store/ConfigStoreManager.java | 28 +- .../rocksdb/RocksDBConfigStoreManager.java | 31 +- .../config/store/rocksdb/RocksDBFactory.java | 4 +- config/seata-config-raft/pom.xml | 4 + .../config/raft/RaftConfigurationClient.java | 577 +++++++++++++++++- .../raft/RaftConfigurationProvider.java | 5 +- .../config/raft/RaftConfigurationServer.java | 2 - .../raft/RaftConfigurationClientTest.java | 53 ++ .../SeataCoreEnvironmentPostProcessor.java | 1 + .../boot/autoconfigure/StarterConstants.java | 1 + .../config/ConfigRaftProperties.java | 78 +++ .../listener/ClusterConfigChangeEvent.java | 40 ++ .../listener/ClusterConfigChangeListener.java | 26 + .../manager/ClusterConfigWatcherManager.java | 102 ++++ .../cluster/raft/RaftConfigServerManager.java | 14 +- .../cluster/raft/RaftConfigStateMachine.java | 1 - .../config/ConfigOperationExecute.java | 24 + .../raft/sync/msg/dto/ConfigOperationDTO.java | 10 + .../server/controller/ClusterController.java | 13 + 22 files changed, 1028 insertions(+), 71 deletions(-) create mode 100644 common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java create mode 100644 config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java diff --git a/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java b/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java new file mode 100644 index 00000000000..9bb7b38dd52 --- /dev/null +++ b/common/src/main/java/org/apache/seata/common/config/ConfigDataResponse.java @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.common.config; + + +public class ConfigDataResponse { + + T result; + String errMsg; + Boolean success; + + public T getResult() { + return result; + } + + public void setResult(T result) { + this.result = result; + } + + public String getErrMsg() { + return errMsg; + } + + public void setErrMsg(String errMsg) { + this.errMsg = errMsg; + } + + public Boolean getSuccess() { + return success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } +} diff --git a/common/src/main/java/org/apache/seata/common/store/StoreMode.java b/common/src/main/java/org/apache/seata/common/store/StoreMode.java index 29c90d544f2..68a5092fd16 100644 --- a/common/src/main/java/org/apache/seata/common/store/StoreMode.java +++ b/common/src/main/java/org/apache/seata/common/store/StoreMode.java @@ -16,6 +16,8 @@ */ package org.apache.seata.common.store; +import org.apache.seata.common.util.StringUtils; + /** * transaction log store mode * @@ -55,6 +57,9 @@ public enum StoreMode { * @return the store mode */ public static StoreMode get(String name) { + if (StringUtils.isEmpty(name)) { + return null; + } for (StoreMode sm : StoreMode.class.getEnumConstants()) { if (sm.name.equalsIgnoreCase(name)) { return sm; diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java index bac436b0e3e..b44087bb967 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java @@ -16,19 +16,10 @@ */ package org.apache.seata.config.store; -import org.apache.seata.common.util.StringUtils; -import org.apache.seata.config.ConfigType; -import org.apache.seata.config.processor.ConfigDataType; -import org.apache.seata.config.processor.ConfigProcessor; -import org.apache.seata.config.store.rocksdb.RocksDBConfigStoreManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Properties; /** * The type Abstract config store manager. @@ -91,27 +82,5 @@ public void destroy() {} public abstract void shutdown(); - protected static String convertConfig2Str(Map configs) { - StringBuilder sb = new StringBuilder(); - for (Map.Entry entry : configs.entrySet()) { - sb.append(entry.getKey()).append("=").append(entry.getValue().toString()).append("\n"); - } - return sb.toString(); - } - - protected static Map convertConfigStr2Map(String configStr) { - if (StringUtils.isEmpty(configStr)) { - return new HashMap<>(); - } - Map configs = new HashMap<>(); - try { - Properties properties = ConfigProcessor.processConfig(configStr, ConfigDataType.properties.name()); - properties.forEach((k, v) -> configs.put(k.toString(), v)); - return configs; - } catch (IOException e) { - LOGGER.error("convert config properties error", e); - return new HashMap<>(); - } - } } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java index 8b8d39c6412..7745c99be44 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java @@ -16,9 +16,15 @@ */ package org.apache.seata.config.store; +import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.ConfigurationChangeListener; +import org.apache.seata.config.processor.ConfigDataType; +import org.apache.seata.config.processor.ConfigProcessor; +import java.io.IOException; +import java.util.HashMap; import java.util.Map; +import java.util.Properties; /** * The interface Local config store manager. @@ -50,5 +56,25 @@ public interface ConfigStoreManager { default void removeConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; - + static String convertConfig2Str(Map configs) { + StringBuilder sb = new StringBuilder(); + for (Map.Entry entry : configs.entrySet()) { + sb.append(entry.getKey()).append("=").append(entry.getValue().toString()).append("\n"); + } + return sb.toString(); + } + + static Map convertConfigStr2Map(String configStr) { + if (StringUtils.isEmpty(configStr)) { + return new HashMap<>(); + } + Map configs = new HashMap<>(); + try { + Properties properties = ConfigProcessor.processConfig(configStr, ConfigDataType.properties.name()); + properties.forEach((k, v) -> configs.put(k.toString(), v)); + return configs; + } catch (IOException e) { + return new HashMap<>(); + } + } } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java index 10d64c8b778..11326d2b8df 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -21,6 +21,7 @@ import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.*; import org.apache.seata.config.store.AbstractConfigStoreManager; +import org.apache.seata.config.store.ConfigStoreManager; import org.rocksdb.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -37,6 +38,7 @@ import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; + /** * The RocksDB config store manager * @@ -80,6 +82,7 @@ public RocksDBConfigStoreManager() { CURRENT_GROUP = FILE_CONFIG.getConfig(CONFIG_STORE_GROUP, DEFAULT_GROUP); maybeNeedLoadOriginConfig(); LOGGER.info("RocksDBConfigStoreManager initialized successfully"); + } /** @@ -118,7 +121,7 @@ private Map getConfigMap(String group) throws RocksDBException{ group = StringUtils.isEmpty(group)? CURRENT_GROUP : group; byte[] value = rocksdb.get(group.getBytes(DEFAULT_CHARSET)); String configStr = value != null ? new String(value, DEFAULT_CHARSET) : null; - return convertConfigStr2Map(configStr); + return ConfigStoreManager.convertConfigStr2Map(configStr); }finally { lock.readLock().unlock(); } @@ -155,10 +158,9 @@ public Boolean put(String group, String key, Object value) { try { Map configMap = getConfigMap(group); configMap.put(key, value); - String configStr = convertConfig2Str(configMap); + String configStr = ConfigStoreManager.convertConfig2Str(configMap); rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); - // LOGGER.info("put {} = {} in group: {}", key, value, group); return true; }catch (RocksDBException e){ LOGGER.error("Failed to put value for key: " + key, e); @@ -174,10 +176,9 @@ public Boolean delete(String group, String key) { try { Map configMap = getConfigMap(group); configMap.remove(key); - String configStr = convertConfig2Str(configMap); + String configStr = ConfigStoreManager.convertConfig2Str(configMap); rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); - // LOGGER.info("delete {} in group: {}", key, group); return true; }catch (RocksDBException e){ LOGGER.error("Failed to delete value for key: " + key, e); @@ -191,7 +192,7 @@ public Boolean delete(String group, String key) { public Boolean putAll(String group, Map configMap) { lock.writeLock().lock(); try{ - String configStr = convertConfig2Str(configMap); + String configStr = ConfigStoreManager.convertConfig2Str(configMap); rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); return true; @@ -284,19 +285,25 @@ public Boolean isEmpty(String group) { return CollectionUtils.isEmpty(getAll(group)); } - // todo server关闭时需要被调用 + // todo @Override public void shutdown() { - RocksDBFactory.close(); - if (RocksDBOptions.getDBDestroyOnShutdown()) { - destroy(); + lock.writeLock().lock(); + try { + RocksDBFactory.close(); + if (RocksDBOptions.getDBDestroyOnShutdown()) { + destroy(); + } + LOGGER.info("RocksDBConfigStoreManager has shutdown"); + }finally { + lock.writeLock().unlock(); } - LOGGER.info("RocksDBConfigStoreManager has shutdown"); + } @Override public void destroy() { - RocksDBFactory.destroy(DB_PATH, DB_OPTIONS); + RocksDBFactory.destroy(DB_PATH); LOGGER.info("DB destroyed, the db path is: {}.", DB_PATH); } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java index c670776f44e..f920d509a2d 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java @@ -67,10 +67,10 @@ public static synchronized void close() { } } - public static synchronized void destroy(String dbPath, Options options) { + public static synchronized void destroy(String dbPath) { close(); try { - RocksDB.destroyDB(dbPath, options); + RocksDB.destroyDB(dbPath, new Options()); }catch (RocksDBException e){ LOGGER.error("RocksDB destroy error: {}",e.getMessage(),e); } diff --git a/config/seata-config-raft/pom.xml b/config/seata-config-raft/pom.xml index bf3faca1528..20b7b3ddb5c 100644 --- a/config/seata-config-raft/pom.xml +++ b/config/seata-config-raft/pom.xml @@ -35,6 +35,10 @@ seata-config-core ${project.version} + + org.apache.httpcomponents + httpclient + \ No newline at end of file diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java index dafead98208..523fcb6b24a 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java @@ -16,12 +16,44 @@ */ package org.apache.seata.config.raft; -import org.apache.seata.config.AbstractConfiguration; -import org.apache.seata.config.ConfigurationChangeListener; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.HttpStatus; +import org.apache.http.StatusLine; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.EntityUtils; +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.common.config.ConfigDataResponse; +import org.apache.seata.common.exception.*; +import org.apache.seata.common.metadata.Metadata; +import org.apache.seata.common.metadata.MetadataResponse; +import org.apache.seata.common.metadata.Node; +import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.HttpClientUtil; +import org.apache.seata.common.util.StringUtils; +import org.apache.seata.config.*; +import org.apache.seata.config.store.ConfigStoreManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.Set; +import java.io.IOException; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.*; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_GROUP; +import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; +import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; +import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; /** * The type Raft configuration of client. @@ -29,8 +61,45 @@ */ public class RaftConfigurationClient extends AbstractConfiguration { private static final Logger LOGGER = LoggerFactory.getLogger(RaftConfigurationClient.class); + + private static final String CONFIG_TYPE = "raft"; + private static final String SERVER_ADDR_KEY = "serverAddr"; + private static final String RAFT_GROUP = RAFT_CONFIG_GROUP; // config + private static final String RAFT_CLUSTER = DEFAULT_SEATA_GROUP; // default + private static final String CONFIG_GROUP; + private static final String USERNAME_KEY = "username"; + private static final String PASSWORD_KEY = "password"; + private static final String AUTHORIZATION_HEADER = "Authorization"; + private static final String TOKEN_VALID_TIME_MS_KEY = "tokenValidityInMilliseconds"; + private static volatile RaftConfigurationClient instance; + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static final String USERNAME; + private static final String PASSWORD; + private static final long TOKEN_EXPIRE_TIME_IN_MILLISECONDS; + private static long tokenTimeStamp = -1; + private static final String IP_PORT_SPLIT_CHAR = ":"; + private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); + private static final Map> INIT_ADDRESSES = new HashMap<>(); + + private static final Metadata METADATA = new Metadata(); + private static volatile ThreadPoolExecutor REFRESH_METADATA_EXECUTOR; + private static volatile ThreadPoolExecutor REFRESH_CONFIG_EXECUTOR; + private static final AtomicBoolean CLOSED = new AtomicBoolean(false); + private static final AtomicBoolean CONFIG_CLOSED = new AtomicBoolean(false); + private static volatile Properties seataConfig = new Properties(); + private static final int MAP_INITIAL_CAPACITY = 8; + private static final ConcurrentMap> CONFIG_LISTENERS_MAP + = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); + private static ConfigStoreListener CONFIG_LISTENER; + static { + USERNAME = FILE_CONFIG.getConfig(getRaftUsernameKey()); + PASSWORD = FILE_CONFIG.getConfig(getRaftPasswordKey()); + TOKEN_EXPIRE_TIME_IN_MILLISECONDS = FILE_CONFIG.getLong(getTokenExpireTimeInMillisecondsKey(), 29 * 60 * 1000L); + CONFIG_GROUP = FILE_CONFIG.getConfig(CONFIG_STORE_GROUP, DEFAULT_STORE_GROUP); + } + public static String jwtToken; public static RaftConfigurationClient getInstance() { if (instance == null) { synchronized (RaftConfigurationClient.class) { @@ -43,6 +112,7 @@ public static RaftConfigurationClient getInstance() { } private RaftConfigurationClient() { + initClusterMetaData(); initClientConfig(); } @@ -53,46 +123,531 @@ private static void initClientConfig() { // 2.等待Raft日志提交,leader从rocksdb中读取全部配置返回(保证一致性) // 3.加载到seataConfig // 4.定期轮询配置变更 + // 触发监听 + try { + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); + if (configMap != null) { + seataConfig.putAll(configMap); + } + CONFIG_LISTENER = new ConfigStoreListener(CONFIG_GROUP, null); + startQueryConfigData(); + }catch (RetryableException e){ + LOGGER.error("init config properties error", e); + } + + } + private static String queryHttpAddress(String clusterName, String group) { + List nodeList = METADATA.getNodes(clusterName, group); + List addressList = null; + Stream stream = null; + if (CollectionUtils.isNotEmpty(nodeList)) { + addressList = + nodeList.stream().map(node -> node.getControl().createAddress()).collect(Collectors.toList()); + } else { + stream = INIT_ADDRESSES.get(clusterName).stream(); + } + if (addressList != null) { + return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); + } else { + Map map = new HashMap<>(); + if (CollectionUtils.isNotEmpty(nodeList)) { + for (Node node : nodeList) { + map.put(new InetSocketAddress(node.getTransaction().getHost(), node.getTransaction().getPort()).getAddress().getHostAddress() + + IP_PORT_SPLIT_CHAR + node.getTransaction().getPort(), node); + } + } + addressList = stream.map(inetSocketAddress -> { + String host = inetSocketAddress.getAddress().getHostAddress(); + Node node = map.get(host + IP_PORT_SPLIT_CHAR + inetSocketAddress.getPort()); + return host + IP_PORT_SPLIT_CHAR + + (node != null ? node.getControl().getPort() : inetSocketAddress.getPort()); + }).collect(Collectors.toList()); + return addressList.get(ThreadLocalRandom.current().nextInt(addressList.size())); + } + } + private static void acquireClusterMetaData(String clusterName, String group) throws RetryableException { + String tcAddress = queryHttpAddress(clusterName, group); + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + if (StringUtils.isNotBlank(tcAddress)) { + Map param = new HashMap<>(); + // param.put("group", group); + String response = null; + try (CloseableHttpResponse httpResponse = + HttpClientUtil.doGet("http://" + tcAddress + "/metadata/v1/config/cluster", param, header, 1000)) { + if (httpResponse != null) { + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + } + MetadataResponse metadataResponse; + if (StringUtils.isNotBlank(response)) { + try { + metadataResponse = OBJECT_MAPPER.readValue(response, MetadataResponse.class); + METADATA.refreshMetadata(clusterName, metadataResponse); + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + } catch (IOException e) { + throw new RetryableException(e.getMessage(), e); + } + } + } + + private static Map acquireClusterConfigData(String clusterName, String group, String configGroup) throws RetryableException { + String tcAddress = queryHttpAddress(clusterName, group); + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + if (StringUtils.isNotBlank(tcAddress)) { + Map param = new HashMap<>(); + param.put("group", configGroup); + String response = null; + try (CloseableHttpResponse httpResponse = + HttpClientUtil.doGet("http://" + tcAddress + "/metadata/v1/config/getAll", param, header, 1000)) { + if (httpResponse != null) { + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + } else if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + } + + ConfigDataResponse> configDataResponse; + if (StringUtils.isNotBlank(response)) { + try { + configDataResponse = OBJECT_MAPPER.readValue(response, new TypeReference>>() {}); + if(configDataResponse.getSuccess()) { + return configDataResponse.getResult(); + }else{ + throw new RetryableException(configDataResponse.getErrMsg()); + } + } catch (JsonProcessingException e) { + LOGGER.error(e.getMessage(), e); + } + } + } catch (IOException e) { + throw new RetryableException(e.getMessage(), e); + } + } + return null; + } + + protected static void startQueryMetadata() { + if (REFRESH_METADATA_EXECUTOR == null) { + synchronized (INIT_ADDRESSES) { + if (REFRESH_METADATA_EXECUTOR == null) { + REFRESH_METADATA_EXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new NamedThreadFactory("refreshMetadata", 1, true)); + REFRESH_METADATA_EXECUTOR.execute(() -> { + long metadataMaxAgeMs = FILE_CONFIG.getLong(ConfigurationKeys.CLIENT_METADATA_MAX_AGE_MS, 30000L); + long currentTime = System.currentTimeMillis(); + while (!CLOSED.get()) { + try { + // Forced refresh of metadata information after set age + boolean fetch = System.currentTimeMillis() - currentTime > metadataMaxAgeMs; + String clusterName = RAFT_CLUSTER; + if (!fetch) { + fetch = watch(); + } + // Cluster changes or reaches timeout refresh time + if (fetch) { + for (String group : METADATA.groups(clusterName)) { + try { + acquireClusterMetaData(clusterName, group); + } catch (Exception e) { + // prevents an exception from being thrown that causes the thread to break + if (e instanceof RetryableException) { + throw e; + } else { + LOGGER.error("failed to get the leader address,error: {}", e.getMessage()); + } + } + } + currentTime = System.currentTimeMillis(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("refresh seata cluster metadata time: {}", currentTime); + } + } + } catch (RetryableException e) { + LOGGER.error(e.getMessage(), e); + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + CLOSED.compareAndSet(false, true); + REFRESH_METADATA_EXECUTOR.shutdown(); + })); + } + } + } + } + + protected static void startQueryConfigData() { + if (REFRESH_CONFIG_EXECUTOR == null) { + synchronized (RaftConfigurationClient.class) { + if (REFRESH_CONFIG_EXECUTOR == null) { + REFRESH_CONFIG_EXECUTOR = new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue<>(), new NamedThreadFactory("refreshConfig", 1, true)); + REFRESH_CONFIG_EXECUTOR.execute(() -> { + long metadataMaxAgeMs = FILE_CONFIG.getLong(ConfigurationKeys.CLIENT_METADATA_MAX_AGE_MS, 30000L); + long currentTime = System.currentTimeMillis(); + while (!CONFIG_CLOSED.get()) { + try { + // Forced refresh of metadata information after set age + boolean fetch = System.currentTimeMillis() - currentTime > metadataMaxAgeMs; + if (!fetch) { + fetch = configWatch(); + } + // Cluster config changes or reaches timeout refresh time + if (fetch) { + try { + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); + if(CollectionUtils.isNotEmpty(configMap)) { + notifyConfigMayChange(configMap); + } + } catch (Exception e) { + // prevents an exception from being thrown that causes the thread to break + if (e instanceof RetryableException) { + throw e; + } else { + LOGGER.error("failed to get the config ,error: {}", e.getMessage()); + } + } + + currentTime = System.currentTimeMillis(); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("refresh seata cluster config time: {}", currentTime); + } + } + } catch (RetryableException e) { + LOGGER.error(e.getMessage(), e); + try { + Thread.sleep(1000); + } catch (InterruptedException ignored) { + } + } + } + }); + Runtime.getRuntime().addShutdownHook(new Thread(() -> { + CONFIG_CLOSED.compareAndSet(false, true); + REFRESH_CONFIG_EXECUTOR.shutdown(); + })); + } + } + } + } + private static boolean watch() throws RetryableException { + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + Map param = new HashMap<>(); + String clusterName = RAFT_CLUSTER; + Map groupTerms = METADATA.getClusterTerm(clusterName); + groupTerms.forEach((k, v) -> param.put(k, String.valueOf(v))); + for (String group : groupTerms.keySet()) { + String tcAddress = queryHttpAddress(clusterName, group); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + try (CloseableHttpResponse response = + HttpClientUtil.doPost("http://" + tcAddress + "/metadata/v1/watch", param, header, 30000)) { + if (response != null) { + StatusLine statusLine = response.getStatusLine(); + if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; + } + } catch (IOException e) { + LOGGER.error("watch cluster node: {}, fail: {}", tcAddress, e.getMessage()); + throw new RetryableException(e.getMessage(), e); + } + break; + } + return false; + } + + private static boolean configWatch() throws RetryableException { + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); + String tcAddress = queryHttpAddress(RAFT_CLUSTER, RAFT_GROUP); + Map param = new HashMap<>(); + param.put("group", CONFIG_GROUP); + if (isTokenExpired()) { + refreshToken(tcAddress); + } + if (StringUtils.isNotBlank(jwtToken)) { + header.put(AUTHORIZATION_HEADER, jwtToken); + } + try (CloseableHttpResponse response = + HttpClientUtil.doPost("http://" + tcAddress + "/metadata/v1/config/watch", param, header, 30000)) { + if (response != null) { + StatusLine statusLine = response.getStatusLine(); + if (statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_UNAUTHORIZED) { + if (StringUtils.isNotBlank(USERNAME) && StringUtils.isNotBlank(PASSWORD)) { + throw new RetryableException("Authentication failed!"); + } else { + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + return statusLine != null && statusLine.getStatusCode() == HttpStatus.SC_OK; + } + } catch (IOException e) { + LOGGER.error("watch cluster node: {}, fail: {}", tcAddress, e.getMessage()); + throw new RetryableException(e.getMessage(), e); + } + return false; + } + private static void initClusterMetaData() { + String clusterName = RAFT_CLUSTER; + String group = RAFT_GROUP; + if (!METADATA.containsGroup(clusterName)) { + String raftClusterAddress = FILE_CONFIG.getConfig(getRaftServerAddrKey()); + if (StringUtils.isNotBlank(raftClusterAddress)) { + List list = new ArrayList<>(); + String[] addresses = raftClusterAddress.split(","); + for (String address : addresses) { + String[] endpoint = address.split(IP_PORT_SPLIT_CHAR); + String host = endpoint[0]; + int port = Integer.parseInt(endpoint[1]); + list.add(new InetSocketAddress(host, port)); + } + if (CollectionUtils.isEmpty(list)) { + throw new SeataRuntimeException(ErrorCode.ERR_CONFIG, + "There are no valid raft addr! you should configure the correct [config.raft.server-addr] in the config file"); + } + INIT_ADDRESSES.put(clusterName, list); + // init jwt token + try { + refreshToken(queryHttpAddress(clusterName, group)); + } catch (Exception e) { + throw new RuntimeException("Init fetch token failed!", e); + } + // Refresh the metadata by initializing the address + try { + acquireClusterMetaData(clusterName, group); + }catch (RetryableException e) { + LOGGER.warn(e.getMessage(), e); + } + startQueryMetadata(); + } + } } + + @Override public String getTypeName() { - return null; + return CONFIG_TYPE; } @Override public boolean putConfig(String dataId, String content, long timeoutMills) { - return false; + throw new NotSupportYetException("not support operation putConfig"); } @Override public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { - return null; + String value = seataConfig.getProperty(dataId); + if (value == null) { + try { + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); + if (CollectionUtils.isNotEmpty(configMap)) { + value = configMap.get(dataId).toString(); + } + } catch (RetryableException e) { + LOGGER.error(e.getMessage()); + } + } + return value == null ? defaultValue : value; } @Override public boolean putConfigIfAbsent(String dataId, String content, long timeoutMills) { - return false; + throw new NotSupportYetException("not support atomic operation putConfigIfAbsent"); } @Override public boolean removeConfig(String dataId, long timeoutMills) { - return false; + throw new NotSupportYetException("not support operation removeConfig"); } @Override public void addConfigListener(String dataId, ConfigurationChangeListener listener) { - + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + ConfigStoreListener storeListener = new ConfigStoreListener(dataId, listener); + CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) + .put(listener, storeListener); } @Override public void removeConfigListener(String dataId, ConfigurationChangeListener listener) { - + if (StringUtils.isBlank(dataId) || listener == null) { + return; + } + Set configChangeListeners = getConfigListeners(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + for (ConfigurationChangeListener entry : configChangeListeners) { + if (listener.equals(entry)) { + ConfigStoreListener storeListener = null; + Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (configListeners != null) { + configListeners.remove(entry); + } + break; + } + } + } } @Override public Set getConfigListeners(String dataId) { - return null; + ConcurrentMap configListeners = CONFIG_LISTENERS_MAP.get(dataId); + if (CollectionUtils.isNotEmpty(configListeners)){ + return configListeners.keySet(); + } else { + return null; + } + } + + private static void notifyConfigMayChange(Map configMap) { + String configStr = ConfigStoreManager.convertConfig2Str(configMap); + CONFIG_LISTENER.onChangeEvent(new ConfigurationChangeEvent(CONFIG_GROUP, configStr)); + } + + + private static String getRaftUsernameKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, USERNAME_KEY); + } + private static String getRaftPasswordKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, PASSWORD_KEY); + } + private static String getRaftServerAddrKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, SERVER_ADDR_KEY); + } + + private static String getTokenExpireTimeInMillisecondsKey() { + return String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, ConfigurationKeys.FILE_ROOT_CONFIG, CONFIG_TYPE, TOKEN_VALID_TIME_MS_KEY); + } + + private static boolean isTokenExpired() { + if (tokenTimeStamp == -1) { + return true; + } + long tokenExpiredTime = tokenTimeStamp + TOKEN_EXPIRE_TIME_IN_MILLISECONDS; + return System.currentTimeMillis() >= tokenExpiredTime; + } + + private static void refreshToken(String tcAddress) throws RetryableException { + // if username and password is not in config , return + if (StringUtils.isBlank(USERNAME) || StringUtils.isBlank(PASSWORD)) { + return; + } + // get token and set it in cache + Map param = new HashMap<>(); + param.put(USERNAME_KEY, USERNAME); + param.put(PASSWORD_KEY, PASSWORD); + Map header = new HashMap<>(); + header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_JSON.getMimeType()); + String response = null; + tokenTimeStamp = System.currentTimeMillis(); + try (CloseableHttpResponse httpResponse = + HttpClientUtil.doPost("http://" + tcAddress + "/api/v1/auth/login", param, header, 1000)) { + if (httpResponse != null) { + if (httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK) { + response = EntityUtils.toString(httpResponse.getEntity(), StandardCharsets.UTF_8); + JsonNode jsonNode = OBJECT_MAPPER.readTree(response); + String codeStatus = jsonNode.get("code").asText(); + if (!StringUtils.equals(codeStatus, "200")) { + //authorized failed,throw exception to kill process + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + jwtToken = jsonNode.get("data").asText(); + } else { + //authorized failed,throw exception to kill process + throw new AuthenticationFailedException("Authentication failed! you should configure the correct username and password."); + } + } + } catch (IOException e) { + throw new RetryableException(e.getMessage(), e); + } + } + + private static class ConfigStoreListener implements ConfigurationChangeListener { + private final String dataId; + private final ConfigurationChangeListener listener; + + public ConfigStoreListener(String dataId, ConfigurationChangeListener listener) { + this.dataId = dataId; + this.listener = listener; + } + @Override + public void onChangeEvent(ConfigurationChangeEvent event) { + if (CONFIG_GROUP.equals(event.getDataId())) { + Properties seataConfigNew = new Properties(); + Map newConfigMap = ConfigStoreManager.convertConfigStr2Map(event.getNewValue()); + if (CollectionUtils.isNotEmpty(newConfigMap)) { + seataConfigNew.putAll(newConfigMap); + } + //Get all the monitored dataids and judge whether it has been modified + for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { + String listenedDataId = entry.getKey(); + String propertyOld = seataConfig.getProperty(listenedDataId, ""); + String propertyNew = seataConfigNew.getProperty(listenedDataId, ""); + if (!propertyOld.equals(propertyNew)) { + ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() + .setDataId(listenedDataId) + .setNewValue(propertyNew) + .setNamespace(CONFIG_GROUP) + .setChangeType(ConfigurationChangeType.MODIFY); + + // 通知ConfigurationCache + ConcurrentMap configListeners = entry.getValue(); + for (ConfigurationChangeListener configListener : configListeners.keySet()) { + configListener.onProcessEvent(newEvent); + } + } + } + seataConfig = seataConfigNew; + System.out.println(seataConfigNew); + return; + } + } } } diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java index beaa2f5adf7..b39d40b2325 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationProvider.java @@ -26,13 +26,12 @@ public class RaftConfigurationProvider implements ConfigurationProvider { @Override public Configuration provide() { + // todo : optimize String applicationType = System.getProperty(APPLICATION_TYPE_KEY); if (APPLICATION_TYPE_SERVER.equals(applicationType)){ return RaftConfigurationServer.getInstance(); - }else if (APPLICATION_TYPE_CLIENT.equals(applicationType)){ - return RaftConfigurationClient.getInstance(); }else{ - throw new IllegalArgumentException(String.format("Unknown application type: %s, it must be server or client", applicationType)); + return RaftConfigurationClient.getInstance(); } } } diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java index 7238c9a4c5f..9cd34ea5d6c 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java @@ -173,8 +173,6 @@ public void onChangeEvent(ConfigurationChangeEvent event) { seataConfig = seataConfigNew; return; } - // todo - // 如果不是当前分组的配置变更,则通知相应client端,配置变更了 // Compatible with old writing listener.onProcessEvent(event); } diff --git a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java new file mode 100644 index 00000000000..11b377bf3cb --- /dev/null +++ b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java @@ -0,0 +1,53 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.raft; + + +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.entity.ContentType; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.EntityUtils; +import org.apache.seata.common.util.HttpClientUtil; +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; + +public class RaftConfigurationClientTest { + @Test + public void testRaftConfigurationClient() { +// 创建一个后台线程 + Thread backgroundThread = new Thread(() -> RaftConfigurationClient.getInstance()); + + // 将线程设置为守护线程 + backgroundThread.setDaemon(true); + backgroundThread.start(); + + // 主线程保持运行 + try { + System.out.println("主线程正在运行..."); + Thread.sleep(60000); // 主线程睡眠60秒 + } catch (InterruptedException e) { + e.printStackTrace(); + } + System.out.println("主线程结束"); + + } +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java index aedaef11878..90d550f671d 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/SeataCoreEnvironmentPostProcessor.java @@ -67,6 +67,7 @@ public static void init() { PROPERTY_BEAN_MAP.put(CONFIG_ETCD3_PREFIX, ConfigEtcd3Properties.class); PROPERTY_BEAN_MAP.put(CONFIG_CUSTOM_PREFIX, ConfigCustomProperties.class); PROPERTY_BEAN_MAP.put(CONFIG_STORE_PREFIX, ConfigStoreProperties.class); + PROPERTY_BEAN_MAP.put(CONFIG_RAFT_PREFIX, ConfigRaftProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_CONSUL_PREFIX, RegistryConsulProperties.class); PROPERTY_BEAN_MAP.put(REGISTRY_ETCD3_PREFIX, RegistryEtcd3Properties.class); diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java index 63dabed0d26..3ffad062d9a 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java @@ -66,6 +66,7 @@ public interface StarterConstants { String CONFIG_FILE_PREFIX = CONFIG_PREFIX + ".file"; String CONFIG_CUSTOM_PREFIX = CONFIG_PREFIX + ".custom"; String CONFIG_STORE_PREFIX = CONFIG_PREFIX + ".db"; + String CONFIG_RAFT_PREFIX = CONFIG_PREFIX + ".raft"; String SERVER_PREFIX = SEATA_PREFIX + ".server"; String SERVER_UNDO_PREFIX = SERVER_PREFIX + ".undo"; diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java new file mode 100644 index 00000000000..17d123900b0 --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java @@ -0,0 +1,78 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.spring.boot.autoconfigure.properties.config; + +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.CONFIG_RAFT_PREFIX; + +@Component +@ConfigurationProperties(prefix = CONFIG_RAFT_PREFIX) +public class ConfigRaftProperties { + private String serverAddr; + + private Long metadataMaxAgeMs = 30000L; + + private String username; + + private String password; + + private Long tokenValidityInMilliseconds = 29 * 60 * 1000L; + + public Long getMetadataMaxAgeMs() { + return metadataMaxAgeMs; + } + + public void setMetadataMaxAgeMs(Long metadataMaxAgeMs) { + this.metadataMaxAgeMs = metadataMaxAgeMs; + } + + public String getUsername() { + return username; + } + + public void setUsername(String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public Long getTokenValidityInMilliseconds() { + return tokenValidityInMilliseconds; + } + + public void setTokenValidityInMilliseconds(Long tokenValidityInMilliseconds) { + this.tokenValidityInMilliseconds = tokenValidityInMilliseconds; + } + + public String getServerAddr() { + return serverAddr; + } + + public ConfigRaftProperties setServerAddr(String serverAddr) { + this.serverAddr = serverAddr; + return this; + } + +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java new file mode 100644 index 00000000000..8f8c12f513d --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.listener; + +import org.springframework.context.ApplicationEvent; + +/** + * The type ClusterConfigChangeEvent + */ +public class ClusterConfigChangeEvent extends ApplicationEvent { + + private String group; + + public ClusterConfigChangeEvent(Object source, String group) { + super(source); + this.group = group; + } + + public String getGroup() { + return group; + } + + public void setGroup(String group) { + this.group = group; + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java new file mode 100644 index 00000000000..4236a15bc71 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeListener.java @@ -0,0 +1,26 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.listener; + +public interface ClusterConfigChangeListener { + + /** + * cluster config change event + * @param event event + */ + void onChangeEvent(ClusterConfigChangeEvent event); +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java new file mode 100644 index 00000000000..04f49496a94 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.manager; + +import org.apache.seata.common.thread.NamedThreadFactory; +import org.apache.seata.server.cluster.listener.ClusterChangeEvent; +import org.apache.seata.server.cluster.listener.ClusterChangeListener; +import org.apache.seata.server.cluster.listener.ClusterConfigChangeEvent; +import org.apache.seata.server.cluster.listener.ClusterConfigChangeListener; +import org.apache.seata.server.cluster.watch.Watcher; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.event.EventListener; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; + +import javax.annotation.PostConstruct; +import javax.servlet.AsyncContext; +import javax.servlet.http.HttpServletResponse; +import java.util.Map; +import java.util.Optional; +import java.util.Queue; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; + + +/** + * + * The type of cluster config watcher manager. + */ +@Component +public class ClusterConfigWatcherManager implements ClusterConfigChangeListener { + private final Logger LOGGER = LoggerFactory.getLogger(getClass()); + + private static final Map>> WATCHERS = new ConcurrentHashMap<>(); + + private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = + new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("long-polling", 1)); + + @PostConstruct + public void init() { + // Responds to monitors that time out + scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> { + for (String group : WATCHERS.keySet()) { + Optional.ofNullable(WATCHERS.remove(group)) + .ifPresent(watchers -> watchers.parallelStream().forEach(watcher -> { + if (System.currentTimeMillis() >= watcher.getTimeout()) { + HttpServletResponse httpServletResponse = + (HttpServletResponse)((AsyncContext)watcher.getAsyncContext()).getResponse(); + watcher.setDone(true); + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + ((AsyncContext)watcher.getAsyncContext()).complete(); + } + if (!watcher.isDone()) { + // Re-register + registryWatcher(watcher); + } + })); + } + }, 1, 1, TimeUnit.SECONDS); + } + @Override + @EventListener + @Async + public void onChangeEvent(ClusterConfigChangeEvent event) { + Optional.ofNullable(WATCHERS.remove(event.getGroup())) + .ifPresent(watchers -> watchers.parallelStream().forEach(this::notify)); + } + + private void notify(Watcher watcher) { + AsyncContext asyncContext = (AsyncContext)watcher.getAsyncContext(); + HttpServletResponse httpServletResponse = (HttpServletResponse)asyncContext.getResponse(); + watcher.setDone(true); + LOGGER.info("notify cluster config change event to: {}", asyncContext.getRequest().getRemoteAddr()); + if (LOGGER.isDebugEnabled()) { + LOGGER.debug("notify cluster config change event to: {}", asyncContext.getRequest().getRemoteAddr()); + } + httpServletResponse.setStatus(HttpServletResponse.SC_OK); + asyncContext.complete(); + } + + public void registryWatcher(Watcher watcher) { + String group = watcher.getGroup(); + WATCHERS.computeIfAbsent(group, value -> new ConcurrentLinkedQueue<>()).add(watcher); + } +} diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java index 6aad5c5e2e6..8a25d56db01 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java @@ -35,11 +35,7 @@ import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.ConfigType; import org.apache.seata.config.ConfigurationFactory; -import org.apache.seata.config.store.rocksdb.RocksDBOptions; import org.apache.seata.core.serializer.SerializerType; -import org.apache.seata.discovery.registry.FileRegistryServiceImpl; -import org.apache.seata.discovery.registry.MultiRegistryFactory; -import org.apache.seata.discovery.registry.RegistryService; import org.apache.seata.server.cluster.raft.processor.ConfigOperationRequestProcessor; import org.apache.seata.server.cluster.raft.processor.PutNodeInfoRequestProcessor; import org.apache.seata.server.cluster.raft.serializer.JacksonBoltSerializer; @@ -47,10 +43,7 @@ import org.slf4j.LoggerFactory; import java.io.IOException; -import java.util.HashMap; -import java.util.Map; import java.util.Optional; -import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; @@ -133,6 +126,9 @@ public static void init() { } } public static void start() { + if (!RAFT_MODE){ + return; + } try { if (raftServer != null) { raftServer.start(); @@ -150,15 +146,17 @@ public static void start() { throw new RuntimeException("start raft node fail!"); } } + Runtime.getRuntime().addShutdownHook(new Thread(RaftConfigServerManager::destroy)); } - // todo 需要调用 + public static void destroy() { raftServer.close(); LOGGER.info("closed seata server raft cluster, group: {} ", group); Optional.ofNullable(rpcServer).ifPresent(RpcServer::shutdown); raftServer = null; rpcServer = null; + RAFT_MODE = false; INIT.set(false); } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java index eb209d7bdce..550cdf5a095 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigStateMachine.java @@ -47,7 +47,6 @@ import org.apache.seata.server.cluster.raft.snapshot.StoreSnapshotFile; import org.apache.seata.server.cluster.raft.snapshot.config.ConfigSnapshotFile; import org.apache.seata.server.cluster.raft.snapshot.metadata.ConfigLeaderMetadataSnapshotFile; -import org.apache.seata.server.cluster.raft.snapshot.metadata.LeaderMetadataSnapshotFile; import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java index 1d676cf0221..15d1874ef84 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java @@ -16,15 +16,24 @@ */ package org.apache.seata.server.cluster.raft.execute.config; +import org.apache.seata.common.holder.ObjectHolder; +import org.apache.seata.server.cluster.listener.ClusterConfigChangeEvent; import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; import org.apache.seata.server.cluster.raft.sync.msg.RaftBaseMsg; import org.apache.seata.server.cluster.raft.sync.msg.RaftConfigOperationSyncMsg; import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationEventPublisher; import java.util.Map; +import static org.apache.seata.common.Constants.OBJECT_KEY_SPRING_APPLICATION_CONTEXT; + public class ConfigOperationExecute extends AbstractRaftConfigMsgExecute { + private static final Logger LOGGER = LoggerFactory.getLogger(ConfigOperationExecute.class); + @Override public Object execute(RaftBaseMsg syncMsg) throws Throwable { RaftConfigOperationSyncMsg configSyncMsg = (RaftConfigOperationSyncMsg) syncMsg; @@ -50,11 +59,26 @@ private ConfigOperationResponse get(ConfigOperationDTO configOperation) { private ConfigOperationResponse put(ConfigOperationDTO configOperation) { Boolean success = configStoreManager.put(configOperation.getGroup(), configOperation.getKey(), configOperation.getValue()); + if (success) { + // ApplicationContext may not have been started at this point + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null){ + ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getGroup())); + } + LOGGER.info("config group: {}, config change event: {}", configOperation.getGroup(), configOperation.getOptType()); + } return success? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); } private ConfigOperationResponse delete(ConfigOperationDTO configOperation) { Boolean success = configStoreManager.delete(configOperation.getGroup(), configOperation.getKey()); + if (success) { + if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null){ + ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getGroup())); + } + LOGGER.info("config group: {}, config change event: {}", configOperation.getGroup(), configOperation.getOptType()); + } return success? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java index 02402ba4767..393a8054e13 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java @@ -95,4 +95,14 @@ public void setValue(Object value) { public static ConfigOperationDTO convertConfigRequest2Dto(ConfigOperationRequest request) { return new ConfigOperationDTO(request.getOptType(), request.getGroup(), request.getKey(), request.getValue()); } + + @Override + public String toString() { + return "ConfigOperationDTO{" + + "optType=" + optType + + ", group='" + group + '\'' + + ", key='" + key + '\'' + + ", value=" + value + + '}'; + } } diff --git a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java index 67527f8a96f..abf7166ea98 100644 --- a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java +++ b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java @@ -37,6 +37,7 @@ import org.apache.seata.config.ConfigurationFactory; import org.apache.seata.console.result.Result; import org.apache.seata.core.serializer.SerializerType; +import org.apache.seata.server.cluster.manager.ClusterConfigWatcherManager; import org.apache.seata.server.cluster.manager.ClusterWatcherManager; import org.apache.seata.server.cluster.raft.RaftConfigServer; import org.apache.seata.server.cluster.raft.RaftConfigServerManager; @@ -67,6 +68,9 @@ public class ClusterController { @Resource private ClusterWatcherManager clusterWatcherManager; + @Resource + private ClusterConfigWatcherManager clusterConfigWatcherManager; + private ServerProperties serverProperties; @Resource @@ -264,4 +268,13 @@ public void watch(HttpServletRequest request, @RequestParam Map }); } + @PostMapping("/config/watch") + public void watch(HttpServletRequest request, @RequestParam String group, + @RequestParam(defaultValue = "28000") int timeout) { + AsyncContext context = request.startAsync(); + context.setTimeout(0L); + Watcher watcher = new Watcher<>(group, context, timeout, 0L); + clusterConfigWatcherManager.registryWatcher(watcher); + } + } From fdb6cc3f397c32f2c0832bf055c755459cf85a25 Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Wed, 17 Jul 2024 16:18:55 +0800 Subject: [PATCH 03/34] bugfix: Add raft and rocksdbjni dependency in pom --- all/pom.xml | 5 ++++ build/pom.xml | 1 + config/seata-config-core/pom.xml | 1 - .../config/store/rocksdb/RocksDBFactory.java | 6 ++--- .../config/store/rocksdb/RocksDBOptions.java | 12 ++++++--- .../config/raft/RaftConfigurationClient.java | 16 ++++-------- .../config/raft/RaftConfigurationServer.java | 2 +- .../raft/RaftConfigurationClientTest.java | 26 +------------------ dependencies/pom.xml | 6 +++++ 9 files changed, 30 insertions(+), 45 deletions(-) diff --git a/all/pom.xml b/all/pom.xml index e25f728d8b5..9c63a4592c0 100644 --- a/all/pom.xml +++ b/all/pom.xml @@ -91,6 +91,11 @@ seata-config-spring-cloud ${project.version} + + org.apache.seata + seata-config-raft + ${project.version} + org.apache.seata seata-core diff --git a/build/pom.xml b/build/pom.xml index 2f072af7de1..309a45820c8 100644 --- a/build/pom.xml +++ b/build/pom.xml @@ -309,6 +309,7 @@ *-pom.xml **/db_store/** **/sessionStore/** + **/configStore/** **/root.data false diff --git a/config/seata-config-core/pom.xml b/config/seata-config-core/pom.xml index dd622265dc1..501276ff505 100644 --- a/config/seata-config-core/pom.xml +++ b/config/seata-config-core/pom.xml @@ -46,7 +46,6 @@ org.rocksdb rocksdbjni - 8.8.1 diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java index f920d509a2d..cd192b6926d 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java @@ -69,10 +69,10 @@ public static synchronized void close() { public static synchronized void destroy(String dbPath) { close(); - try { - RocksDB.destroyDB(dbPath, new Options()); + try(final Options opt = new Options()) { + RocksDB.destroyDB(dbPath, opt); }catch (RocksDBException e){ - LOGGER.error("RocksDB destroy error: {}",e.getMessage(),e); + LOGGER.error("RocksDB destroy error: {}", e.getMessage(), e); } } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java index d6077aaefdc..541cf90c078 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java @@ -59,10 +59,14 @@ public static boolean getDBDestroyOnShutdown() { private static Options buildOptions() { Options options = new Options(); - options.setCreateIfMissing(true); // 不存在则创建 - options.setKeepLogFileNum(1); // 只保留最新的一个日志文件 - options.setLogFileTimeToRoll(0); // 禁止基于时间的日志滚动 - options.setMaxLogFileSize(0); // 禁止基于大小的日志滚动 + // If the database does not exist, create it + options.setCreateIfMissing(true); + // Retain only the latest log file + options.setKeepLogFileNum(1); + // Disable log file rolling based on time + options.setLogFileTimeToRoll(0); + // Disable log file rolling based on size + options.setMaxLogFileSize(0); return options; } diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java index 523fcb6b24a..899b05a2adb 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java @@ -64,8 +64,8 @@ public class RaftConfigurationClient extends AbstractConfiguration { private static final String CONFIG_TYPE = "raft"; private static final String SERVER_ADDR_KEY = "serverAddr"; - private static final String RAFT_GROUP = RAFT_CONFIG_GROUP; // config - private static final String RAFT_CLUSTER = DEFAULT_SEATA_GROUP; // default + private static final String RAFT_GROUP = RAFT_CONFIG_GROUP; + private static final String RAFT_CLUSTER = DEFAULT_SEATA_GROUP; private static final String CONFIG_GROUP; private static final String USERNAME_KEY = "username"; private static final String PASSWORD_KEY = "password"; @@ -117,13 +117,6 @@ private RaftConfigurationClient() { } private static void initClientConfig() { - // acquire configs from server - // 0.发送/cluster获取raft集群 - // 1.向raft集群发送getAll请求 - // 2.等待Raft日志提交,leader从rocksdb中读取全部配置返回(保证一致性) - // 3.加载到seataConfig - // 4.定期轮询配置变更 - // 触发监听 try { Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); if (configMap != null) { @@ -302,6 +295,7 @@ protected static void startQueryMetadata() { }); Runtime.getRuntime().addShutdownHook(new Thread(() -> { CLOSED.compareAndSet(false, true); + // is there should be shutdown gracefully? REFRESH_METADATA_EXECUTOR.shutdown(); })); } @@ -486,7 +480,7 @@ public String getLatestConfig(String dataId, String defaultValue, long timeoutMi try { Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); if (CollectionUtils.isNotEmpty(configMap)) { - value = configMap.get(dataId).toString(); + value = configMap.get(dataId) == null ? null : configMap.get(dataId).toString(); } } catch (RetryableException e) { LOGGER.error(e.getMessage()); @@ -524,7 +518,6 @@ public void removeConfigListener(String dataId, ConfigurationChangeListener list if (CollectionUtils.isNotEmpty(configChangeListeners)) { for (ConfigurationChangeListener entry : configChangeListeners) { if (listener.equals(entry)) { - ConfigStoreListener storeListener = null; Map configListeners = CONFIG_LISTENERS_MAP.get(dataId); if (configListeners != null) { configListeners.remove(entry); @@ -644,6 +637,7 @@ public void onChangeEvent(ConfigurationChangeEvent event) { } } seataConfig = seataConfigNew; + // todo : test code remove System.out.println(seataConfigNew); return; } diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java index 9cd34ea5d6c..baa36178191 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java @@ -24,7 +24,7 @@ public class RaftConfigurationServer extends AbstractConfiguration { private static volatile RaftConfigurationServer instance; private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; private static ConfigStoreManager configStoreManager; - private static String CURRENT_GROUP; // current group of configuration + private static String CURRENT_GROUP; private static final String CONFIG_TYPE = "raft"; private static volatile Properties seataConfig = new Properties(); private static final int MAP_INITIAL_CAPACITY = 8; diff --git a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java index 11b377bf3cb..cbfa2ec6a40 100644 --- a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java +++ b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java @@ -16,38 +16,14 @@ */ package org.apache.seata.config.raft; - -import org.apache.http.HttpStatus; -import org.apache.http.client.methods.CloseableHttpResponse; -import org.apache.http.entity.ContentType; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; -import org.apache.seata.common.util.HttpClientUtil; import org.junit.jupiter.api.Test; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashMap; -import java.util.Map; + public class RaftConfigurationClientTest { @Test public void testRaftConfigurationClient() { -// 创建一个后台线程 - Thread backgroundThread = new Thread(() -> RaftConfigurationClient.getInstance()); - - // 将线程设置为守护线程 - backgroundThread.setDaemon(true); - backgroundThread.start(); - // 主线程保持运行 - try { - System.out.println("主线程正在运行..."); - Thread.sleep(60000); // 主线程睡眠60秒 - } catch (InterruptedException e) { - e.printStackTrace(); - } - System.out.println("主线程结束"); } } diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 717ad14019c..d25f26e81e5 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -77,6 +77,7 @@ 4.1.94.Final 4.0.3 1.6.7 + 8.8.1 3.16.3 1.27.1 @@ -195,6 +196,11 @@ bolt ${sofa.bolt.version} + + org.rocksdb + rocksdbjni + ${rocksdbjni.version} + com.alibaba fastjson From 7aed12e16c05500a11ed941a8730b6300a94a351 Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Wed, 17 Jul 2024 22:44:29 +0800 Subject: [PATCH 04/34] optimize: Add some unit test and fix upload config bug --- .../rocksdb/RocksDBConfigStoreManager.java | 2 +- .../config/store/rocksdb/RocksDBTest.java | 2 +- .../raft/RaftConfigurationClientTest.java | 6 ++- .../raft/RaftConfigurationServerTest.java | 6 +++ .../server/raft/RaftSyncMessageTest.java | 41 +++++++++++++++---- 5 files changed, 45 insertions(+), 12 deletions(-) diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java index 11326d2b8df..b4036c358c3 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -62,7 +62,7 @@ public class RocksDBConfigStoreManager extends AbstractConfigStoreManager { //====================================NON COMMON FILED=================================== private static volatile RocksDBConfigStoreManager instance; private final RocksDB rocksdb; - private static final List prefixList = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, + private static final List prefixList = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, CLIENT_PREFIX, SERVICE_PREFIX, STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX); public static RocksDBConfigStoreManager getInstance() { diff --git a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java index 7d1d2a52f14..76272e6bef6 100644 --- a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java +++ b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java @@ -33,7 +33,7 @@ class RocksDBTest { private static RocksDBConfigStoreManager configStoreManager; - private static String group = DEFAULT_STORE_GROUP; + private static final String group = DEFAULT_STORE_GROUP; @BeforeAll static void setUp() { configStoreManager = RocksDBConfigStoreManager.getInstance(); diff --git a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java index cbfa2ec6a40..f9f8c839c64 100644 --- a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java +++ b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationClientTest.java @@ -18,8 +18,10 @@ import org.junit.jupiter.api.Test; - - +/** + * The type Raft configuration of server test + * + */ public class RaftConfigurationClientTest { @Test public void testRaftConfigurationClient() { diff --git a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java index fb69b38e1ff..daa27e424da 100644 --- a/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java +++ b/config/seata-config-raft/src/test/java/org/apache/seata/config/raft/RaftConfigurationServerTest.java @@ -16,6 +16,8 @@ */ package org.apache.seata.config.raft; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; import org.junit.jupiter.api.Test; /** @@ -24,6 +26,10 @@ */ public class RaftConfigurationServerTest { + @Test + public void testRaftConfigurationServer() { + } + } diff --git a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java index 5510a190662..eb81c9d018e 100644 --- a/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java +++ b/server/src/test/java/org/apache/seata/server/raft/RaftSyncMessageTest.java @@ -17,24 +17,20 @@ package org.apache.seata.server.raft; import java.io.IOException; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import org.apache.seata.common.metadata.ClusterRole; import org.apache.seata.common.metadata.Node; import org.apache.seata.core.exception.TransactionException; import org.apache.seata.core.model.BranchType; +import org.apache.seata.server.cluster.raft.execute.config.ConfigOperationType; import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshot; -import org.apache.seata.server.cluster.raft.sync.msg.RaftBranchSessionSyncMsg; -import org.apache.seata.server.cluster.raft.sync.msg.RaftClusterMetadataMsg; -import org.apache.seata.server.cluster.raft.sync.msg.RaftGlobalSessionSyncMsg; -import org.apache.seata.server.cluster.raft.sync.msg.RaftSyncMessage; +import org.apache.seata.server.cluster.raft.sync.msg.*; import org.apache.seata.server.cluster.raft.sync.RaftSyncMessageSerializer; import org.apache.seata.server.cluster.raft.snapshot.RaftSnapshotSerializer; import org.apache.seata.server.cluster.raft.snapshot.session.RaftSessionSnapshot; import org.apache.seata.server.cluster.raft.sync.msg.dto.BranchTransactionDTO; +import org.apache.seata.server.cluster.raft.sync.msg.dto.ConfigOperationDTO; import org.apache.seata.server.cluster.raft.sync.msg.dto.GlobalTransactionDTO; import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; import org.apache.seata.server.session.GlobalSession; @@ -191,4 +187,33 @@ public void testRaftClusterMetadataSerialize() throws IOException { Assertions.assertEquals(ClusterRole.LEARNER,learner1.getRole()); } + @Test + public void testConfigSnapshotSerialize() throws IOException{ + Map configMap = new HashMap<>(); + configMap.put("config.type","file"); + configMap.put("store","file"); + + RaftSnapshot raftSnapshot = new RaftSnapshot(); + raftSnapshot.setBody(configMap); + raftSnapshot.setType(RaftSnapshot.SnapshotType.config); + byte[] msg = RaftSnapshotSerializer.encode(raftSnapshot); + RaftSnapshot raftSnapshot1 = RaftSnapshotSerializer.decode(msg); + HashMap configMap1 = (HashMap) raftSnapshot1.getBody(); + Assertions.assertEquals(configMap,configMap1); + } + + @Test + public void testConfigMsgSerialize() throws IOException{ + RaftSyncMessage raftSyncMessage = new RaftSyncMessage(); + ConfigOperationDTO configOperationDTO = new ConfigOperationDTO(ConfigOperationType.PUT, "group", "key", "value"); + RaftConfigOperationSyncMsg configSyncMsg = new RaftConfigOperationSyncMsg(configOperationDTO); + raftSyncMessage.setBody(configSyncMsg); + byte[] msg = RaftSyncMessageSerializer.encode(raftSyncMessage); + RaftSyncMessage raftSyncMessage1 = RaftSyncMessageSerializer.decode(msg); + Assertions.assertEquals(configSyncMsg.getMsgType(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getMsgType()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getKey(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getKey()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getValue(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getValue()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getGroup(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getGroup()); + Assertions.assertEquals(configSyncMsg.getConfigOperation().getOptType(), ((RaftConfigOperationSyncMsg)raftSyncMessage1.getBody()).getConfigOperation().getOptType()); + } } From 5c5ba4a813bb81814139517b7b0088e029f1581d Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Thu, 18 Jul 2024 19:09:59 +0800 Subject: [PATCH 05/34] bugfix: add log and tcc prefix config items --- .../rocksdb/RocksDBConfigStoreManager.java | 2 +- .../src/test/resources/registry.conf | 101 ++++++++++++++++++ 2 files changed, 102 insertions(+), 1 deletion(-) create mode 100644 config/seata-config-raft/src/test/resources/registry.conf diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java index b4036c358c3..e9b794ca6e5 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -63,7 +63,7 @@ public class RocksDBConfigStoreManager extends AbstractConfigStoreManager { private static volatile RocksDBConfigStoreManager instance; private final RocksDB rocksdb; private static final List prefixList = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, CLIENT_PREFIX, SERVICE_PREFIX, - STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX); + STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX, LOG_PREFIX, TCC_PREFIX); public static RocksDBConfigStoreManager getInstance() { if (instance == null) { diff --git a/config/seata-config-raft/src/test/resources/registry.conf b/config/seata-config-raft/src/test/resources/registry.conf new file mode 100644 index 00000000000..1da942ecf08 --- /dev/null +++ b/config/seata-config-raft/src/test/resources/registry.conf @@ -0,0 +1,101 @@ +# +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +registry { + # file 、nacos 、eureka、redis、zk、consul、etcd3、sofa + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + cluster = "default" + } + eureka { + serviceUrl = "http://localhost:8761/eureka" + application = "default" + weight = "1" + } + redis { + serverAddr = "localhost:6379" + db = "0" + } + zk { + cluster = "default" + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + consul { + cluster = "default" + serverAddr = "127.0.0.1:8500" + } + etcd3 { + cluster = "default" + serverAddr = "http://localhost:2379" + } + sofa { + serverAddr = "127.0.0.1:9603" + application = "default" + region = "DEFAULT_ZONE" + datacenter = "DefaultDataCenter" + cluster = "default" + group = "SEATA_GROUP" + addressWaitTime = "3000" + } + file { + name = "file.conf" + } +} + +config { + # file、nacos 、apollo、zk、consul、etcd3 + type = "file" + + nacos { + serverAddr = "localhost" + namespace = "" + } + consul { + serverAddr = "127.0.0.1:8500" + } + apollo { + appId = "seata-server" + apolloMeta = "http://192.168.1.204:8801" + } + zk { + serverAddr = "127.0.0.1:2181" + sessionTimeout = 6000 + connectTimeout = 2000 + } + etcd3 { + serverAddr = "http://localhost:2379" + } + file { + name = "file.conf" + } + db { + type = "rocksdb" + dir = "configStore" + destroyOnShutdown = false + group = "SEATA_GROUP" + } + raft { + serverAddr = "127.0.0.1:7091" + username = "seata" + password = "seata" + } +} From e048418eb7735c931cc419f7f0455887274edb7b Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Fri, 26 Jul 2024 13:53:25 +0800 Subject: [PATCH 06/34] optimize: rename db config key and add config unit tests --- .../seata/common/ConfigurationKeys.java | 2 +- .../boot/autoconfigure/StarterConstants.java | 3 +- .../config/ConfigRaftProperties.java | 12 ++-- .../config/test/RaftPropertiesTest.java | 55 +++++++++++++++++++ .../config/test/StorePropertiesTest.java | 34 +++++++++++- .../resources/application-test.properties | 11 ++++ .../cluster/raft/RaftConfigServerManager.java | 10 +++- 7 files changed, 118 insertions(+), 9 deletions(-) create mode 100644 seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/RaftPropertiesTest.java diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index db7e121ed91..89b96b43b4d 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -1012,7 +1012,7 @@ public interface ConfigurationKeys { */ String ROCKET_MQ_MSG_TIMEOUT = SERVER_PREFIX + "rocketmqMsgTimeout"; - String CONFIG_STORE_PREFIX = FILE_ROOT_PREFIX_CONFIG + "db" + FILE_CONFIG_SPLIT_CHAR; + String CONFIG_STORE_PREFIX = FILE_ROOT_PREFIX_CONFIG + "raft" + FILE_CONFIG_SPLIT_CHAR + "db" + FILE_CONFIG_SPLIT_CHAR; /** * The constant CONFIG_STORE_TYPE diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java index 3ffad062d9a..b2e3438a03a 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/StarterConstants.java @@ -65,9 +65,8 @@ public interface StarterConstants { String CONFIG_ZK_PREFIX = CONFIG_PREFIX + ".zk"; String CONFIG_FILE_PREFIX = CONFIG_PREFIX + ".file"; String CONFIG_CUSTOM_PREFIX = CONFIG_PREFIX + ".custom"; - String CONFIG_STORE_PREFIX = CONFIG_PREFIX + ".db"; String CONFIG_RAFT_PREFIX = CONFIG_PREFIX + ".raft"; - + String CONFIG_STORE_PREFIX = CONFIG_RAFT_PREFIX + ".db"; String SERVER_PREFIX = SEATA_PREFIX + ".server"; String SERVER_UNDO_PREFIX = SERVER_PREFIX + ".undo"; String SERVER_RAFT_PREFIX = SERVER_PREFIX + ".raft"; diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java index 17d123900b0..af2fff40e9b 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigRaftProperties.java @@ -38,32 +38,36 @@ public Long getMetadataMaxAgeMs() { return metadataMaxAgeMs; } - public void setMetadataMaxAgeMs(Long metadataMaxAgeMs) { + public ConfigRaftProperties setMetadataMaxAgeMs(Long metadataMaxAgeMs) { this.metadataMaxAgeMs = metadataMaxAgeMs; + return this; } public String getUsername() { return username; } - public void setUsername(String username) { + public ConfigRaftProperties setUsername(String username) { this.username = username; + return this; } public String getPassword() { return password; } - public void setPassword(String password) { + public ConfigRaftProperties setPassword(String password) { this.password = password; + return this; } public Long getTokenValidityInMilliseconds() { return tokenValidityInMilliseconds; } - public void setTokenValidityInMilliseconds(Long tokenValidityInMilliseconds) { + public ConfigRaftProperties setTokenValidityInMilliseconds(Long tokenValidityInMilliseconds) { this.tokenValidityInMilliseconds = tokenValidityInMilliseconds; + return this; } public String getServerAddr() { diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/RaftPropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/RaftPropertiesTest.java new file mode 100644 index 00000000000..f32997d5fec --- /dev/null +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/RaftPropertiesTest.java @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.spring.boot.autoconfigure.properties.config.test; + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ExtConfigurationProvider; +import org.apache.seata.config.FileConfiguration; +import org.apache.seata.spring.boot.autoconfigure.BasePropertiesTest; +import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigRaftProperties; +import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigStoreProperties; +import org.apache.seata.spring.boot.autoconfigure.provider.SpringApplicationContextProvider; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.mock; + + +@org.springframework.context.annotation.Configuration +@Import(SpringApplicationContextProvider.class) +public class RaftPropertiesTest extends BasePropertiesTest { + @Bean("testConfigRaftProperties") + public ConfigRaftProperties configRaftProperties() { + return new ConfigRaftProperties().setUsername(STR_TEST_AAA).setPassword(STR_TEST_BBB).setServerAddr(STR_TEST_CCC).setMetadataMaxAgeMs((long)LONG_TEST_ONE).setTokenValidityInMilliseconds((long)LONG_TEST_TWO); + } + + @Test + public void testConfigRaftProperties() { + FileConfiguration configuration = mock(FileConfiguration.class); + Configuration currentConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); + + assertEquals(STR_TEST_AAA, currentConfiguration.getConfig("config.raft.username")); + assertEquals(STR_TEST_BBB, currentConfiguration.getConfig("config.raft.password")); + assertEquals(STR_TEST_CCC, currentConfiguration.getConfig("config.raft.serverAddr")); + assertEquals(LONG_TEST_ONE, currentConfiguration.getInt("config.raft.metadataMaxAgeMs")); + assertEquals(LONG_TEST_TWO, currentConfiguration.getInt("config.raft.tokenValidityInMilliseconds")); + + } +} diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java index 900dbefc2e7..e7a9a674c6e 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java @@ -17,5 +17,37 @@ package org.apache.seata.spring.boot.autoconfigure.properties.config.test; -public class StorePropertiesTest { +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ExtConfigurationProvider; +import org.apache.seata.config.FileConfiguration; +import org.apache.seata.spring.boot.autoconfigure.BasePropertiesTest; +import org.apache.seata.spring.boot.autoconfigure.properties.config.ConfigStoreProperties; +import org.apache.seata.spring.boot.autoconfigure.provider.SpringApplicationContextProvider; +import org.junit.jupiter.api.Test; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.mockito.Mockito.mock; + +@org.springframework.context.annotation.Configuration +@Import(SpringApplicationContextProvider.class) +public class StorePropertiesTest extends BasePropertiesTest { + @Bean("testConfigStoreProperties") + public ConfigStoreProperties configStoreProperties() { + return new ConfigStoreProperties().setType(STR_TEST_AAA).setDir(STR_TEST_BBB).setDestroyOnShutdown(false).setGroup(STR_TEST_DDD); + } + + @Test + public void testConfigStoreProperties() { + FileConfiguration configuration = mock(FileConfiguration.class); + Configuration currentConfiguration = EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); + + assertEquals(STR_TEST_AAA, currentConfiguration.getConfig("config.raft.db.type")); + assertEquals(STR_TEST_BBB, currentConfiguration.getConfig("config.raft.db.dir")); + assertFalse(currentConfiguration.getBoolean("config.raft.db.destroyOnShutdown")); + assertEquals(STR_TEST_DDD, currentConfiguration.getConfig("config.raft.db.group")); + } } diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties index 8467d6cc7b9..6b9e012560b 100755 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/resources/application-test.properties @@ -51,3 +51,14 @@ seata.config.zk.password=ddd seata.config.zk.node-path=aaa seata.config.custom.name=aaa + +seata.config.raft.db.type=aaa +seata.config.raft.db.dir=bbb +seata.config.raft.db.destroy-on-shutdown=false +seata.config.raft.db.group=ddd + +seata.config.raft.username=aaa +seata.config.raft.password=bbb +seata.config.raft.server-addr=ccc +seata.config.raft.metadata-max-age-ms=1 +seata.config.raft.token-validity-in-milliseconds=2 diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java index 8a25d56db01..10cac302be0 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/RaftConfigServerManager.java @@ -52,6 +52,8 @@ import static org.apache.seata.common.ConfigurationKeys.SERVER_RAFT_ELECTION_TIMEOUT_MS; import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; import static org.apache.seata.common.DefaultValues.*; +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGEX_SPLIT_CHAR; +import static org.apache.seata.spring.boot.autoconfigure.StarterConstants.REGISTRY_PREFERED_NETWORKS; /** * The type to manager raft server of config center @@ -96,7 +98,13 @@ public static void init() { int port = Integer.parseInt(System.getProperty(SERVER_RAFT_PORT_CAMEL, "0")); PeerId serverId = null; // XID may be null when configuration center is not initialized. - String host = XID.getIpAddress() == null? NetUtil.getLocalIp() : XID.getIpAddress(); + String host = null; + if (XID.getIpAddress() == null){ + String preferredNetworks = CONFIG.getConfig(REGISTRY_PREFERED_NETWORKS); + host = StringUtils.isNotBlank(preferredNetworks) ? NetUtil.getLocalIp(preferredNetworks.split(REGEX_SPLIT_CHAR)) : NetUtil.getLocalIp(); + }else{ + host = XID.getIpAddress(); + } if (port <= 0) { // Highly available deployments require different nodes for (PeerId peer : initConf.getPeers()) { From 8f31636ba2cb77e187c4bf300b4e391598a97d24 Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Sun, 28 Jul 2024 20:05:56 +0800 Subject: [PATCH 07/34] optimize: rocksdb multi-tenant storage optimization --- .../seata/common/ConfigurationKeys.java | 9 +- .../org/apache/seata/common/Constants.java | 9 +- .../config/ConfigurationChangeEvent.java | 4 + .../store/AbstractConfigStoreManager.java | 20 +- .../config/store/ConfigStoreManager.java | 18 +- .../rocksdb/RocksDBConfigStoreManager.java | 300 ++++++++++++------ .../config/store/rocksdb/RocksDBFactory.java | 16 +- .../config/store/rocksdb/RocksDBOptions.java | 73 ----- .../store/rocksdb/RocksDBOptionsFactory.java | 127 ++++++++ .../config/store/rocksdb/RocksDBTest.java | 106 +++++-- .../config/raft/RaftConfigurationClient.java | 34 +- .../config/raft/RaftConfigurationServer.java | 30 +- .../config/ConfigStoreProperties.java | 20 +- .../config/test/StorePropertiesTest.java | 5 +- .../listener/ClusterConfigChangeEvent.java | 24 +- .../manager/ClusterConfigWatcherManager.java | 54 ++-- .../config/ConfigOperationExecute.java | 16 +- .../request/ConfigOperationRequest.java | 50 +-- .../snapshot/config/ConfigSnapshotFile.java | 4 +- .../raft/sync/msg/dto/ConfigOperationDTO.java | 46 ++- .../server/cluster/watch/ConfigWatcher.java | 89 ++++++ .../server/controller/ClusterController.java | 31 +- 22 files changed, 740 insertions(+), 345 deletions(-) delete mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java create mode 100644 server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java diff --git a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java index 89b96b43b4d..f6d46514880 100644 --- a/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java +++ b/common/src/main/java/org/apache/seata/common/ConfigurationKeys.java @@ -1030,8 +1030,11 @@ public interface ConfigurationKeys { String CONFIG_STORE_DESTROY_ON_SHUTDOWN = CONFIG_STORE_PREFIX + "destroyOnShutdown"; /** - * The constant CONFIG_STORE_GROUP + * The constant CONFIG_STORE_NAMESPACE */ - String CONFIG_STORE_GROUP = CONFIG_STORE_PREFIX + "group"; - + String CONFIG_STORE_NAMESPACE = CONFIG_STORE_PREFIX + "namespace"; + /** + * The constant CONFIG_STORE_DATA_ID + */ + String CONFIG_STORE_DATA_ID = CONFIG_STORE_PREFIX + "dataId"; } diff --git a/common/src/main/java/org/apache/seata/common/Constants.java b/common/src/main/java/org/apache/seata/common/Constants.java index 38d1ca87a40..a4f05d50e1c 100644 --- a/common/src/main/java/org/apache/seata/common/Constants.java +++ b/common/src/main/java/org/apache/seata/common/Constants.java @@ -236,9 +236,14 @@ public interface Constants { String APPLICATION_TYPE_CLIENT = "client"; /** - * The constant DEFAULT_DB_CONFIG_KEY in raft configuration + * The constant DEFAULT_STORE_NAMESPACE in raft configuration */ - String DEFAULT_STORE_GROUP = "SEATA_GROUP"; + + String DEFAULT_STORE_NAMESPACE = "default"; + /** + * The constant DEFAULT_STORE_DATA_ID in raft configuration + */ + String DEFAULT_STORE_DATA_ID = "seata.properties"; /** * The constant RAFT_CONFIG_GROUP diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java index 215805a3011..987038509c7 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/ConfigurationChangeEvent.java @@ -38,6 +38,10 @@ public ConfigurationChangeEvent(String dataId, String newValue) { this(dataId, DEFAULT_NAMESPACE, null, newValue, ConfigurationChangeType.MODIFY); } + public ConfigurationChangeEvent(String namespace, String dataId, String newValue) { + this(dataId, namespace, null, newValue, ConfigurationChangeType.MODIFY); + } + public ConfigurationChangeEvent(String dataId, String namespace, String oldValue, String newValue, ConfigurationChangeType type) { this.dataId = dataId; diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java index b44087bb967..915379261cd 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java @@ -27,48 +27,49 @@ */ public abstract class AbstractConfigStoreManager implements ConfigStoreManager { private static final Logger LOGGER = LoggerFactory.getLogger(AbstractConfigStoreManager.class); + @Override - public String get(String group, String key) { + public String get(String namespace, String dataId, String key) { return null; } @Override - public Map getAll(String group) { + public Map getAll(String namespace, String dataId) { return new HashMap<>(); } @Override - public Boolean put(String group, String key, Object value) { + public Boolean put(String namespace, String dataId, String key, Object value) { return Boolean.FALSE; } @Override - public Boolean delete(String group, String key) { + public Boolean delete(String namespace, String dataId, String key) { return Boolean.FALSE; } @Override - public Boolean putAll(String group, Map configMap) { + public Boolean putAll(String namespace, String dataId, Map configMap) { return Boolean.FALSE; } @Override - public Boolean deleteAll(String group) { + public Boolean deleteAll(String namespace, String dataId) { return Boolean.FALSE; } @Override - public Boolean isEmpty(String group) { + public Boolean isEmpty(String namespace, String dataId) { return Boolean.TRUE; } @Override - public Map getConfigMap() { + public Map> getConfigMap() { return new HashMap<>(); } @Override - public Boolean putConfigMap(Map configMap) { + public Boolean putConfigMap(Map> configMap) { return Boolean.FALSE; } @@ -82,5 +83,4 @@ public void destroy() {} public abstract void shutdown(); - } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java index 7745c99be44..762c1dd5e23 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java @@ -31,23 +31,23 @@ * */ public interface ConfigStoreManager { - String get(String group, String key); + String get(String namespace, String dataId, String key); - Map getAll(String group); + Map getAll(String namespace, String dataId); - Boolean put(String group, String key, Object value); + Boolean put(String namespace, String dataId, String key, Object value); - Boolean delete(String group, String key); + Boolean delete(String namespace, String dataId, String key); - Boolean putAll(String group, Map configMap); + Boolean putAll(String namespace, String dataId, Map configMap); - Boolean deleteAll(String group); + Boolean deleteAll(String namespace, String dataId); - Boolean isEmpty(String group); + Boolean isEmpty(String namespace, String dataId); - Map getConfigMap(); + Map> getConfigMap(); - Boolean putConfigMap(Map configMap); + Boolean putConfigMap(Map> configMap); Boolean clearData(); void destroy(); diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java index e9b794ca6e5..b9edc0247ce 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -35,7 +35,8 @@ import static org.apache.seata.common.ConfigurationKeys.*; -import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; +import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; +import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; @@ -47,21 +48,24 @@ public class RocksDBConfigStoreManager extends AbstractConfigStoreManager { private static final Logger LOGGER = LoggerFactory.getLogger(RocksDBConfigStoreManager.class); private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private static final String DB_PATH = RocksDBOptions.getDBPath(); - private static final String DEFAULT_GROUP = DEFAULT_STORE_GROUP; - private static String CURRENT_GROUP; + private static final String DB_PATH = RocksDBOptionsFactory.getDBPath(); + private static final String DEFAULT_NAMESPACE = DEFAULT_STORE_NAMESPACE; + private static final String DEFAULT_DATA_ID = DEFAULT_STORE_DATA_ID; + private static String CURRENT_DATA_ID; + private static String CURRENT_NAMESPACE; private static final String NAME_KEY = "name"; private static final String FILE_TYPE = "file"; private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - private static final Options DB_OPTIONS = RocksDBOptions.getOptions(); - private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); + private static final DBOptions DB_OPTIONS = RocksDBOptionsFactory.getDBOptions(); + private final Map LOCK_MAP = new ConcurrentHashMap<>(); private static final int MAP_INITIAL_CAPACITY = 8; - private static final ConcurrentMap> CONFIG_LISTENERS_MAP = new ConcurrentHashMap<>( + private static final ConcurrentMap>> CONFIG_LISTENERS_MAP = new ConcurrentHashMap<>( MAP_INITIAL_CAPACITY); //====================================NON COMMON FILED=================================== private static volatile RocksDBConfigStoreManager instance; - private final RocksDB rocksdb; + private RocksDB rocksdb; + private final Map columnFamilyHandleMap = new ConcurrentHashMap<>(); private static final List prefixList = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, CLIENT_PREFIX, SERVICE_PREFIX, STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX, LOG_PREFIX, TCC_PREFIX); @@ -78,18 +82,54 @@ public static RocksDBConfigStoreManager getInstance() { public RocksDBConfigStoreManager() { super(); - this.rocksdb = RocksDBFactory.getInstance(DB_PATH, DB_OPTIONS); - CURRENT_GROUP = FILE_CONFIG.getConfig(CONFIG_STORE_GROUP, DEFAULT_GROUP); + CURRENT_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_NAMESPACE); + CURRENT_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_DATA_ID); + openRocksDB(); maybeNeedLoadOriginConfig(); LOGGER.info("RocksDBConfigStoreManager initialized successfully"); - + } + + private void openRocksDB(){ + final List handles = new ArrayList<>(); + final List descriptors = new ArrayList<>(); + try (final Options options = new Options()){ + List cfs = RocksDB.listColumnFamilies(options, DB_PATH); + for (byte[] cf : cfs) { + String namespace = new String(cf); + descriptors.add(new ColumnFamilyDescriptor(cf, RocksDBOptionsFactory.getColumnFamilyOptionsMap(namespace))); + } + if (CollectionUtils.isEmpty(descriptors)) { + descriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, RocksDBOptionsFactory.getColumnFamilyOptionsMap(new String(RocksDB.DEFAULT_COLUMN_FAMILY)))); + } + this.rocksdb = RocksDBFactory.getInstance(DB_PATH, DB_OPTIONS, descriptors, handles); + for (ColumnFamilyHandle handle : handles) { + columnFamilyHandleMap.put(new String(handle.getName()), handle); + } + }catch (RocksDBException e){ + LOGGER.error("open rocksdb error", e); + } + } + + private ColumnFamilyHandle getOrCreateColumnFamilyHandle(String namespace) throws RocksDBException{ + ColumnFamilyHandle handle = columnFamilyHandleMap.get(namespace); + if (handle == null) { + synchronized (columnFamilyHandleMap) { + handle = columnFamilyHandleMap.get(namespace); + if (handle == null) { + handle = rocksdb.createColumnFamily(new ColumnFamilyDescriptor( + namespace.getBytes(DEFAULT_CHARSET), RocksDBOptionsFactory.getColumnFamilyOptionsMap(namespace))); + columnFamilyHandleMap.put(namespace, handle); + } + } + } + return handle; } /** * load origin config if first startup */ private void maybeNeedLoadOriginConfig() { - if (isEmpty(CURRENT_GROUP)){ + if (isEmpty(CURRENT_NAMESPACE, CURRENT_DATA_ID)){ Map configs = new HashMap<>(); Map seataConfigs = new HashMap<>(); String pathDataId = String.join(ConfigurationKeys.FILE_CONFIG_SPLIT_CHAR, @@ -110,16 +150,35 @@ private void maybeNeedLoadOriginConfig() { seataConfigs.put(k, v); } }); - putAll(CURRENT_GROUP, seataConfigs); + putAll(CURRENT_NAMESPACE, CURRENT_DATA_ID, seataConfigs); } } + /** + * acquire lock of the given namespace + * @param namespace + */ + private ReentrantReadWriteLock acquireLock(String namespace) { + namespace = StringUtils.isEmpty(namespace)? CURRENT_NAMESPACE : namespace; + return LOCK_MAP.computeIfAbsent(namespace, k -> new ReentrantReadWriteLock()); + } - private Map getConfigMap(String group) throws RocksDBException{ + /* + * Get config map of the given namespace and dataId + * + */ + private Map getConfigMap(String namespace, String dataId) throws RocksDBException{ + dataId = StringUtils.isEmpty(dataId)? CURRENT_DATA_ID : dataId; + namespace = StringUtils.isEmpty(namespace)? CURRENT_NAMESPACE : namespace; + ReentrantReadWriteLock lock = acquireLock(namespace); lock.readLock().lock(); try { - group = StringUtils.isEmpty(group)? CURRENT_GROUP : group; - byte[] value = rocksdb.get(group.getBytes(DEFAULT_CHARSET)); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + // the column family not exist, return empty map + if (handle == null) { + return new HashMap<>(); + } + byte[] value = rocksdb.get(handle, dataId.getBytes(DEFAULT_CHARSET)); String configStr = value != null ? new String(value, DEFAULT_CHARSET) : null; return ConfigStoreManager.convertConfigStr2Map(configStr); }finally { @@ -129,10 +188,11 @@ private Map getConfigMap(String group) throws RocksDBException{ @Override - public String get(String group, String key) { + public String get(String namespace, String dataId, String key) { + ReentrantReadWriteLock lock = acquireLock(namespace); lock.readLock().lock(); try { - Map configMap = getConfigMap(group); + Map configMap = getConfigMap(namespace, dataId); return configMap.get(key) != null ? configMap.get(key).toString() : null; }catch (RocksDBException e) { LOGGER.error("Failed to get value for key: " + key, e); @@ -143,9 +203,9 @@ public String get(String group, String key) { } @Override - public Map getAll(String group) { + public Map getAll(String namespace, String dataId) { try { - return getConfigMap(group); + return getConfigMap(namespace, dataId); }catch (RocksDBException e) { LOGGER.error("Failed to get all configs", e); } @@ -153,14 +213,16 @@ public Map getAll(String group) { } @Override - public Boolean put(String group, String key, Object value) { + public Boolean put(String namespace, String dataId, String key, Object value) { + ReentrantReadWriteLock lock = acquireLock(namespace); lock.writeLock().lock(); try { - Map configMap = getConfigMap(group); + Map configMap = getConfigMap(namespace, dataId); configMap.put(key, value); String configStr = ConfigStoreManager.convertConfig2Str(configMap); - rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); - notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); return true; }catch (RocksDBException e){ LOGGER.error("Failed to put value for key: " + key, e); @@ -171,14 +233,16 @@ public Boolean put(String group, String key, Object value) { } @Override - public Boolean delete(String group, String key) { + public Boolean delete(String namespace, String dataId, String key) { + ReentrantReadWriteLock lock = acquireLock(namespace); lock.writeLock().lock(); try { - Map configMap = getConfigMap(group); + Map configMap = getConfigMap(namespace, dataId); configMap.remove(key); String configStr = ConfigStoreManager.convertConfig2Str(configMap); - rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); - notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); return true; }catch (RocksDBException e){ LOGGER.error("Failed to delete value for key: " + key, e); @@ -189,12 +253,14 @@ public Boolean delete(String group, String key) { } @Override - public Boolean putAll(String group, Map configMap) { + public Boolean putAll(String namespace, String dataId, Map configMap) { + ReentrantReadWriteLock lock = acquireLock(namespace); lock.writeLock().lock(); try{ String configStr = ConfigStoreManager.convertConfig2Str(configMap); - rocksdb.put(group.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); - notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); return true; }catch (RocksDBException e){ LOGGER.error("Failed to put all configs", e); @@ -205,11 +271,13 @@ public Boolean putAll(String group, Map configMap) { } @Override - public Boolean deleteAll(String group) { + public Boolean deleteAll(String namespace, String dataId) { + ReentrantReadWriteLock lock = acquireLock(namespace); lock.writeLock().lock(); try { - rocksdb.delete(group.getBytes(DEFAULT_CHARSET)); - notifyConfigChange(group, new ConfigurationChangeEvent(group, null)); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + rocksdb.delete(handle, dataId.getBytes(DEFAULT_CHARSET)); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, null)); return true; } catch (RocksDBException e) { LOGGER.error("Failed to clear all configs", e); @@ -220,85 +288,125 @@ public Boolean deleteAll(String group) { } @Override - public Map getConfigMap() { - lock.readLock().lock(); - HashMap configMap = new HashMap<>(); - try (RocksIterator iterator = rocksdb.newIterator()) { - for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { - String key = new String(iterator.key(), DEFAULT_CHARSET); - String value = new String(iterator.value(), DEFAULT_CHARSET); - configMap.put(key, value); + public Map> getConfigMap() { + Map> configMap = new HashMap<>(); + for (String namespace : columnFamilyHandleMap.keySet()) { + HashMap configs = new HashMap<>(); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.readLock().lock(); + try ( + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + RocksIterator iterator = rocksdb.newIterator(handle)) { + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + String key = new String(iterator.key(), DEFAULT_CHARSET); + String value = new String(iterator.value(), DEFAULT_CHARSET); + configs.put(key, value); + } + configMap.put(namespace, configs); + } catch (RocksDBException e) { + LOGGER.error("Failed to get configMap in namespace : {}", namespace, e); + } finally { + lock.readLock().unlock(); } - return configMap; - } finally { - lock.readLock().unlock(); } + return configMap; } @Override - public Boolean putConfigMap(Map configMap) { - lock.writeLock().lock(); + public Boolean putConfigMap(Map> configMap) { try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { - for (Map.Entry entry : configMap.entrySet()) { - batch.put(entry.getKey().getBytes(DEFAULT_CHARSET), entry.getValue().toString().getBytes(DEFAULT_CHARSET)); + for (Map.Entry> entry : configMap.entrySet()) { + String namespace = entry.getKey(); + Map configs = entry.getValue(); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + for (Map.Entry nsEntry : configs .entrySet()) { + batch.put(handle, nsEntry.getKey().getBytes(DEFAULT_CHARSET), nsEntry.getValue().toString().getBytes(DEFAULT_CHARSET)); + } + }catch (RocksDBException e){ + LOGGER.error("Failed to put configMap in namespace : {}", namespace, e); + }finally { + lock.writeLock().unlock(); + } } rocksdb.write(writeOptions, batch); - for (Map.Entry entry : configMap.entrySet()) { - String group = entry.getKey(); - String configStr = (String) entry.getValue(); - notifyConfigChange(group, new ConfigurationChangeEvent(group, configStr)); + for (Map.Entry> entry : configMap.entrySet()) { + String namespace = entry.getKey(); + Map configs = entry.getValue(); + for (Map.Entry kv : configs.entrySet()) { + notifyConfigChange(namespace, kv.getKey(), new ConfigurationChangeEvent(namespace, kv.getKey(), kv.getValue().toString())); + } } return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to put values for multiple keys", e); - } finally { - lock.writeLock().unlock(); + }catch (RocksDBException e) { + LOGGER.error("Failed to put all configMap", e); + return false; } - return false; } @Override public Boolean clearData() { - lock.writeLock().lock(); - try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions(); - RocksIterator iterator = rocksdb.newIterator()) { - Set groupSet = new HashSet<>(); - for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { - batch.delete(iterator.key()); - groupSet.add(new String(iterator.key(), DEFAULT_CHARSET)); + Map> clearDataMap = new HashMap<>(); + try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { + for (ColumnFamilyHandle handle : columnFamilyHandleMap.values()) { + String namespace = new String(handle.getName()); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.writeLock().lock(); + HashSet deleteKeySet = new HashSet<>(); + try(RocksIterator iterator = rocksdb.newIterator(handle)) { + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + batch.delete(handle, iterator.key()); + deleteKeySet.add(new String(iterator.key())); + } + clearDataMap.put(namespace, deleteKeySet); + }finally { + lock.writeLock().unlock(); + } } rocksdb.write(writeOptions, batch); - for (String group : groupSet) { - notifyConfigChange(group, new ConfigurationChangeEvent(group, null)); + for (Map.Entry> entry : clearDataMap.entrySet()) { + String namespace = entry.getKey(); + for (String key : entry.getValue()) { + notifyConfigChange(namespace, key, new ConfigurationChangeEvent(namespace, key, null)); + } } return true; - } catch (RocksDBException e) { - LOGGER.error("Failed to clear the database", e); - } finally { - lock.writeLock().unlock(); + }catch (RocksDBException e) { + LOGGER.error("Failed to clear all data in rocksdb", e); + return false; } - return false; } @Override - public Boolean isEmpty(String group) { - return CollectionUtils.isEmpty(getAll(group)); + public Boolean isEmpty(String namespace, String dataId) { + return CollectionUtils.isEmpty(getAll(namespace, dataId)); } // todo @Override public void shutdown() { - lock.writeLock().lock(); - try { + synchronized (RocksDBConfigStoreManager.class){ + // 1. close all handles + for (ColumnFamilyHandle handle : columnFamilyHandleMap.values()) { + if (handle != null) { + handle.close(); + } + } + // 2. close options + RocksDBOptionsFactory.releaseAllOptions(); + // 3. close db RocksDBFactory.close(); - if (RocksDBOptions.getDBDestroyOnShutdown()) { + // 4. destroy db if needed + if (RocksDBOptionsFactory.getDBDestroyOnShutdown()) { destroy(); } + // 5. help gc + columnFamilyHandleMap.clear(); + this.rocksdb = null; LOGGER.info("RocksDBConfigStoreManager has shutdown"); - }finally { - lock.writeLock().unlock(); } - } @Override @@ -309,30 +417,38 @@ public void destroy() { @Override - public void addConfigListener(String group, String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(dataId) || listener == null) { + public void addConfigListener(String namespace, String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(namespace) || StringUtils.isBlank(dataId) || listener == null) { return; } - CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, k -> ConcurrentHashMap.newKeySet()) + Map> listenerMap = CONFIG_LISTENERS_MAP.computeIfAbsent(namespace, k -> new ConcurrentHashMap<>()); + listenerMap.computeIfAbsent(dataId, k -> ConcurrentHashMap.newKeySet()) .add(listener); } @Override - public void removeConfigListener(String group, String dataId, ConfigurationChangeListener listener) { - if (StringUtils.isBlank(dataId) || listener == null) { + public void removeConfigListener(String namespace, String dataId, ConfigurationChangeListener listener) { + if (StringUtils.isBlank(namespace) || StringUtils.isBlank(dataId) || listener == null) { return; } - Set configChangeListeners = CONFIG_LISTENERS_MAP.get(dataId); - if (CollectionUtils.isNotEmpty(configChangeListeners)) { - configChangeListeners.remove(listener); + // dataId -> listener + Map> listenerMap = CONFIG_LISTENERS_MAP.get(namespace); + if (CollectionUtils.isNotEmpty(listenerMap)) { + Set configChangeListeners = listenerMap.get(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + configChangeListeners.remove(listener); + } } } - private void notifyConfigChange(String dataId, ConfigurationChangeEvent event) { - Set configChangeListeners = CONFIG_LISTENERS_MAP.get(dataId); - if (CollectionUtils.isNotEmpty(configChangeListeners)) { - configChangeListeners.forEach(listener -> listener.onChangeEvent(event)); + private void notifyConfigChange(String namespace, String dataId, ConfigurationChangeEvent event) { + Map> listenerMap = CONFIG_LISTENERS_MAP.get(namespace); + if (CollectionUtils.isNotEmpty(listenerMap)) { + Set configChangeListeners = listenerMap.get(dataId); + if (CollectionUtils.isNotEmpty(configChangeListeners)) { + configChangeListeners.forEach(listener -> listener.onChangeEvent(event)); + } } } } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java index cd192b6926d..1a770d0226a 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBFactory.java @@ -16,14 +16,15 @@ */ package org.apache.seata.config.store.rocksdb; -import org.rocksdb.Options; -import org.rocksdb.RocksDB; -import org.rocksdb.RocksDBException; +import org.rocksdb.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; /** * The RocksDB Factory @@ -34,25 +35,26 @@ public class RocksDBFactory { private static volatile RocksDB instance = null; + static { RocksDB.loadLibrary(); } - public static RocksDB getInstance(String dbPath, Options options) { + public static RocksDB getInstance(String dbPath, DBOptions dbOptions, List columnFamilyDescriptors, List columnFamilyHandles) { if (instance == null) { synchronized (RocksDBFactory.class) { if (instance == null) { - instance = build(dbPath,options); + instance = build(dbPath, dbOptions, columnFamilyDescriptors, columnFamilyHandles); } } } return instance; } - private static RocksDB build(String dbPath, Options options) { + private static RocksDB build(String dbPath, DBOptions dbOptions, List columnFamilyDescriptors, List columnFamilyHandles) { try { checkPath(dbPath); - return RocksDB.open(options, dbPath); + return RocksDB.open(dbOptions, dbPath, columnFamilyDescriptors, columnFamilyHandles); }catch (RocksDBException | IOException e){ LOGGER.error("RocksDB open error: {}", e.getMessage(), e); return null; diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java deleted file mode 100644 index 541cf90c078..00000000000 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptions.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.apache.seata.config.store.rocksdb; - -import org.apache.seata.common.ConfigurationKeys; -import org.apache.seata.config.Configuration; -import org.apache.seata.config.ConfigurationFactory; -import org.rocksdb.Options; - -import static java.io.File.separator; -import static org.apache.seata.common.ConfigurationKeys.*; -import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; - - -/** - * The RocksDB options builder - * - */ -public class RocksDBOptions { - private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; - - public static final String ROCKSDB_SUFFIX = "rocksdb"; - - private static volatile Options options = null; - public static Options getOptions() { - if (options == null){ - synchronized (RocksDBOptions.class){ - if (options == null){ - options = buildOptions(); - } - } - } - return options; - } - - public static String getDBPath() { - String dir = FILE_CONFIG.getConfig(CONFIG_STORE_DIR); - String group = FILE_CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); - return String.join(separator, dir, group, ROCKSDB_SUFFIX); - } - - public static boolean getDBDestroyOnShutdown() { - return FILE_CONFIG.getBoolean(CONFIG_STORE_DESTROY_ON_SHUTDOWN, false); - } - - private static Options buildOptions() { - Options options = new Options(); - // If the database does not exist, create it - options.setCreateIfMissing(true); - // Retain only the latest log file - options.setKeepLogFileNum(1); - // Disable log file rolling based on time - options.setLogFileTimeToRoll(0); - // Disable log file rolling based on size - options.setMaxLogFileSize(0); - return options; - } - -} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java new file mode 100644 index 00000000000..8bfb7777ea5 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java @@ -0,0 +1,127 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store.rocksdb; + +import org.apache.seata.common.ConfigurationKeys; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; +import org.rocksdb.*; +import org.rocksdb.util.SizeUnit; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +import static java.io.File.separator; +import static org.apache.seata.common.ConfigurationKeys.*; +import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; + + +/** + * The RocksDB options builder + * + */ +public class RocksDBOptionsFactory { + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + + public static final String ROCKSDB_SUFFIX = "rocksdb"; + private static volatile DBOptions options = null; + + private static final Map columnFamilyOptionsMap = new ConcurrentHashMap<>(); + public static DBOptions getDBOptions() { + if (options == null){ + synchronized (RocksDBOptionsFactory.class){ + if (options == null){ + options = buildDBOptions(); + } + } + } + return options; + } + + public static ColumnFamilyOptions getColumnFamilyOptionsMap(final String namespace) { + ColumnFamilyOptions opts = columnFamilyOptionsMap.get(namespace); + if (opts == null){ + final ColumnFamilyOptions newOpts = buildColumnFamilyOptions(); + opts = columnFamilyOptionsMap.putIfAbsent(namespace, newOpts); + if (opts != null) { + newOpts.close(); + }else{ + opts = newOpts; + } + } + new ColumnFamilyOptions(opts); + return opts; + } + public static String getDBPath() { + String dir = FILE_CONFIG.getConfig(CONFIG_STORE_DIR); + String group = FILE_CONFIG.getConfig(ConfigurationKeys.SERVER_RAFT_GROUP, DEFAULT_SEATA_GROUP); + return String.join(separator, dir, group, ROCKSDB_SUFFIX); + } + + public static boolean getDBDestroyOnShutdown() { + return FILE_CONFIG.getBoolean(CONFIG_STORE_DESTROY_ON_SHUTDOWN, false); + } + + private static DBOptions buildDBOptions() { + final DBOptions options = new DBOptions(); + // If the database does not exist, create it + options.setCreateIfMissing(true); + // If true, missing column families will be automatically created. + options.setCreateMissingColumnFamilies(true); + // Retain only the latest log file + options.setKeepLogFileNum(1); + // Disable log file rolling based on time + options.setLogFileTimeToRoll(0); + // Disable log file rolling based on size + options.setMaxLogFileSize(0); + // Number of open files that can be used by the DB. + options.setMaxOpenFiles(-1); + return options; + } + + private static ColumnFamilyOptions buildColumnFamilyOptions() { + ColumnFamilyOptions columnFamilyOptions = new ColumnFamilyOptions(); + // set little write buffer size, since the size of config file is small + columnFamilyOptions.setWriteBufferSize(4 * SizeUnit.MB); + // Set memtable prefix bloom filter to reduce memory usage. + columnFamilyOptions.setMemtablePrefixBloomSizeRatio(0.125); + // Set compression type + columnFamilyOptions.setCompressionType(CompressionType.LZ4_COMPRESSION); + // Set compaction style + columnFamilyOptions.setCompactionStyle(CompactionStyle.LEVEL); + // Optimize level style compaction + columnFamilyOptions.optimizeLevelStyleCompaction(); + return columnFamilyOptions; + } + + public static void releaseAllOptions() { + // close all options + if (options != null){ + options.close(); + } + for (final ColumnFamilyOptions opts : columnFamilyOptionsMap.values()) { + if (opts != null){ + opts.close(); + } + } + // help gc + options = null; + columnFamilyOptionsMap.clear(); + } +} diff --git a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java index 76272e6bef6..2b854a60d04 100644 --- a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java +++ b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java @@ -16,24 +16,23 @@ */ package org.apache.seata.config.store.rocksdb; -import org.apache.seata.config.ConfigurationFactory; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import org.rocksdb.RocksDB; -import org.rocksdb.RocksDBException; import java.util.HashMap; +import java.util.Map; -import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; +import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; class RocksDBTest { private static RocksDBConfigStoreManager configStoreManager; - private static final String group = DEFAULT_STORE_GROUP; + private static final String dataId = "seata.properties"; + private static final String namespace = DEFAULT_STORE_NAMESPACE; @BeforeAll static void setUp() { configStoreManager = RocksDBConfigStoreManager.getInstance(); @@ -54,50 +53,105 @@ void getConfigStoreManagerTest() { @Test void crudTest() { - configStoreManager.deleteAll(group); + configStoreManager.deleteAll(namespace, dataId); String key = "aaa"; String value = "bbb"; String updateValue = "ccc"; - Assertions.assertTrue(configStoreManager.put(group, key, value)); - Assertions.assertEquals(value, configStoreManager.get(group, key)); - Assertions.assertTrue(configStoreManager.put(group, key, updateValue)); - Assertions.assertEquals(updateValue, configStoreManager.get(group, key)); - Assertions.assertTrue(configStoreManager.delete(group, key)); - Assertions.assertNull(configStoreManager.get(group, key)); + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, value)); + Assertions.assertEquals(value, configStoreManager.get(namespace, dataId, key)); + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, updateValue)); + Assertions.assertEquals(updateValue, configStoreManager.get(namespace, dataId, key)); + Assertions.assertTrue(configStoreManager.delete(namespace, dataId, key)); + Assertions.assertNull(configStoreManager.get(namespace, dataId, key)); } @Test void uploadConfigTest() { - configStoreManager.deleteAll(group); + configStoreManager.deleteAll(namespace, dataId); HashMap uploadConfigs = new HashMap<>(); uploadConfigs.put("aaa","111"); uploadConfigs.put("bbb","222"); - Assertions.assertTrue(configStoreManager.putAll(group, uploadConfigs)); - Assertions.assertEquals(uploadConfigs, configStoreManager.getAll(group)); - configStoreManager.deleteAll(group); - Assertions.assertTrue(configStoreManager.isEmpty(group)); + Assertions.assertTrue(configStoreManager.putAll(namespace, dataId, uploadConfigs)); + Assertions.assertEquals(uploadConfigs, configStoreManager.getAll(namespace, dataId)); + configStoreManager.deleteAll(namespace, dataId); + Assertions.assertTrue(configStoreManager.isEmpty(namespace, dataId)); } @Test void multiGroupTest() { - configStoreManager.deleteAll(group); + configStoreManager.deleteAll(namespace, dataId); String group1 = "group1"; String group2 = "group2"; String key = "aaa"; String value1 = "aaa"; String value2 = "bbb"; // put and get - Assertions.assertTrue(configStoreManager.put(group1, key, value1)); - Assertions.assertTrue(configStoreManager.put(group2, key, value2)); - Assertions.assertEquals(value1, configStoreManager.get(group1, key)); - Assertions.assertEquals(value2, configStoreManager.get(group2, key)); + Assertions.assertTrue(configStoreManager.put(namespace, group1, key, value1)); + Assertions.assertTrue(configStoreManager.put(namespace, group2, key, value2)); + Assertions.assertEquals(value1, configStoreManager.get(namespace, group1, key)); + Assertions.assertEquals(value2, configStoreManager.get(namespace, group2, key)); // delete - Assertions.assertTrue(configStoreManager.delete(group1, key)); - Assertions.assertTrue(configStoreManager.delete(group2, key)); - Assertions.assertNull(configStoreManager.get(group1, key)); - Assertions.assertNull(configStoreManager.get(group2, key)); + Assertions.assertTrue(configStoreManager.delete(namespace, group1, key)); + Assertions.assertTrue(configStoreManager.delete(namespace, group2, key)); + Assertions.assertNull(configStoreManager.get(namespace, group1, key)); + Assertions.assertNull(configStoreManager.get(namespace, group2, key)); + } + + + @Test + void multiNamespaceAndGroupTest() { + configStoreManager.clearData(); + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String dataId1 = "dataId1"; + String dataId2 = "dataId2"; + String key = "aaa"; + // put and get + Assertions.assertTrue(configStoreManager.put(namespace1, dataId1, key , "11")); + Assertions.assertTrue(configStoreManager.put(namespace1, dataId2, key , "12")); + Assertions.assertTrue(configStoreManager.put(namespace2, dataId1, key , "21")); + Assertions.assertTrue(configStoreManager.put(namespace2, dataId2, key , "22")); + Assertions.assertEquals("11", configStoreManager.get(namespace1, dataId1, key)); + Assertions.assertEquals("12", configStoreManager.get(namespace1, dataId2, key)); + Assertions.assertEquals("21", configStoreManager.get(namespace2, dataId1, key)); + Assertions.assertEquals("22", configStoreManager.get(namespace2, dataId2, key)); + + // delete + Assertions.assertTrue(configStoreManager.delete(namespace1, dataId1, key)); + Assertions.assertTrue(configStoreManager.delete(namespace1, dataId2, key)); + Assertions.assertTrue(configStoreManager.delete(namespace2, dataId1, key)); + Assertions.assertTrue(configStoreManager.delete(namespace2, dataId2, key)); + Assertions.assertNull(configStoreManager.get(namespace1, dataId1, key)); + Assertions.assertNull(configStoreManager.get(namespace1, dataId2, key)); + Assertions.assertNull(configStoreManager.get(namespace2, dataId1, key)); + Assertions.assertNull(configStoreManager.get(namespace2, dataId2, key)); + } + + @Test + void uploadTest() { + configStoreManager.clearData(); + String namespace1 = "namespace1"; + String namespace2 = "namespace2"; + String dataId1 = "dataId1"; + String dataId2 = "dataId2"; + HashMap> configMap = new HashMap>(); + HashMap map1 = new HashMap() {{ + put(dataId1, "11"); + put(dataId2, "12"); + }}; + HashMap map2 = new HashMap() {{ + put(dataId1, "21"); + put(dataId2, "22"); + }}; + configMap.put(namespace1,map1); + configMap.put(namespace2,map2); + // ensure default namespace + configMap.put("default",new HashMap<>()); + Assertions.assertTrue(configStoreManager.putConfigMap(configMap)); + Map> other = configStoreManager.getConfigMap(); + Assertions.assertEquals(other, configMap); } } diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java index 899b05a2adb..88f470be446 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java @@ -50,9 +50,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_GROUP; -import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; -import static org.apache.seata.common.Constants.RAFT_CONFIG_GROUP; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; +import static org.apache.seata.common.Constants.*; import static org.apache.seata.common.DefaultValues.DEFAULT_SEATA_GROUP; /** @@ -66,7 +66,8 @@ public class RaftConfigurationClient extends AbstractConfiguration { private static final String SERVER_ADDR_KEY = "serverAddr"; private static final String RAFT_GROUP = RAFT_CONFIG_GROUP; private static final String RAFT_CLUSTER = DEFAULT_SEATA_GROUP; - private static final String CONFIG_GROUP; + private static final String CONFIG_NAMESPACE; + private static final String CONFIG_DATA_ID; private static final String USERNAME_KEY = "username"; private static final String PASSWORD_KEY = "password"; private static final String AUTHORIZATION_HEADER = "Authorization"; @@ -97,7 +98,8 @@ public class RaftConfigurationClient extends AbstractConfiguration { USERNAME = FILE_CONFIG.getConfig(getRaftUsernameKey()); PASSWORD = FILE_CONFIG.getConfig(getRaftPasswordKey()); TOKEN_EXPIRE_TIME_IN_MILLISECONDS = FILE_CONFIG.getLong(getTokenExpireTimeInMillisecondsKey(), 29 * 60 * 1000L); - CONFIG_GROUP = FILE_CONFIG.getConfig(CONFIG_STORE_GROUP, DEFAULT_STORE_GROUP); + CONFIG_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE); + CONFIG_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); } public static String jwtToken; public static RaftConfigurationClient getInstance() { @@ -118,11 +120,11 @@ private RaftConfigurationClient() { private static void initClientConfig() { try { - Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); if (configMap != null) { seataConfig.putAll(configMap); } - CONFIG_LISTENER = new ConfigStoreListener(CONFIG_GROUP, null); + CONFIG_LISTENER = new ConfigStoreListener(CONFIG_NAMESPACE, null); startQueryConfigData(); }catch (RetryableException e){ LOGGER.error("init config properties error", e); @@ -200,7 +202,7 @@ private static void acquireClusterMetaData(String clusterName, String group) thr } } - private static Map acquireClusterConfigData(String clusterName, String group, String configGroup) throws RetryableException { + private static Map acquireClusterConfigData(String clusterName, String group, String configNamespace, String configDataId) throws RetryableException { String tcAddress = queryHttpAddress(clusterName, group); Map header = new HashMap<>(); header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); @@ -212,7 +214,8 @@ private static Map acquireClusterConfigData(String clusterName, } if (StringUtils.isNotBlank(tcAddress)) { Map param = new HashMap<>(); - param.put("group", configGroup); + param.put("namespace", configNamespace); + param.put("dataId", configDataId); String response = null; try (CloseableHttpResponse httpResponse = HttpClientUtil.doGet("http://" + tcAddress + "/metadata/v1/config/getAll", param, header, 1000)) { @@ -322,7 +325,7 @@ protected static void startQueryConfigData() { // Cluster config changes or reaches timeout refresh time if (fetch) { try { - Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); if(CollectionUtils.isNotEmpty(configMap)) { notifyConfigMayChange(configMap); } @@ -399,7 +402,8 @@ private static boolean configWatch() throws RetryableException { header.put(HTTP.CONTENT_TYPE, ContentType.APPLICATION_FORM_URLENCODED.getMimeType()); String tcAddress = queryHttpAddress(RAFT_CLUSTER, RAFT_GROUP); Map param = new HashMap<>(); - param.put("group", CONFIG_GROUP); + param.put("namespace", CONFIG_NAMESPACE); + param.put("dataId", CONFIG_DATA_ID); if (isTokenExpired()) { refreshToken(tcAddress); } @@ -478,7 +482,7 @@ public String getLatestConfig(String dataId, String defaultValue, long timeoutMi String value = seataConfig.getProperty(dataId); if (value == null) { try { - Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_GROUP); + Map configMap = acquireClusterConfigData(RAFT_CLUSTER, RAFT_GROUP, CONFIG_NAMESPACE, CONFIG_DATA_ID); if (CollectionUtils.isNotEmpty(configMap)) { value = configMap.get(dataId) == null ? null : configMap.get(dataId).toString(); } @@ -540,7 +544,7 @@ public Set getConfigListeners(String dataId) { private static void notifyConfigMayChange(Map configMap) { String configStr = ConfigStoreManager.convertConfig2Str(configMap); - CONFIG_LISTENER.onChangeEvent(new ConfigurationChangeEvent(CONFIG_GROUP, configStr)); + CONFIG_LISTENER.onChangeEvent(new ConfigurationChangeEvent(CONFIG_NAMESPACE, configStr)); } @@ -611,7 +615,7 @@ public ConfigStoreListener(String dataId, ConfigurationChangeListener listener) } @Override public void onChangeEvent(ConfigurationChangeEvent event) { - if (CONFIG_GROUP.equals(event.getDataId())) { + if (CONFIG_NAMESPACE.equals(event.getDataId())) { Properties seataConfigNew = new Properties(); Map newConfigMap = ConfigStoreManager.convertConfigStr2Map(event.getNewValue()); if (CollectionUtils.isNotEmpty(newConfigMap)) { @@ -626,7 +630,7 @@ public void onChangeEvent(ConfigurationChangeEvent event) { ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() .setDataId(listenedDataId) .setNewValue(propertyNew) - .setNamespace(CONFIG_GROUP) + .setNamespace(CONFIG_NAMESPACE) .setChangeType(ConfigurationChangeType.MODIFY); // 通知ConfigurationCache diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java index baa36178191..3c56e87eb0b 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java @@ -24,7 +24,8 @@ public class RaftConfigurationServer extends AbstractConfiguration { private static volatile RaftConfigurationServer instance; private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; private static ConfigStoreManager configStoreManager; - private static String CURRENT_GROUP; + private static String CURRENT_NAMESPACE; + private static String CURRENT_DATA_ID; private static final String CONFIG_TYPE = "raft"; private static volatile Properties seataConfig = new Properties(); private static final int MAP_INITIAL_CAPACITY = 8; @@ -34,13 +35,14 @@ public class RaftConfigurationServer extends AbstractConfiguration { private static void initServerConfig() { String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); configStoreManager = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); - CURRENT_GROUP = FILE_CONFIG.getConfig(CONFIG_STORE_GROUP, DEFAULT_STORE_GROUP); + CURRENT_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE); + CURRENT_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); // load config from store - Map configMap = configStoreManager.getAll(CURRENT_GROUP); + Map configMap = configStoreManager.getAll(CURRENT_NAMESPACE, CURRENT_DATA_ID); seataConfig.putAll(configMap); // build listener - ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_GROUP, null); - configStoreManager.addConfigListener(CURRENT_GROUP, CURRENT_GROUP, storeListener); + ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_NAMESPACE, CURRENT_DATA_ID, null); + configStoreManager.addConfigListener(CURRENT_NAMESPACE, CURRENT_DATA_ID, storeListener); } @@ -73,7 +75,7 @@ public boolean putConfig(String dataId, String content, long timeoutMills) { public String getLatestConfig(String dataId, String defaultValue, long timeoutMills) { String value = seataConfig.getProperty(dataId); if (value == null) { - value = configStoreManager.get(CURRENT_GROUP, dataId); + value = configStoreManager.get(CURRENT_NAMESPACE, CURRENT_DATA_ID, dataId); } return value == null ? defaultValue : value; } @@ -93,10 +95,10 @@ public void addConfigListener(String dataId, ConfigurationChangeListener listene if (StringUtils.isBlank(dataId) || listener == null) { return; } - ConfigStoreListener storeListener = new ConfigStoreListener(dataId, listener); + ConfigStoreListener storeListener = new ConfigStoreListener(CURRENT_NAMESPACE, dataId, listener); CONFIG_LISTENERS_MAP.computeIfAbsent(dataId, key -> new ConcurrentHashMap<>()) .put(listener, storeListener); - configStoreManager.addConfigListener(CURRENT_GROUP, dataId, storeListener); + configStoreManager.addConfigListener(CURRENT_NAMESPACE, dataId, storeListener); } @Override @@ -115,7 +117,7 @@ public void removeConfigListener(String dataId, ConfigurationChangeListener list configListeners.remove(entry); } if (storeListener != null) { - configStoreManager.removeConfigListener(CURRENT_GROUP, dataId, storeListener); + configStoreManager.removeConfigListener(CURRENT_NAMESPACE, dataId, storeListener); } break; } @@ -138,19 +140,21 @@ public Set getConfigListeners(String dataId) { * the type config change listener for raft config store */ private static class ConfigStoreListener implements ConfigurationChangeListener { + private final String namespace; private final String dataId; private final ConfigurationChangeListener listener; - public ConfigStoreListener(String dataId, ConfigurationChangeListener listener) { + public ConfigStoreListener(String namespace, String dataId, ConfigurationChangeListener listener) { + this.namespace = namespace; this.dataId = dataId; this.listener = listener; } @Override public void onChangeEvent(ConfigurationChangeEvent event) { - if (CURRENT_GROUP.equals(event.getDataId())) { + if (CURRENT_DATA_ID.equals(event.getDataId())) { Properties seataConfigNew = new Properties(); - seataConfigNew.putAll(configStoreManager.getAll(CURRENT_GROUP)); + seataConfigNew.putAll(configStoreManager.getAll(CURRENT_NAMESPACE, CURRENT_DATA_ID)); //Get all the monitored dataids and judge whether it has been modified for (Map.Entry> entry : CONFIG_LISTENERS_MAP.entrySet()) { @@ -161,7 +165,7 @@ public void onChangeEvent(ConfigurationChangeEvent event) { ConfigurationChangeEvent newEvent = new ConfigurationChangeEvent() .setDataId(listenedDataId) .setNewValue(propertyNew) - .setNamespace(CURRENT_GROUP) + .setNamespace(CURRENT_NAMESPACE) .setChangeType(ConfigurationChangeType.MODIFY); ConcurrentMap configListeners = entry.getValue(); diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java index b68a43d8c3a..fa853b77cbc 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/main/java/org/apache/seata/spring/boot/autoconfigure/properties/config/ConfigStoreProperties.java @@ -31,7 +31,8 @@ public class ConfigStoreProperties { private String type = "rocksdb"; private String dir = "configStore"; private boolean destroyOnShutdown = false; - private String group = "SEATA_GROUP"; + private String namespace = "SEATA_GROUP"; + private String dataId = "seata.properties"; public String getType() { return type; @@ -60,12 +61,21 @@ public ConfigStoreProperties setDestroyOnShutdown(boolean destroyOnShutdown) { return this; } - public String getGroup() { - return group; + public String getNamespace() { + return namespace; } - public ConfigStoreProperties setGroup(String group) { - this.group = group; + public ConfigStoreProperties setNamespace(String namespace) { + this.namespace = namespace; + return this; + } + + public String getDataId() { + return dataId; + } + + public ConfigStoreProperties setDataId(String dataId) { + this.dataId = dataId; return this; } } diff --git a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java index e7a9a674c6e..1c9e83c9a85 100644 --- a/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java +++ b/seata-spring-autoconfigure/seata-spring-autoconfigure-core/src/test/java/org/apache/seata/spring/boot/autoconfigure/properties/config/test/StorePropertiesTest.java @@ -37,7 +37,7 @@ public class StorePropertiesTest extends BasePropertiesTest { @Bean("testConfigStoreProperties") public ConfigStoreProperties configStoreProperties() { - return new ConfigStoreProperties().setType(STR_TEST_AAA).setDir(STR_TEST_BBB).setDestroyOnShutdown(false).setGroup(STR_TEST_DDD); + return new ConfigStoreProperties().setType(STR_TEST_AAA).setDir(STR_TEST_BBB).setDestroyOnShutdown(false).setNamespace(STR_TEST_DDD).setDataId(STR_TEST_EEE); } @Test @@ -48,6 +48,7 @@ public void testConfigStoreProperties() { assertEquals(STR_TEST_AAA, currentConfiguration.getConfig("config.raft.db.type")); assertEquals(STR_TEST_BBB, currentConfiguration.getConfig("config.raft.db.dir")); assertFalse(currentConfiguration.getBoolean("config.raft.db.destroyOnShutdown")); - assertEquals(STR_TEST_DDD, currentConfiguration.getConfig("config.raft.db.group")); + assertEquals(STR_TEST_DDD, currentConfiguration.getConfig("config.raft.db.namespace")); + assertEquals(STR_TEST_EEE, currentConfiguration.getConfig("config.raft.db.dataId")); } } diff --git a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java index 8f8c12f513d..fd9461bcdbd 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java +++ b/server/src/main/java/org/apache/seata/server/cluster/listener/ClusterConfigChangeEvent.java @@ -23,18 +23,28 @@ */ public class ClusterConfigChangeEvent extends ApplicationEvent { - private String group; + private String namespace; + private String dataId; - public ClusterConfigChangeEvent(Object source, String group) { + public ClusterConfigChangeEvent(Object source, String namespace, String dataId) { super(source); - this.group = group; + this.namespace = namespace; + this.dataId = dataId; } - public String getGroup() { - return group; + public String getNamespace() { + return namespace; } - public void setGroup(String group) { - this.group = group; + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; } } diff --git a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java index 04f49496a94..366b898b7b5 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java +++ b/server/src/main/java/org/apache/seata/server/cluster/manager/ClusterConfigWatcherManager.java @@ -17,11 +17,9 @@ package org.apache.seata.server.cluster.manager; import org.apache.seata.common.thread.NamedThreadFactory; -import org.apache.seata.server.cluster.listener.ClusterChangeEvent; -import org.apache.seata.server.cluster.listener.ClusterChangeListener; import org.apache.seata.server.cluster.listener.ClusterConfigChangeEvent; import org.apache.seata.server.cluster.listener.ClusterConfigChangeListener; -import org.apache.seata.server.cluster.watch.Watcher; +import org.apache.seata.server.cluster.watch.ConfigWatcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.event.EventListener; @@ -48,7 +46,7 @@ public class ClusterConfigWatcherManager implements ClusterConfigChangeListener { private final Logger LOGGER = LoggerFactory.getLogger(getClass()); - private static final Map>> WATCHERS = new ConcurrentHashMap<>(); + private static final Map>>> WATCHERS = new ConcurrentHashMap<>(); private final ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("long-polling", 1)); @@ -57,21 +55,24 @@ public class ClusterConfigWatcherManager implements ClusterConfigChangeListener public void init() { // Responds to monitors that time out scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> { - for (String group : WATCHERS.keySet()) { - Optional.ofNullable(WATCHERS.remove(group)) - .ifPresent(watchers -> watchers.parallelStream().forEach(watcher -> { - if (System.currentTimeMillis() >= watcher.getTimeout()) { - HttpServletResponse httpServletResponse = - (HttpServletResponse)((AsyncContext)watcher.getAsyncContext()).getResponse(); - watcher.setDone(true); - httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); - ((AsyncContext)watcher.getAsyncContext()).complete(); - } - if (!watcher.isDone()) { - // Re-register - registryWatcher(watcher); - } - })); + for (String namespace : WATCHERS.keySet()) { + Map>> dataIdWatchersMap = WATCHERS.get(namespace); + for (String dataId : dataIdWatchersMap.keySet()) { + Optional.ofNullable(dataIdWatchersMap.remove(dataId)) + .ifPresent(watchers -> watchers.parallelStream().forEach(watcher -> { + if (System.currentTimeMillis() >= watcher.getTimeout()) { + HttpServletResponse httpServletResponse = + (HttpServletResponse)((AsyncContext)watcher.getAsyncContext()).getResponse(); + watcher.setDone(true); + httpServletResponse.setStatus(HttpServletResponse.SC_NOT_MODIFIED); + ((AsyncContext)watcher.getAsyncContext()).complete(); + } + if (!watcher.isDone()) { + // Re-register + registryWatcher(watcher); + } + })); + } } }, 1, 1, TimeUnit.SECONDS); } @@ -79,11 +80,14 @@ public void init() { @EventListener @Async public void onChangeEvent(ClusterConfigChangeEvent event) { - Optional.ofNullable(WATCHERS.remove(event.getGroup())) + String namespace = event.getNamespace(); + String dataId = event.getDataId(); + Map>> dataIdWatchersMap = WATCHERS.get(namespace); + Optional.ofNullable(dataIdWatchersMap.remove(dataId)) .ifPresent(watchers -> watchers.parallelStream().forEach(this::notify)); } - private void notify(Watcher watcher) { + private void notify(ConfigWatcher watcher) { AsyncContext asyncContext = (AsyncContext)watcher.getAsyncContext(); HttpServletResponse httpServletResponse = (HttpServletResponse)asyncContext.getResponse(); watcher.setDone(true); @@ -95,8 +99,10 @@ private void notify(Watcher watcher) { asyncContext.complete(); } - public void registryWatcher(Watcher watcher) { - String group = watcher.getGroup(); - WATCHERS.computeIfAbsent(group, value -> new ConcurrentLinkedQueue<>()).add(watcher); + public void registryWatcher(ConfigWatcher watcher) { + String namespace = watcher.getNamespace(); + String dataId = watcher.getDataId(); + WATCHERS.computeIfAbsent(namespace, ns -> new ConcurrentHashMap<>()) + .computeIfAbsent(dataId, did -> new ConcurrentLinkedQueue<>()).add(watcher); } } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java index 15d1874ef84..4aab30fedca 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/execute/config/ConfigOperationExecute.java @@ -53,37 +53,37 @@ public Object execute(RaftBaseMsg syncMsg) throws Throwable { } private ConfigOperationResponse get(ConfigOperationDTO configOperation) { - String result = configStoreManager.get(configOperation.getGroup(), configOperation.getKey()); + String result = configStoreManager.get(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey()); return ConfigOperationResponse.success(result); } private ConfigOperationResponse put(ConfigOperationDTO configOperation) { - Boolean success = configStoreManager.put(configOperation.getGroup(), configOperation.getKey(), configOperation.getValue()); + Boolean success = configStoreManager.put(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey(), configOperation.getValue()); if (success) { // ApplicationContext may not have been started at this point if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null){ ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) - .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getGroup())); + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); } - LOGGER.info("config group: {}, config change event: {}", configOperation.getGroup(), configOperation.getOptType()); + LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); } return success? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); } private ConfigOperationResponse delete(ConfigOperationDTO configOperation) { - Boolean success = configStoreManager.delete(configOperation.getGroup(), configOperation.getKey()); + Boolean success = configStoreManager.delete(configOperation.getNamespace(), configOperation.getDataId(), configOperation.getKey()); if (success) { if (ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT) != null){ ((ApplicationEventPublisher) ObjectHolder.INSTANCE.getObject(OBJECT_KEY_SPRING_APPLICATION_CONTEXT)) - .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getGroup())); + .publishEvent(new ClusterConfigChangeEvent(this, configOperation.getNamespace(), configOperation.getDataId())); } - LOGGER.info("config group: {}, config change event: {}", configOperation.getGroup(), configOperation.getOptType()); + LOGGER.info("config namespace: {}, dataId: {}, config change event: {}", configOperation.getNamespace(), configOperation.getDataId(), configOperation.getOptType()); } return success? ConfigOperationResponse.success() : ConfigOperationResponse.fail(); } private ConfigOperationResponse getAll(ConfigOperationDTO configOperation) { - Map configMap = configStoreManager.getAll(configOperation.getGroup()); + Map configMap = configStoreManager.getAll(configOperation.getNamespace(), configOperation.getDataId()); return ConfigOperationResponse.success(configMap); } } diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java index 56f59a0c928..f983667873f 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/processor/request/ConfigOperationRequest.java @@ -24,45 +24,49 @@ public class ConfigOperationRequest implements Serializable { private static final long serialVersionUID = -1149573667621259458L; private ConfigOperationType optType; - private String group; + private String namespace; + private String dataId; private String key; private String value; public ConfigOperationRequest() { } - public ConfigOperationRequest(ConfigOperationType optType, String group) { + public ConfigOperationRequest(ConfigOperationType optType, String namespace, String dataId) { this.optType = optType; - this.group = group; + this.namespace = namespace; + this.dataId = dataId; } - public ConfigOperationRequest(ConfigOperationType optType, String group, String key) { + public ConfigOperationRequest(ConfigOperationType optType,String namespace, String dataId, String key) { this.optType = optType; - this.group = group; + this.namespace = namespace; + this.dataId = dataId; this.key = key; } - public ConfigOperationRequest(ConfigOperationType optType, String group, String key, String value) { + public ConfigOperationRequest(ConfigOperationType optType, String namespace, String dataId, String key, String value) { this.optType = optType; - this.group = group; + this.namespace = namespace; + this.dataId = dataId; this.key = key; this.value = value; } - public static ConfigOperationRequest buildGetRequest(String group, String key) { - return new ConfigOperationRequest(ConfigOperationType.GET, group, key); + public static ConfigOperationRequest buildGetRequest(String namespace, String dataId, String key) { + return new ConfigOperationRequest(ConfigOperationType.GET, namespace, dataId, key); } - public static ConfigOperationRequest buildPutRequest(String group, String key, String value) { - return new ConfigOperationRequest(ConfigOperationType.PUT, group, key, value); + public static ConfigOperationRequest buildPutRequest(String namespace, String dataId, String key, String value) { + return new ConfigOperationRequest(ConfigOperationType.PUT, namespace, dataId, key, value); } - public static ConfigOperationRequest buildDeleteRequest(String group, String key) { - return new ConfigOperationRequest(ConfigOperationType.DELETE, group, key); + public static ConfigOperationRequest buildDeleteRequest(String namespace, String dataId, String key) { + return new ConfigOperationRequest(ConfigOperationType.DELETE, namespace, dataId, key); } - public static ConfigOperationRequest buildGetAllRequest(String group) { - return new ConfigOperationRequest(ConfigOperationType.GET_ALL, group); + public static ConfigOperationRequest buildGetAllRequest(String namespace, String dataId) { + return new ConfigOperationRequest(ConfigOperationType.GET_ALL, namespace, dataId); } public ConfigOperationType getOptType() { @@ -72,12 +76,20 @@ public void setOptType(ConfigOperationType optType) { this.optType = optType; } - public String getGroup() { - return group; + public String getNamespace() { + return namespace; } - public void setGroup(String group) { - this.group = group; + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; } public String getKey() { diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java index 50cedd5f819..e493e02865c 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/snapshot/config/ConfigSnapshotFile.java @@ -62,7 +62,7 @@ public ConfigSnapshotFile(String group) { @Override public Status save(SnapshotWriter writer) { - Map configMap = configStoreManager.getConfigMap(); + Map> configMap = configStoreManager.getConfigMap(); RaftSnapshot raftSnapshot = new RaftSnapshot(); raftSnapshot.setBody(configMap); raftSnapshot.setType(RaftSnapshot.SnapshotType.config); @@ -91,7 +91,7 @@ public boolean load(SnapshotReader reader) { String path = new StringBuilder(reader.getPath()).append(File.separator).append(fileName).toString(); try { LOGGER.info("on snapshot load start index: {}", reader.load().getLastIncludedIndex()); - Map configMap = (Map)load(path); + Map> configMap = (Map>)load(path); ConfigStoreManager configStoreManager = RocksDBConfigStoreManager.getInstance(); configStoreManager.clearData(); configStoreManager.putConfigMap(configMap); diff --git a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java index 393a8054e13..2f105b17015 100644 --- a/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java +++ b/server/src/main/java/org/apache/seata/server/cluster/raft/sync/msg/dto/ConfigOperationDTO.java @@ -22,31 +22,42 @@ import java.io.Serializable; -import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_GROUP; -import static org.apache.seata.common.Constants.DEFAULT_STORE_GROUP; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_DATA_ID; +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_NAMESPACE; +import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; +import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; public class ConfigOperationDTO implements Serializable { private static final long serialVersionUID = -1237293571963636954L; private ConfigOperationType optType; - private String group = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(CONFIG_STORE_GROUP, DEFAULT_STORE_GROUP);; + private String namespace = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE);; + private String dataId = ConfigurationFactory.CURRENT_FILE_INSTANCE.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); private String key; private Object value; public ConfigOperationDTO() { } - public ConfigOperationDTO(ConfigOperationType optType, String group, String key, Object value) { + public ConfigOperationDTO(ConfigOperationType optType, String namespace, String dataId, String key, Object value) { this.optType = optType; - this.group = group; + this.namespace = namespace; + this.dataId = dataId; this.key = key; this.value = value; } - public ConfigOperationDTO(ConfigOperationType optType, String group, String key){ + public ConfigOperationDTO(ConfigOperationType optType, String dataId, String key, Object value) { this.optType = optType; - this.group = group; + this.dataId = dataId; + this.key = key; + this.value = value; + } + + public ConfigOperationDTO(ConfigOperationType optType, String dataId, String key){ + this.optType = optType; + this.dataId = dataId; this.key = key; } public ConfigOperationDTO(ConfigOperationType optType, String key){ @@ -68,12 +79,20 @@ public void setOptType(ConfigOperationType optType) { this.optType = optType; } - public String getGroup() { - return group; + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; } - public void setGroup(String group) { - this.group = group; + public void setDataId(String dataId) { + this.dataId = dataId; } public String getKey() { @@ -93,14 +112,15 @@ public void setValue(Object value) { } public static ConfigOperationDTO convertConfigRequest2Dto(ConfigOperationRequest request) { - return new ConfigOperationDTO(request.getOptType(), request.getGroup(), request.getKey(), request.getValue()); + return new ConfigOperationDTO(request.getOptType(), request.getNamespace(), request.getDataId(), request.getKey(), request.getValue()); } @Override public String toString() { return "ConfigOperationDTO{" + "optType=" + optType + - ", group='" + group + '\'' + + ", namespace='" + namespace + '\'' + + ", dataId='" + dataId + '\'' + ", key='" + key + '\'' + ", value=" + value + '}'; diff --git a/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java b/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java new file mode 100644 index 00000000000..d2dfcd6c725 --- /dev/null +++ b/server/src/main/java/org/apache/seata/server/cluster/watch/ConfigWatcher.java @@ -0,0 +1,89 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.server.cluster.watch; + +import static org.apache.seata.server.cluster.watch.Watcher.Protocol.HTTP; + +public class ConfigWatcher { + private String namespace; + + private String dataId; + + private volatile boolean done = false; + + private T asyncContext; + + private long timeout; + + + private String protocol = HTTP; + + public ConfigWatcher(String namespace, String dataId, T asyncContext, int timeout) { + this.namespace = namespace; + this.dataId = dataId; + this.asyncContext = asyncContext; + this.timeout = System.currentTimeMillis() + timeout; + } + + public String getNamespace() { + return namespace; + } + + public void setNamespace(String namespace) { + this.namespace = namespace; + } + + public String getDataId() { + return dataId; + } + + public void setDataId(String dataId) { + this.dataId = dataId; + } + + public boolean isDone() { + return done; + } + + public void setDone(boolean done) { + this.done = done; + } + + public T getAsyncContext() { + return asyncContext; + } + + public void setAsyncContext(T asyncContext) { + this.asyncContext = asyncContext; + } + + public long getTimeout() { + return timeout; + } + + public void setTimeout(long timeout) { + this.timeout = timeout; + } + + public String getProtocol() { + return protocol; + } + + public void setProtocol(String protocol) { + this.protocol = protocol; + } +} diff --git a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java index abf7166ea98..5eacbef53dc 100644 --- a/server/src/main/java/org/apache/seata/server/controller/ClusterController.java +++ b/server/src/main/java/org/apache/seata/server/controller/ClusterController.java @@ -46,6 +46,7 @@ import org.apache.seata.server.cluster.raft.processor.request.ConfigOperationRequest; import org.apache.seata.server.cluster.raft.processor.response.ConfigOperationResponse; import org.apache.seata.server.cluster.raft.sync.msg.dto.RaftClusterMetadata; +import org.apache.seata.server.cluster.watch.ConfigWatcher; import org.apache.seata.server.cluster.watch.Watcher; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -176,8 +177,8 @@ public MetadataResponse configCluster() { } @GetMapping("/config/get") - public ConfigOperationResponse getConfig(String group, String key) { - ConfigOperationRequest request = ConfigOperationRequest.buildGetRequest(group, key); + public ConfigOperationResponse getConfig(String namespace, String dataId, String key) { + ConfigOperationRequest request = ConfigOperationRequest.buildGetRequest(namespace, dataId, key); PeerId leader = RaftConfigServerManager.getLeader(); if (leader == null) { @@ -190,14 +191,14 @@ public ConfigOperationResponse getConfig(String group, String key) { try { return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); } catch (Exception e) { - LOGGER.error("Failed to get value for key: {} in group: {}: ",key, group, e); + LOGGER.error("Failed to get value for key: {} in namespace: {} and dataId: {}!",key, namespace, dataId, e); return ConfigOperationResponse.fail(e.getMessage()); } } @PostMapping("/config/put") - public ConfigOperationResponse putConfig(String group, String key, String value) { - ConfigOperationRequest request = ConfigOperationRequest.buildPutRequest(group, key, value); + public ConfigOperationResponse putConfig(String namespace, String dataId, String key, String value) { + ConfigOperationRequest request = ConfigOperationRequest.buildPutRequest(namespace, dataId, key, value); PeerId leader = RaftConfigServerManager.getLeader(); if (leader == null) { @@ -210,14 +211,14 @@ public ConfigOperationResponse putConfig(String group, String key, String value) try { return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); } catch (Exception e) { - LOGGER.error("Failed to put value: {} for key: {} in group: {}: ", value, key, group, e); + LOGGER.error("Failed to put value: {} for key: {} in namespace: {} and dataId: {}!", value, key, namespace, dataId, e); return ConfigOperationResponse.fail(e.getMessage()); } } @DeleteMapping("/config/delete") - public ConfigOperationResponse deleteConfig(String group, String key) { - ConfigOperationRequest request = ConfigOperationRequest.buildDeleteRequest(group, key); + public ConfigOperationResponse deleteConfig(String namespace, String dataId, String key) { + ConfigOperationRequest request = ConfigOperationRequest.buildDeleteRequest(namespace, dataId, key); PeerId leader = RaftConfigServerManager.getLeader(); if (leader == null) { @@ -230,14 +231,14 @@ public ConfigOperationResponse deleteConfig(String group, String key) { try { return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); } catch (Exception e) { - LOGGER.error("Failed to delete key: {} in group: {}: ", key, group, e); + LOGGER.error("Failed to delete key: {} in namespace: {} and dataId: {}!", key, namespace, dataId, e); return ConfigOperationResponse.fail(e.getMessage()); } } @GetMapping("/config/getAll") - public ConfigOperationResponse getAllConfig(String group) { - ConfigOperationRequest request = ConfigOperationRequest.buildGetAllRequest(group); + public ConfigOperationResponse getAllConfig(String namespace, String dataId) { + ConfigOperationRequest request = ConfigOperationRequest.buildGetAllRequest(namespace, dataId); PeerId leader = RaftConfigServerManager.getLeader(); if (leader == null) { @@ -250,7 +251,7 @@ public ConfigOperationResponse getAllConfig(String group) { try { return (ConfigOperationResponse)cliClientService.getRpcClient().invokeSync(leader.getEndpoint(), request, invokeContext, 1000); } catch (Exception e) { - LOGGER.error("Failed to get all configs in group:{}: ", group, e); + LOGGER.error("Failed to get all configs in namespace: {} and dataId: {}!", namespace, dataId, e); return ConfigOperationResponse.fail(e.getMessage()); } } @@ -269,12 +270,12 @@ public void watch(HttpServletRequest request, @RequestParam Map } @PostMapping("/config/watch") - public void watch(HttpServletRequest request, @RequestParam String group, + public void watch(HttpServletRequest request, @RequestParam String namespace, @RequestParam String dataId, @RequestParam(defaultValue = "28000") int timeout) { AsyncContext context = request.startAsync(); context.setTimeout(0L); - Watcher watcher = new Watcher<>(group, context, timeout, 0L); - clusterConfigWatcherManager.registryWatcher(watcher); + ConfigWatcher configWatcher = new ConfigWatcher<>(namespace, dataId, context, timeout); + clusterConfigWatcherManager.registryWatcher(configWatcher); } } From 119e2d2f073727ea607bde022967a005523782eb Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Mon, 29 Jul 2024 21:59:50 +0800 Subject: [PATCH 08/34] bugfix: Fix JVM exception caused by incorrect close of rocksdb's object --- .../store/rocksdb/RocksDBConfigStoreManager.java | 12 ++++++++---- .../config/store/rocksdb/RocksDBOptionsFactory.java | 3 +-- .../seata/config/store/rocksdb/RocksDBTest.java | 5 ++++- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java index b9edc0247ce..ad2061ecdde 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -113,7 +113,7 @@ private void openRocksDB(){ private ColumnFamilyHandle getOrCreateColumnFamilyHandle(String namespace) throws RocksDBException{ ColumnFamilyHandle handle = columnFamilyHandleMap.get(namespace); if (handle == null) { - synchronized (columnFamilyHandleMap) { + synchronized (RocksDBConfigStoreManager.class) { handle = columnFamilyHandleMap.get(namespace); if (handle == null) { handle = rocksdb.createColumnFamily(new ColumnFamilyDescriptor( @@ -294,9 +294,10 @@ public Map> getConfigMap() { HashMap configs = new HashMap<>(); ReentrantReadWriteLock lock = acquireLock(namespace); lock.readLock().lock(); - try ( - ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); - RocksIterator iterator = rocksdb.newIterator(handle)) { + RocksIterator iterator = null; + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + iterator = rocksdb.newIterator(handle); for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { String key = new String(iterator.key(), DEFAULT_CHARSET); String value = new String(iterator.value(), DEFAULT_CHARSET); @@ -306,6 +307,9 @@ public Map> getConfigMap() { } catch (RocksDBException e) { LOGGER.error("Failed to get configMap in namespace : {}", namespace, e); } finally { + if (iterator != null) { + iterator.close(); + } lock.readLock().unlock(); } } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java index 8bfb7777ea5..d47c277f3c3 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBOptionsFactory.java @@ -41,7 +41,6 @@ public class RocksDBOptionsFactory { public static final String ROCKSDB_SUFFIX = "rocksdb"; private static volatile DBOptions options = null; - private static final Map columnFamilyOptionsMap = new ConcurrentHashMap<>(); public static DBOptions getDBOptions() { if (options == null){ @@ -65,7 +64,6 @@ public static ColumnFamilyOptions getColumnFamilyOptionsMap(final String namespa opts = newOpts; } } - new ColumnFamilyOptions(opts); return opts; } public static String getDBPath() { @@ -124,4 +122,5 @@ public static void releaseAllOptions() { options = null; columnFamilyOptionsMap.clear(); } + } diff --git a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java index 2b854a60d04..fd5096e5274 100644 --- a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java +++ b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java @@ -25,13 +25,14 @@ import java.util.Map; +import static org.apache.seata.common.Constants.DEFAULT_STORE_DATA_ID; import static org.apache.seata.common.Constants.DEFAULT_STORE_NAMESPACE; class RocksDBTest { private static RocksDBConfigStoreManager configStoreManager; - private static final String dataId = "seata.properties"; + private static final String dataId = DEFAULT_STORE_DATA_ID; private static final String namespace = DEFAULT_STORE_NAMESPACE; @BeforeAll static void setUp() { @@ -153,5 +154,7 @@ void uploadTest() { Assertions.assertTrue(configStoreManager.putConfigMap(configMap)); Map> other = configStoreManager.getConfigMap(); Assertions.assertEquals(other, configMap); + + Assertions.assertDoesNotThrow(()->configStoreManager.getAll(namespace1, dataId1)); } } From ec10f68f9fff0ab8de301d0bda1ec4955aca44ba Mon Sep 17 00:00:00 2001 From: leggasai <1642271413@qq.com> Date: Tue, 6 Aug 2024 11:06:24 +0800 Subject: [PATCH 09/34] optimize: add configuration version manager in raft mode and add configuration management page in seata console UI. --- .../apache/seata/common/util/NumberUtils.java | 18 + .../store/AbstractConfigStoreManager.java | 28 + .../config/store/ConfigStoreManager.java | 11 + .../store/ConfigStoreManagerFactory.java | 45 ++ .../rocksdb/RocksDBConfigStoreManager.java | 221 +++++- .../config/store/rocksdb/RocksDBTest.java | 21 + .../src/test/resources/registry.conf | 10 + .../config/raft/RaftConfigurationClient.java | 17 +- .../config/raft/RaftConfigurationServer.java | 8 +- .../resources/static/console-fe/src/app.tsx | 7 +- .../static/console-fe/src/locales/en-us.ts | 25 + .../static/console-fe/src/locales/zh-cn.ts | 25 + .../src/pages/ConfigInfo/ConfigInfo.tsx | 632 ++++++++++++++++++ .../src/pages/ConfigInfo/index.scss | 16 + .../console-fe/src/pages/ConfigInfo/index.ts | 21 + .../static/console-fe/src/router.tsx | 2 + .../console-fe/src/service/configInfo.ts | 80 +++ .../static/console-fe/src/utils/request.ts | 47 ++ .../src/main/resources/static/css/main.css | 1 + console/src/main/resources/static/js/main.js | 47 +- .../resources/application-test.properties | 3 +- .../manager/ClusterConfigWatcherManager.java | 7 +- .../config/AbstractRaftConfigMsgExecute.java | 5 +- .../config/ConfigOperationExecute.java | 51 +- .../execute/config/ConfigOperationType.java | 10 +- .../request/ConfigOperationRequest.java | 36 +- .../raft/sync/msg/dto/ConfigOperationDTO.java | 4 +- .../server/controller/ClusterController.java | 177 +++-- .../server/raft/RaftSyncMessageTest.java | 5 +- 29 files changed, 1486 insertions(+), 94 deletions(-) create mode 100644 config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java create mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx create mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss create mode 100644 console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts create mode 100644 console/src/main/resources/static/console-fe/src/service/configInfo.ts diff --git a/common/src/main/java/org/apache/seata/common/util/NumberUtils.java b/common/src/main/java/org/apache/seata/common/util/NumberUtils.java index 7374bdc08c5..cd5a468b7b9 100644 --- a/common/src/main/java/org/apache/seata/common/util/NumberUtils.java +++ b/common/src/main/java/org/apache/seata/common/util/NumberUtils.java @@ -60,4 +60,22 @@ public static Long toLong(String str) { } return null; } + + public static byte[] longToBytes(long x) { + byte[] result = new byte[8]; + for (int i = 7; i >= 0; i--) { + result[i] = (byte)(x & 0xFF); + x >>= 8; + } + return result; + } + + public static long bytesToLong(byte[] bytes) { + long result = 0; + for (int i = 0; i < 8; i++) { + result <<= 8; + result |= (bytes[i] & 0xFF); + } + return result; + } } diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java index 915379261cd..19821384bdd 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/AbstractConfigStoreManager.java @@ -18,7 +18,10 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; + +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; /** @@ -78,6 +81,31 @@ public Boolean clearData() { return Boolean.FALSE; } + @Override + public List getAllNamespaces() { + return Collections.emptyList(); + } + + @Override + public List getAllDataIds(String namespace) { + return Collections.emptyList(); + } + + @Override + public Long getConfigVersion(String namespace, String dataId) { + return 0L; + } + + @Override + public Boolean putConfigVersion(String namespace, String dataId, Long version) { + return Boolean.FALSE; + } + + @Override + public Boolean deleteConfigVersion(String namespace, String dataId) { + return Boolean.FALSE; + } + @Override public void destroy() {} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java index 762c1dd5e23..63dd0bd18b1 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManager.java @@ -23,6 +23,7 @@ import java.io.IOException; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Properties; @@ -50,6 +51,16 @@ public interface ConfigStoreManager { Boolean putConfigMap(Map> configMap); Boolean clearData(); + + List getAllNamespaces(); + + List getAllDataIds(String namespace); + + Long getConfigVersion(String namespace, String dataId); + + Boolean putConfigVersion(String namespace, String dataId, Long version); + + Boolean deleteConfigVersion(String namespace, String dataId); void destroy(); default void addConfigListener(String group, String dataId, ConfigurationChangeListener listener) {}; diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java new file mode 100644 index 00000000000..ce6a6c269e9 --- /dev/null +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/ConfigStoreManagerFactory.java @@ -0,0 +1,45 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.seata.config.store; + + +import org.apache.seata.common.loader.EnhancedServiceLoader; +import org.apache.seata.config.Configuration; +import org.apache.seata.config.ConfigurationFactory; + + +import java.util.Objects; + +import static org.apache.seata.common.ConfigurationKeys.CONFIG_STORE_TYPE; +import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; + +public class ConfigStoreManagerFactory { + private static final Configuration FILE_CONFIG = ConfigurationFactory.CURRENT_FILE_INSTANCE; + private static volatile ConfigStoreManager instance; + + public static ConfigStoreManager getInstance() { + if (instance == null) { + synchronized (ConfigStoreManagerFactory.class) { + if (instance == null) { + String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); + instance = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); + } + } + } + return instance; + } +} diff --git a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java index ad2061ecdde..7108d641f11 100644 --- a/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java +++ b/config/seata-config-core/src/main/java/org/apache/seata/config/store/rocksdb/RocksDBConfigStoreManager.java @@ -18,6 +18,7 @@ import org.apache.seata.common.util.CollectionUtils; +import org.apache.seata.common.util.NumberUtils; import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.*; import org.apache.seata.config.store.AbstractConfigStoreManager; @@ -32,6 +33,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.locks.ReentrantReadWriteLock; +import java.util.stream.Collectors; import static org.apache.seata.common.ConfigurationKeys.*; @@ -66,6 +68,7 @@ public class RocksDBConfigStoreManager extends AbstractConfigStoreManager { private static volatile RocksDBConfigStoreManager instance; private RocksDB rocksdb; private final Map columnFamilyHandleMap = new ConcurrentHashMap<>(); + private final String VERSION_COLUMN_FAMILY = "config_version"; private static final List prefixList = Arrays.asList(FILE_ROOT_PREFIX_CONFIG, FILE_ROOT_PREFIX_REGISTRY, SERVER_PREFIX, CLIENT_PREFIX, SERVICE_PREFIX, STORE_PREFIX, METRICS_PREFIX, TRANSPORT_PREFIX, LOG_PREFIX, TCC_PREFIX); @@ -98,8 +101,10 @@ private void openRocksDB(){ String namespace = new String(cf); descriptors.add(new ColumnFamilyDescriptor(cf, RocksDBOptionsFactory.getColumnFamilyOptionsMap(namespace))); } + // create default column family and config version column family if (CollectionUtils.isEmpty(descriptors)) { descriptors.add(new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, RocksDBOptionsFactory.getColumnFamilyOptionsMap(new String(RocksDB.DEFAULT_COLUMN_FAMILY)))); + descriptors.add(new ColumnFamilyDescriptor(VERSION_COLUMN_FAMILY.getBytes(DEFAULT_CHARSET), RocksDBOptionsFactory.getColumnFamilyOptionsMap(VERSION_COLUMN_FAMILY))); } this.rocksdb = RocksDBFactory.getInstance(DB_PATH, DB_OPTIONS, descriptors, handles); for (ColumnFamilyHandle handle : handles) { @@ -151,25 +156,26 @@ private void maybeNeedLoadOriginConfig() { } }); putAll(CURRENT_NAMESPACE, CURRENT_DATA_ID, seataConfigs); + LOGGER.info("Load initialization configuration file sucessfully in namespace: {}, dataId: {}", CURRENT_NAMESPACE, CURRENT_DATA_ID); } } /** - * acquire lock of the given namespace + * Acquire lock of the given namespace * @param namespace */ private ReentrantReadWriteLock acquireLock(String namespace) { - namespace = StringUtils.isEmpty(namespace)? CURRENT_NAMESPACE : namespace; return LOCK_MAP.computeIfAbsent(namespace, k -> new ReentrantReadWriteLock()); } - /* - * Get config map of the given namespace and dataId - * - */ + /** + * Get config map of the given namespace and dataId + * @param namespace + * @param dataId + * @return + * @throws RocksDBException + */ private Map getConfigMap(String namespace, String dataId) throws RocksDBException{ - dataId = StringUtils.isEmpty(dataId)? CURRENT_DATA_ID : dataId; - namespace = StringUtils.isEmpty(namespace)? CURRENT_NAMESPACE : namespace; ReentrantReadWriteLock lock = acquireLock(namespace); lock.readLock().lock(); try { @@ -186,7 +192,13 @@ private Map getConfigMap(String namespace, String dataId) throws } } - + /** + * Get the config value of the given namespace and dataId + * @param namespace + * @param dataId + * @param key + * @return + */ @Override public String get(String namespace, String dataId, String key) { ReentrantReadWriteLock lock = acquireLock(namespace); @@ -202,6 +214,12 @@ public String get(String namespace, String dataId, String key) { return null; } + /** + * Get all config items of the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ @Override public Map getAll(String namespace, String dataId) { try { @@ -212,6 +230,14 @@ public Map getAll(String namespace, String dataId) { return null; } + /** + * Put a config item to the given namespace and dataId + * @param namespace + * @param dataId + * @param key + * @param value + * @return + */ @Override public Boolean put(String namespace, String dataId, String key, Object value) { ReentrantReadWriteLock lock = acquireLock(namespace); @@ -222,6 +248,7 @@ public Boolean put(String namespace, String dataId, String key, Object value) { String configStr = ConfigStoreManager.convertConfig2Str(configMap); ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + updateConfigVersion(namespace, dataId); notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); return true; }catch (RocksDBException e){ @@ -232,6 +259,13 @@ public Boolean put(String namespace, String dataId, String key, Object value) { return false; } + /** + * Delete a config item with the given key from the given namespace and dataId + * @param namespace + * @param dataId + * @param key + * @return + */ @Override public Boolean delete(String namespace, String dataId, String key) { ReentrantReadWriteLock lock = acquireLock(namespace); @@ -242,6 +276,7 @@ public Boolean delete(String namespace, String dataId, String key) { String configStr = ConfigStoreManager.convertConfig2Str(configMap); ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + updateConfigVersion(namespace, dataId); notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); return true; }catch (RocksDBException e){ @@ -252,6 +287,13 @@ public Boolean delete(String namespace, String dataId, String key) { return false; } + /** + * Put all config items into the given namespace and dataId + * @param namespace + * @param dataId + * @param configMap + * @return + */ @Override public Boolean putAll(String namespace, String dataId, Map configMap) { ReentrantReadWriteLock lock = acquireLock(namespace); @@ -260,6 +302,7 @@ public Boolean putAll(String namespace, String dataId, Map confi String configStr = ConfigStoreManager.convertConfig2Str(configMap); ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); rocksdb.put(handle, dataId.getBytes(DEFAULT_CHARSET), configStr.getBytes(DEFAULT_CHARSET)); + updateConfigVersion(namespace, dataId); notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, configStr)); return true; }catch (RocksDBException e){ @@ -270,6 +313,12 @@ public Boolean putAll(String namespace, String dataId, Map confi return false; } + /** + * Delete all config items in the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ @Override public Boolean deleteAll(String namespace, String dataId) { ReentrantReadWriteLock lock = acquireLock(namespace); @@ -277,6 +326,7 @@ public Boolean deleteAll(String namespace, String dataId) { try { ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); rocksdb.delete(handle, dataId.getBytes(DEFAULT_CHARSET)); + deleteConfigVersion(namespace, dataId); notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, dataId, null)); return true; } catch (RocksDBException e) { @@ -287,6 +337,11 @@ public Boolean deleteAll(String namespace, String dataId) { return false; } + + /** + * Get all key-values pairs in all namespaces, mainly used for backup or snapshot + * @return Map(namespace -> Map(dataId -> value)) + */ @Override public Map> getConfigMap() { Map> configMap = new HashMap<>(); @@ -316,6 +371,11 @@ public Map> getConfigMap() { return configMap; } + /** + * Put all key-value pairs into the specified column family, mainly used for backup or snapshot + * @param configMap Map(namespace -> Map(dataId -> value)) + * @return + */ @Override public Boolean putConfigMap(Map> configMap) { try (WriteBatch batch = new WriteBatch(); WriteOptions writeOptions = new WriteOptions()) { @@ -340,7 +400,9 @@ public Boolean putConfigMap(Map> configMap) { String namespace = entry.getKey(); Map configs = entry.getValue(); for (Map.Entry kv : configs.entrySet()) { - notifyConfigChange(namespace, kv.getKey(), new ConfigurationChangeEvent(namespace, kv.getKey(), kv.getValue().toString())); + String dataId = kv.getKey(); + updateConfigVersion(namespace, dataId); + notifyConfigChange(namespace, dataId, new ConfigurationChangeEvent(namespace, kv.getKey(), kv.getValue().toString())); } } return true; @@ -350,6 +412,10 @@ public Boolean putConfigMap(Map> configMap) { } } + /** + * Empty all data in rocksdb, i.e. delete all key-value pairs in all column family + * @return + */ @Override public Boolean clearData() { Map> clearDataMap = new HashMap<>(); @@ -373,6 +439,7 @@ public Boolean clearData() { for (Map.Entry> entry : clearDataMap.entrySet()) { String namespace = entry.getKey(); for (String key : entry.getValue()) { + deleteConfigVersion(namespace, key); notifyConfigChange(namespace, key, new ConfigurationChangeEvent(namespace, key, null)); } } @@ -383,11 +450,137 @@ public Boolean clearData() { } } + /** + * Check whether the config data exists in the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ @Override public Boolean isEmpty(String namespace, String dataId) { return CollectionUtils.isEmpty(getAll(namespace, dataId)); } + /** + * Get all namespaces in current rocksdb instance + * @return + */ + @Override + public List getAllNamespaces() { + return columnFamilyHandleMap.keySet().stream() + .filter(namespace -> !namespace.equals(VERSION_COLUMN_FAMILY)) + .collect(Collectors.toList()); + } + + /** + * Get all dataIds in the given namespace + * @param namespace + * @return + */ + @Override + public List getAllDataIds(String namespace) { + if (StringUtils.isEmpty(namespace) || !columnFamilyHandleMap.containsKey(namespace)) { + return Collections.emptyList(); + } + List dataIds = new ArrayList<>(); + ReentrantReadWriteLock lock = acquireLock(namespace); + lock.readLock().lock(); + RocksIterator iterator = null; + try { + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(namespace); + iterator = rocksdb.newIterator(handle); + for (iterator.seekToFirst(); iterator.isValid(); iterator.next()) { + String dataId = new String(iterator.key(), DEFAULT_CHARSET); + dataIds.add(dataId); + } + }catch (RocksDBException e) { + LOGGER.error("Failed to get all dataIds in namespace: {}", namespace, e); + }finally { + if (iterator != null) { + iterator.close(); + } + lock.readLock().unlock(); + } + return dataIds; + } + + /** + * Get the config version in the given namespace and dataId + * @param namespace + * @param dataId + * @return + */ + @Override + public Long getConfigVersion(String namespace, String dataId) { + ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); + lock.readLock().lock(); + try { + String configVersionKey = getConfigVersionKey(namespace, dataId); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); + byte[] value = rocksdb.get(handle, configVersionKey.getBytes(DEFAULT_CHARSET)); + return value != null ? NumberUtils.bytesToLong(value) : null; + }catch (RocksDBException | IllegalArgumentException e) { + LOGGER.error("Failed to get config version in namespace: {} and dataId: {}", namespace, dataId, e); + }finally { + lock.readLock().unlock(); + } + return null; + } + + /** + * Put the config version in the given namespace and dataId + * @param namespace + * @param dataId + * @param version + * @return + */ + @Override + public Boolean putConfigVersion(String namespace, String dataId, Long version) { + ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); + lock.writeLock().lock(); + try { + String configVersionKey = getConfigVersionKey(namespace, dataId); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); + rocksdb.put(handle, configVersionKey.getBytes(DEFAULT_CHARSET), NumberUtils.longToBytes(version)); + return true; + }catch (RocksDBException | IllegalArgumentException e) { + LOGGER.error("Failed to put config version in namespace: {} and dataId: {}", namespace, dataId, e); + }finally { + lock.writeLock().unlock(); + } + return false; + } + + /** + * Delete the config version in the given namespace and dataId when the config data is deleted. + * @param namespace + * @param dataId + * @return + */ + @Override + public Boolean deleteConfigVersion(String namespace, String dataId) { + ReentrantReadWriteLock lock = acquireLock(VERSION_COLUMN_FAMILY); + lock.writeLock().lock(); + try { + String configVersionKey = getConfigVersionKey(namespace, dataId); + ColumnFamilyHandle handle = getOrCreateColumnFamilyHandle(VERSION_COLUMN_FAMILY); + rocksdb.delete(handle, configVersionKey.getBytes(DEFAULT_CHARSET)); + return true; + }catch (RocksDBException | IllegalArgumentException e) { + LOGGER.error("Failed to put config version in namespace: {} and dataId: {}", namespace, dataId, e); + }finally { + lock.writeLock().unlock(); + } + return false; + } + + private String getConfigVersionKey(String namespace, String dataId) { + if (StringUtils.isEmpty(namespace) || StringUtils.isEmpty(dataId)) { + throw new IllegalArgumentException("Invalid config namespace or dataId"); + } + return namespace + "_" + dataId; + } + // todo @Override public void shutdown() { @@ -445,6 +638,14 @@ public void removeConfigListener(String namespace, String dataId, ConfigurationC } } + private void updateConfigVersion(String namespace, String dataId) { + Long version = getConfigVersion(namespace, dataId); + if (version == null) { + version = 0L; + } + putConfigVersion(namespace, dataId, version + 1); + } + private void notifyConfigChange(String namespace, String dataId, ConfigurationChangeEvent event) { Map> listenerMap = CONFIG_LISTENERS_MAP.get(namespace); diff --git a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java index fd5096e5274..73777a09941 100644 --- a/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java +++ b/config/seata-config-core/src/test/java/org/apache/seata/config/store/rocksdb/RocksDBTest.java @@ -157,4 +157,25 @@ void uploadTest() { Assertions.assertDoesNotThrow(()->configStoreManager.getAll(namespace1, dataId1)); } + + @Test + void configVersionTest() { + configStoreManager.clearData(); + Long version = 0L; + + String key = "aaa"; + String value = "bbb"; + String newValue = "ccc"; + + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, value)); + version++; + Assertions.assertEquals(version, configStoreManager.getConfigVersion(namespace, dataId)); + + Assertions.assertTrue(configStoreManager.put(namespace, dataId, key, newValue)); + version++; + Assertions.assertEquals(version, configStoreManager.getConfigVersion(namespace, dataId)); + + Assertions.assertTrue(configStoreManager.deleteAll(namespace, dataId)); + Assertions.assertNull(configStoreManager.getConfigVersion(namespace, dataId)); + } } diff --git a/config/seata-config-core/src/test/resources/registry.conf b/config/seata-config-core/src/test/resources/registry.conf index bab6e8ec0ef..343179ab6a8 100644 --- a/config/seata-config-core/src/test/resources/registry.conf +++ b/config/seata-config-core/src/test/resources/registry.conf @@ -87,4 +87,14 @@ config { file { name = "file.conf" } + raft { + db { + type = "rocksdb" + dir = "configStore" + destroy-on-shutdown = false + namespace = "default" + dataId = "seata.properties" + } + } + } diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java index 88f470be446..0df0e5e6538 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationClient.java @@ -47,6 +47,7 @@ import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -70,6 +71,8 @@ public class RaftConfigurationClient extends AbstractConfiguration { private static final String CONFIG_DATA_ID; private static final String USERNAME_KEY = "username"; private static final String PASSWORD_KEY = "password"; + private static final String CONFIG_KEY = "config"; + private static final String VERSION_KEY = "version"; private static final String AUTHORIZATION_HEADER = "Authorization"; private static final String TOKEN_VALID_TIME_MS_KEY = "tokenValidityInMilliseconds"; @@ -89,6 +92,7 @@ public class RaftConfigurationClient extends AbstractConfiguration { private static final AtomicBoolean CLOSED = new AtomicBoolean(false); private static final AtomicBoolean CONFIG_CLOSED = new AtomicBoolean(false); private static volatile Properties seataConfig = new Properties(); + private static final AtomicLong CONFIG_VERSION = new AtomicLong(0); private static final int MAP_INITIAL_CAPACITY = 8; private static final ConcurrentMap> CONFIG_LISTENERS_MAP = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); @@ -236,7 +240,17 @@ private static Map acquireClusterConfigData(String clusterName, try { configDataResponse = OBJECT_MAPPER.readValue(response, new TypeReference>>() {}); if(configDataResponse.getSuccess()) { - return configDataResponse.getResult(); + Map result = configDataResponse.getResult(); + Map configMap = (Map) result.get(CONFIG_KEY); + Long version = ((Integer)result.get(VERSION_KEY)).longValue(); + Long currentVersion = CONFIG_VERSION.get(); + if (version < currentVersion) { + LOGGER.info("The configuration version: {} of the server is lower than the current configuration: {} , it may be expired configuration.", version, CONFIG_VERSION.get()); + throw new RetryableException("Expired configuration!"); + }else{ + CONFIG_VERSION.set(version); + return configMap; + } }else{ throw new RetryableException(configDataResponse.getErrMsg()); } @@ -404,6 +418,7 @@ private static boolean configWatch() throws RetryableException { Map param = new HashMap<>(); param.put("namespace", CONFIG_NAMESPACE); param.put("dataId", CONFIG_DATA_ID); + param.put("version", CONFIG_VERSION.toString()); if (isTokenExpired()) { refreshToken(tcAddress); } diff --git a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java index 3c56e87eb0b..b8eb6874c5e 100644 --- a/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java +++ b/config/seata-config-raft/src/main/java/org/apache/seata/config/raft/RaftConfigurationServer.java @@ -1,12 +1,11 @@ package org.apache.seata.config.raft; import org.apache.seata.common.exception.NotSupportYetException; -import org.apache.seata.common.loader.EnhancedServiceLoader; import org.apache.seata.common.util.CollectionUtils; import org.apache.seata.common.util.StringUtils; import org.apache.seata.config.*; import org.apache.seata.config.store.ConfigStoreManager; -import org.apache.seata.config.store.ConfigStoreManagerProvider; +import org.apache.seata.config.store.ConfigStoreManagerFactory; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -16,7 +15,7 @@ import static org.apache.seata.common.ConfigurationKeys.*; import static org.apache.seata.common.Constants.*; -import static org.apache.seata.common.DefaultValues.DEFAULT_DB_TYPE; + public class RaftConfigurationServer extends AbstractConfiguration { @@ -33,8 +32,7 @@ public class RaftConfigurationServer extends AbstractConfiguration { = new ConcurrentHashMap<>(MAP_INITIAL_CAPACITY); private static void initServerConfig() { - String dbType = FILE_CONFIG.getConfig(CONFIG_STORE_TYPE, DEFAULT_DB_TYPE); - configStoreManager = EnhancedServiceLoader.load(ConfigStoreManagerProvider.class, Objects.requireNonNull(dbType), false).provide(); + configStoreManager = ConfigStoreManagerFactory.getInstance(); CURRENT_NAMESPACE = FILE_CONFIG.getConfig(CONFIG_STORE_NAMESPACE, DEFAULT_STORE_NAMESPACE); CURRENT_DATA_ID = FILE_CONFIG.getConfig(CONFIG_STORE_DATA_ID, DEFAULT_STORE_DATA_ID); // load config from store diff --git a/console/src/main/resources/static/console-fe/src/app.tsx b/console/src/main/resources/static/console-fe/src/app.tsx index d3bac4a24d0..b003efcbd52 100644 --- a/console/src/main/resources/static/console-fe/src/app.tsx +++ b/console/src/main/resources/static/console-fe/src/app.tsx @@ -78,7 +78,8 @@ class App extends React.Component { get menu() { const { locale }: AppPropsType = this.props; const { MenuRouter = {} } = locale; - const { overview, transactionInfo, globalLockInfo, sagaStatemachineDesigner } = MenuRouter; + const { overview, transactionInfo, globalLockInfo, configInfo, sagaStatemachineDesigner } = MenuRouter; + return { items: [ // { @@ -93,6 +94,10 @@ class App extends React.Component { key: '/globallock/list', label: globalLockInfo, }, + { + key: '/config/list', + label: configInfo, + }, { key: '/sagastatemachinedesigner', label: sagaStatemachineDesigner, diff --git a/console/src/main/resources/static/console-fe/src/locales/en-us.ts b/console/src/main/resources/static/console-fe/src/locales/en-us.ts index 58e3a8fba06..ce64d4ab78b 100644 --- a/console/src/main/resources/static/console-fe/src/locales/en-us.ts +++ b/console/src/main/resources/static/console-fe/src/locales/en-us.ts @@ -22,6 +22,7 @@ const enUs: ILocale = { overview: 'Overview', transactionInfo: 'TransactionInfo', globalLockInfo: 'GlobalLockInfo', + configInfo: 'ConfigurationInfo', sagaStatemachineDesigner: 'SagaStatemachineDesigner', }, Header: { @@ -70,6 +71,30 @@ const enUs: ILocale = { resetButtonLabel: 'Reset', searchButtonLabel: 'Search', }, + ConfigInfo: { + title: 'ConfigurationInfo', + subTitle: 'list', + resetButtonLabel: 'Reset', + searchButtonLabel: 'Search', + createButtonLabel: 'Create', + editButtonLabel: 'Edit', + deleteButtonLabel: 'Delete', + clearButtonLabel: 'Clear', + uploadButtonLabel: 'Upload', + operateTitle: 'Actions', + disableTitle: 'This page is only available if the Configuration Center type is raft mode', + editTitle: 'Edit Config', + deleteTitle: 'Delete Config', + deleteConfirmLabel: 'Are you sure you want to delete the configuration item with key: ', + deleteAllConfirmLabel: 'Are you sure you want to clear all configuration items in the following namespace and dataId: ', + addTitle: 'Add Config', + operationSuccess: 'Operation Success!', + operationFailed: 'Operation Failed', + inputFilterPlaceholder: 'Please select filter criteria', + fieldFillingTips: 'Please fill in the required fields', + uploadTitle: 'Upload Config', + uploadFileButtonLabel: 'Upload File', + }, }; export default enUs; diff --git a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts index e2ed430d514..5b367720152 100644 --- a/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts +++ b/console/src/main/resources/static/console-fe/src/locales/zh-cn.ts @@ -22,6 +22,7 @@ const zhCn: ILocale = { overview: '概览', transactionInfo: '事务信息', globalLockInfo: '全局锁信息', + configInfo:'配置信息', sagaStatemachineDesigner: 'Saga状态机设计器', }, Header: { @@ -70,6 +71,30 @@ const zhCn: ILocale = { resetButtonLabel: '重置', searchButtonLabel: '搜索', }, + ConfigInfo: { + title: '配置信息', + subTitle: '配置列表页', + resetButtonLabel: '重置', + searchButtonLabel: '搜索', + createButtonLabel: '创建', + editButtonLabel: '编辑', + deleteButtonLabel: '删除', + clearButtonLabel: '清空', + uploadButtonLabel: '上传', + operateTitle: '操作', + disableTitle: '该页面仅在配置中心类型为raft模式下可用', + editTitle: '编辑配置', + deleteTitle: '删除配置', + deleteConfirmLabel: '确认需要删除以下配置项: ', + deleteAllConfirmLabel: '确认需要清空以下namespace和dataId中的所有配置项: ', + addTitle: '新增配置', + operationSuccess: '操作成功!', + operationFailed: '操作失败', + inputFilterPlaceholder: '请选择筛选条件', + fieldFillingTips: '请将必要字段填充完整', + uploadTitle: '上传配置', + uploadFileButtonLabel: '上传文件', + }, }; export default zhCn; diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx new file mode 100644 index 00000000000..992ce0fc35a --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/ConfigInfo.tsx @@ -0,0 +1,632 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import React from 'react'; +import { + ConfigProvider, + Table, + Button, + DatePicker, + Form, + Icon, + Pagination, + Input, + Dialog, + Message, + Select, + Upload, +} from '@alicloud/console-components'; +import { withRouter } from 'react-router-dom'; + +import {getConfig, getClusterInfo, putConfig, deleteConfig, deleteAllConfig, getAllNamespaces, getAllDataIds, uploadConfig} from "@/service/configInfo"; +import Page from '@/components/Page'; +import { GlobalProps } from '@/module'; +import styled, { css } from 'styled-components'; +import PropTypes from 'prop-types'; +import './index.scss'; + +import moment from "moment/moment"; +type ConfigInfoState = { + configList: Array; + editDialogVisible: boolean; + deleteDialogVisible: boolean; + uploadDialogVisible: boolean; + loading: boolean; + configParam: ConfigParam; + editDialogInfo: DialogInfo; + deleteDialogInfo: DeleteDialogInfo; + uploadDialogInfo: UploadDialogInfo; + isRaft: boolean; + namespaces: Array; + dataIds: Array; +} +export type ConfigParam = { + namespace: string, + dataId: string, +}; + +type DialogInfo = { + isEdit: boolean; + namespace: string; + dataId: string; + key: string; + value: string; +} + +type DeleteDialogInfo = { + namespace: string; + dataId: string; +} + +type UploadDialogInfo = { + namespace: string; + dataId: string; + file: File; +} + +const FormItem = Form.Item; + + + +class ConfigInfo extends React.Component { + static displayName = 'ConfigInfo'; + + static propTypes = { + locale: PropTypes.object, + history: PropTypes.object, + }; + + state: ConfigInfoState = { + configList: [], + loading: false, + editDialogVisible: false, + deleteDialogVisible: false, + uploadDialogVisible: false, + namespaces: [], + dataIds: [], + configParam: { + namespace: '', + dataId: '', + }, + editDialogInfo: { + isEdit: false, + namespace: '', + dataId: '', + key: '', + value: '', + }, + deleteDialogInfo: { + namespace: '', + dataId: '', + }, + uploadDialogInfo: { + namespace: '', + dataId: '', + file: null, + }, + isRaft: false, + } + + componentDidMount() { + this.init(); + } + + init = async () => { + const { disableTitle } = this.props.locale; + this.setState({ loading: true }); + try { + const response = await getClusterInfo(); + const raftMode = response.configMode + if (raftMode === 'raft') { + this.setState({ isRaft: true, loading: false }); + this.fetchNamespaces(); + }else{ + this.setState({ loading: false }); + Message.error(disableTitle); + setTimeout(() => this.props.history.goBack(), 1000); + } + //this.setState({ clusterInfo: result, loading: false }); + } catch (error) { + Message.error('Failed to fetch cluster info'); + this.setState({ loading: false }); + this.props.history.goBack(); + } + } + + fetchNamespaces = async () => { + try { + const response = await getAllNamespaces(); + const result = response.result; + this.setState({ namespaces: result }); + } catch (error) { + Message.error('Failed to fetch namespace list'); + } + } + + fetchDataIds = async (namespace: string) => { + try { + const response = await getAllDataIds({ namespace }); + const result = response.result; + this.setState({ dataIds: result }); + } catch (error) { + Message.error('Failed to fetch dataIds'); + } + } + + fetchConfigList = async () => { + this.setState({ loading: true }); + try { + const response = await getConfig({namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId}); + console.log(response) + if (response.success && response.result){ + const { config } = response.result; + const configList = Object.keys(config).map((key) => ({ key, value: config[key] })); + this.setState({ configList, loading: false }); + }else { + Message.error(response.errMsg || 'Failed to fetch config list'); + this.setState({ loading: false }); + } + } catch (error) { + Message.error('Failed to fetch config list'); + this.setState({ loading: false }); + } + } + searchFilterOnChange = async (key:string, val:string) => { + this.setState({ + configParam: Object.assign(this.state.configParam, + { [key]: val }), + }); + if (key === 'namespace') { + this.setState({ + configParam: Object.assign(this.state.configParam, + { dataId: '' }), + }); + await this.fetchDataIds(val); + } + } + search = () => { + this.fetchConfigList(); + } + resetSearchFilter = () => { + this.setState({ + configParam: { + // pagination info don`t reset + namespace: '', + dataId: '', + }, + }); + } + + resetDialog = () => { + this.setState({ + editDialogInfo: {isEdit: true, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: '', value: ''}, + deleteDialogInfo: {namespace: '', dataId: ''}, + uploadDialogInfo: {namespace: '', dataId: '', file: null}, + }); + }; + openEditDialog = (config: { key: string; value: string }) => { + this.setState({ editDialogVisible: true, editDialogInfo: {isEdit: true, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, ...config}}); + }; + + openDeleteDialog = () => { + this.setState({ deleteDialogVisible: true, deleteDialogInfo: {namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId}}); + } + + openUploadDialog = () => { + this.setState({ uploadDialogVisible: true, uploadDialogInfo: {namespace: this.state.configParam.namespace, dataId: '', file: null}}); + } + + createConfig = () => { + this.setState({ editDialogVisible: true, editDialogInfo: {isEdit: false, namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: '', value: ''}}); + }; + + closeDialog = () => { + this.setState({ editDialogVisible: false, deleteDialogVisible: false, uploadDialogVisible: false}); + this.resetDialog(); + }; + + handleAddOrEditConfig = async () => { + const { operationSuccess, operationFail } = this.props.locale; + const { editDialogInfo } = this.state; + try { + const response =await putConfig({ + namespace: editDialogInfo.namespace, + dataId: editDialogInfo.dataId, + key: editDialogInfo.key, + value: editDialogInfo.value, + }); + if (response.success) { + Message.success(operationSuccess); + this.setState({ + editDialogVisible: false, + configParam: { + namespace: editDialogInfo.namespace, + dataId: editDialogInfo.dataId, + } + }); + this.fetchNamespaces(); + this.fetchDataIds(editDialogInfo.namespace); + this.fetchConfigList(); + } else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + } + + handleDeleteConfig = async (record: { key: string }) => { + const { deleteTitle, deleteConfirmLabel, operationSuccess, operationFail } = this.props.locale; + Dialog.confirm({ + title: deleteTitle, + content: deleteConfirmLabel + `${record.key} ?`, + onOk: async () => { + try { + const response = await deleteConfig({ namespace: this.state.configParam.namespace, dataId: this.state.configParam.dataId, key: record.key }); + if (response.success) { + Message.success(operationSuccess); + this.fetchConfigList(); + }else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + }, + onCancel: () => { + + }, + }); + } + + handleDeleteAllConfig = async () => { + const {operationSuccess, operationFail, fieldFillingTips} = this.props.locale; + const { namespace, dataId } = this.state.deleteDialogInfo; + + if (!namespace || !dataId) { + Message.error(fieldFillingTips); + return; + } + try { + const response = await deleteAllConfig({ namespace, dataId }); + if (response.success) { + Message.success(operationSuccess); + this.setState({ + configParam: { + namespace: namespace, + dataId: dataId, + }, + deleteDialogVisible: false, + deleteDialogInfo: { namespace: '', dataId: '' }, + }); + this.fetchDataIds(namespace) + this.fetchConfigList(); + } else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + } + + handleUploadConfig = async () => { + const {operationSuccess, operationFail, fieldFillingTips} = this.props.locale; + const { namespace, dataId,file } = this.state.uploadDialogInfo; + if (!namespace || !dataId || !file) { + Message.error(fieldFillingTips); + return; + } + const formData = new FormData(); + formData.append('namespace', namespace); + formData.append('dataId', dataId); + formData.append('file', file); + + try { + const response = await uploadConfig(formData); + if (response.success) { + Message.success(operationSuccess); + this.setState({ + uploadDialogVisible: false, + uploadDialogInfo: { namespace: '', dataId: '', file: null}, + configParam: { + namespace: namespace, + dataId: dataId, + } + }); + this.fetchNamespaces(); + this.fetchDataIds(namespace) + this.fetchConfigList(); + } else { + Message.error(response.errMsg || operationFail); + } + } catch (error) { + Message.error(operationFail); + } + } + + handleDialogInputChange = (key: string, value: string) => { + this.setState((prevState) => ({ + editDialogInfo: { + ...prevState.editDialogInfo, + [key]: value, + }, + })); + }; + + handleDeleteDialogInputChange = (key: string, value: string) => { + this.setState((prevState) => ({ + deleteDialogInfo: { + ...prevState.deleteDialogInfo, + [key]: value + } + })) + } + + handleUploadDialogInputChange = (key: string, value: string) => { + this.setState((prevState) => ({ + uploadDialogInfo: { + ...prevState.uploadDialogInfo, + [key]: value + } + })) + } + + handleFileInputChange = (fileList: Array) => { + const file = fileList.length > 0 ? fileList[0] : null; + if (file && file.originFileObj) { + this.handleUploadDialogInputChange('file', file.originFileObj); + } + } + render() { + const { locale = {} } = this.props; + const { title, subTitle, + searchButtonLabel, + resetButtonLabel, + createButtonLabel, + clearButtonLabel, + uploadButtonLabel, + operateTitle, + editTitle, + deleteTitle, + uploadTitle, + deleteAllConfirmLabel, + editButtonLabel, + deleteButtonLabel, + inputFilterPlaceholder, + uploadFileButtonLabel, + } = locale; + + return ( + + {/* search form */} +
+ {/* {search filters} */} + + { this.searchFilterOnChange('dataId', value); }} + dataSource={this.state.dataIds} + style={{ width: 200 }} + hasClear={true} + /> + + + {/* {reset search filter button} */} + + + {resetButtonLabel} + + + {/* {search button} */} + + + {searchButtonLabel} + + + + + {createButtonLabel} + + + + + {uploadButtonLabel} + + + + + {clearButtonLabel} + + +
+ + {/* config info table */} +
+ + + + ( + <> + + + + )} + /> +
+
+ {/* config edit dialog */} + +
+ + this.handleDialogInputChange('namespace', value)} + /> + + + this.handleDialogInputChange('dataId', value)} + /> + + + this.handleDialogInputChange('key', value)} + /> + + + this.handleDialogInputChange('value', value)} + /> + +
+
+ + {/* config delete dialog*/} + +
+ + + { + this.handleDeleteDialogInputChange('namespace', value)} + } + /> + + + { + this.handleDeleteDialogInputChange('dataId', value)} + } + /> + +
+
+ + {/* config upload dialog*/} + +
+ + + this.handleUploadDialogInputChange('namespace', value)} + /> + + + this.handleUploadDialogInputChange('dataId', value)} + /> + + + false} // Prevent auto-upload + accept={".txt,.text,.yaml,.yml,.properties"} + limit={1} + > + + + +
+
+
+ ); + } +} +export default withRouter(ConfigProvider.config(ConfigInfo, {})); diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss new file mode 100644 index 00000000000..2944f981947 --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.scss @@ -0,0 +1,16 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ diff --git a/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts new file mode 100644 index 00000000000..d30c8ca5a93 --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/pages/ConfigInfo/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import ConfigInfo from './ConfigInfo'; + +export * from './ConfigInfo'; + +export default ConfigInfo; diff --git a/console/src/main/resources/static/console-fe/src/router.tsx b/console/src/main/resources/static/console-fe/src/router.tsx index d881b472d02..88c7be6b920 100644 --- a/console/src/main/resources/static/console-fe/src/router.tsx +++ b/console/src/main/resources/static/console-fe/src/router.tsx @@ -18,10 +18,12 @@ import { HashRouter, Route, Switch, Redirect } from 'react-router-dom'; import Overview from '@/pages/Overview'; import TransactionInfo from '@/pages/TransactionInfo'; import GlobalLockInfo from './pages/GlobalLockInfo'; +import ConfigInfo from "./pages/ConfigInfo"; export default [ // { path: '/', exact: true, render: () => }, // { path: '/Overview', component: Overview }, { path: '/transaction/list', component: TransactionInfo }, { path: '/globallock/list', component: GlobalLockInfo }, + { path: '/config/list', component: ConfigInfo }, ]; diff --git a/console/src/main/resources/static/console-fe/src/service/configInfo.ts b/console/src/main/resources/static/console-fe/src/service/configInfo.ts new file mode 100644 index 00000000000..37e4658d689 --- /dev/null +++ b/console/src/main/resources/static/console-fe/src/service/configInfo.ts @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import {configRequest} from '@/utils/request'; + + +export async function getConfig(params: { namespace: string, dataId: string}): Promise { + const result = await configRequest('/config/getAll', { + method: 'get', + params, + }); + return result; +} + +export async function putConfig(params: { namespace: string, dataId: string, key: string, value: string}): Promise { + const result = await configRequest('/config/put', { + method: 'post', + params, + }); + return result; +} + +export async function deleteConfig(params: { namespace: string, dataId: string, key: string }): Promise { + const result = await configRequest('/config/delete', { + method: 'delete', + params, + }); + return result; +} + +export async function deleteAllConfig(params: { namespace: string, dataId: string}): Promise { + const result = await configRequest('/config/deleteAll', { + method: 'delete', + params, + }); + return result; +} + +export async function uploadConfig(formData: FormData): Promise { + const result = await configRequest('/config/upload', { + method: 'post', + data: formData, + }); + return result; +} + +export async function getClusterInfo(): Promise { + const result = await configRequest('/config/cluster', { + method: 'get', + }); + return result; +} + +export async function getAllNamespaces(): Promise { + const result = await configRequest('/config/getNamespaces', { + method: 'get', + }); + return result; +} + +export async function getAllDataIds(params: { namespace: string}): Promise { + const result = await configRequest('/config/getDataIds', { + method: 'get', + params, + }); + return result; +} diff --git a/console/src/main/resources/static/console-fe/src/utils/request.ts b/console/src/main/resources/static/console-fe/src/utils/request.ts index 47ab7597615..e28c6a2e5c6 100644 --- a/console/src/main/resources/static/console-fe/src/utils/request.ts +++ b/console/src/main/resources/static/console-fe/src/utils/request.ts @@ -90,3 +90,50 @@ const request = () => { }; export default request(); + + +const clusterRequest = () => { + const instance: AxiosInstance = axios.create({ + baseURL: 'metadata/v1', + method: 'get', + }); + + instance.interceptors.request.use((config: AxiosRequestConfig) => { + let authHeader: string | null = localStorage.getItem(AUTHORIZATION_HEADER); + // add jwt header + config.headers[AUTHORIZATION_HEADER] = authHeader; + return config; + }) + + instance.interceptors.response.use( + (response: AxiosResponse): Promise => { + const isSuccess = get(response, 'data.success'); + if (response.status === 200 || isSuccess) { + return Promise.resolve(get(response, 'data')); + } else { + const errorText = + get(response, 'data.errMsg') || + response.statusText; + Message.error(errorText); + return Promise.reject(response); + } + }, + error => { + if (error.response) { + const { status } = error.response; + if (status === 403 || status === 401) { + (window as any).globalHistory.replace('/login'); + return; + } + Message.error(`HTTP ERROR: ${status}`); + } else { + Message.error(API_GENERAL_ERROR_MESSAGE); + } + return Promise.reject(error); + } + ); + + return instance; +}; + +export const configRequest = clusterRequest(); diff --git a/console/src/main/resources/static/css/main.css b/console/src/main/resources/static/css/main.css index 9bba81562b4..5b11cf89e81 100644 --- a/console/src/main/resources/static/css/main.css +++ b/console/src/main/resources/static/css/main.css @@ -3,6 +3,7 @@ + /*! * @alife/theme-xconsole-v4@0.12.0 (https://fusion.design) * @alifd/next@1.24.18 (https://fusion.design) diff --git a/console/src/main/resources/static/js/main.js b/console/src/main/resources/static/js/main.js index 73a295da754..c3a398add79 100644 --- a/console/src/main/resources/static/js/main.js +++ b/console/src/main/resources/static/js/main.js @@ -1,17 +1,17 @@ -!function(n){var r={};function a(e){var t;return(r[e]||(t=r[e]={i:e,l:!1,exports:{}},n[e].call(t.exports,t,t.exports,a),t.l=!0,t)).exports}a.m=n,a.c=r,a.d=function(e,t,n){a.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},a.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},a.t=function(t,e){if(1&e&&(t=a(t)),8&e)return t;if(4&e&&"object"==typeof t&&t&&t.__esModule)return t;var n=Object.create(null);if(a.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:t}),2&e&&"string"!=typeof t)for(var r in t)a.d(n,r,function(e){return t[e]}.bind(null,r));return n},a.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return a.d(t,"a",t),t},a.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},a.p="",a(a.s=383)}([function(e,t,n){e.exports=n(348)()},function(e,t,n){"use strict";e.exports=n(299)},function(e,t,n){"use strict";t.__esModule=!0;var n=n(335),n=(n=n)&&n.__esModule?n:{default:n};t.default=n.default||function(e){for(var t=1;t>>0,r;for(r=0;r0)for(n=0;n=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+r}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 r(e,t,n,r){var a=r;if(typeof r==="string")a=function(){return this[r]()};if(e)ue[e]=a;if(t)ue[t[0]]=function(){return o(a.apply(this,arguments),t[1],t[2])};if(n)ue[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),e)}}function ce(e){if(e.match(/\[[\s\S]/))return e.replace(/^\[|\]$/g,"");return e.replace(/\\/g,"")}function de(r){var a=r.match(ie),e,o;for(e=0,o=a.length;e=0&&se.test(e)){e=e.replace(se,r);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 ye="Invalid date";function ge(){return this._invalidDate}var _e="%d",ve=/\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,r){var a=this._relativeTime[n];return h(a)?a(e,t,n,r):a.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 xe={D:"date",dates:"date",date:"date",d:"day",days:"day",day:"day",e:"weekday",weekdays:"weekday",weekday:"weekday",E:"isoWeekday",isoweekdays:"isoWeekday",isoweekday:"isoWeekday",DDD:"dayOfYear",dayofyears:"dayOfYear",dayofyear:"dayOfYear",h:"hour",hours:"hour",hour:"hour",ms:"millisecond",milliseconds:"millisecond",millisecond:"millisecond",m:"minute",minutes:"minute",minute:"minute",M:"month",months:"month",month:"month",Q:"quarter",quarters:"quarter",quarter:"quarter",s:"second",seconds:"second",second:"second",gg:"weekYear",weekyears:"weekYear",weekyear:"weekYear",GG:"isoWeekYear",isoweekyears:"isoWeekYear",isoweekyear:"isoWeekYear",w:"week",weeks:"week",week:"week",W:"isoWeek",isoweeks:"isoWeek",isoweek:"isoWeek",y:"year",years:"year",year:"year"};function m(e){return typeof e==="string"?xe[e]||xe[e.toLowerCase()]:undefined}function Le(e){var t={},n,r;for(r in e)if(l(e,r)){n=m(r);if(n)t[n]=e[r]}return t}var Se={date:9,day:11,weekday:11,isoWeekday:11,dayOfYear:4,hour:13,millisecond:16,minute:14,month:8,quarter:7,second:15,weekYear:1,isoWeekYear:1,week:5,isoWeek:5,year:1};function Te(e){var t=[],n;for(n in e)if(l(e,n))t.push({unit:n,priority:Se[n]});t.sort(function(e,t){return e.priority-t.priority});return t}var Ce=/\d/,t=/\d\d/,Ee=/\d{3}/,De=/\d{4}/,Ye=/[+-]?\d{6}/,n=/\d\d?/,Oe=/\d\d\d\d?/,Pe=/\d\d\d\d\d\d?/,Ne=/\d{1,3}/,je=/\d{1,4}/,Ae=/[+-]?\d{1,6}/,Re=/\d+/,He=/[+-]?\d+/,Ie=/Z|[+-]\d\d:?\d\d/gi,Fe=/Z|[+-]\d\d(?::?\d\d)?/gi,ze=/[+-]?\d+(\.\d{1,3})?/,We=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,Ve=/^[1-9]\d?/,Be=/^([1-9]\d|\d)/,Ue;function a(e,n,r){Ue[e]=h(n)?n:function(e,t){return e&&r?r:n}}function Ke(e,t){if(!l(Ue,e))return new RegExp($e(e));return Ue[e](t._strict,t._locale)}function $e(e){return y(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,n,r,a){return t||n||r||a}))}function y(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function g(e){if(e<0)return Math.ceil(e)||0;else return Math.floor(e)}function _(e){var t=+e,n=0;if(t!==0&&isFinite(t))n=g(t);return n}var Ue={},Ge={};function v(e,n){var t,r=n,a;if(typeof e==="string")e=[e];if(u(n))r=function(e,t){t[n]=_(e)};a=e.length;for(t=0;t68?1900:2e3)};var nt=at("FullYear",true),S;function rt(){return Qe(this.year())}function at(t,n){return function(e){if(e!=null){it(this,t,e);d.updateOffset(this,n);return this}else return ot(this,t)}}function ot(e,t){if(!e.isValid())return NaN;var n=e._d,r=e._isUTC;switch(t){case"Milliseconds":return r?n.getUTCMilliseconds():n.getMilliseconds();case"Seconds":return r?n.getUTCSeconds():n.getSeconds();case"Minutes":return r?n.getUTCMinutes():n.getMinutes();case"Hours":return r?n.getUTCHours():n.getHours();case"Date":return r?n.getUTCDate():n.getDate();case"Day":return r?n.getUTCDay():n.getDay();case"Month":return r?n.getUTCMonth():n.getMonth();case"FullYear":return r?n.getUTCFullYear():n.getFullYear();default:return NaN}}function it(e,t,n){var r,a,o,i,s;if(!e.isValid()||isNaN(n))return;r=e._d;a=e._isUTC;switch(t){case"Milliseconds":return void(a?r.setUTCMilliseconds(n):r.setMilliseconds(n));case"Seconds":return void(a?r.setUTCSeconds(n):r.setSeconds(n));case"Minutes":return void(a?r.setUTCMinutes(n):r.setMinutes(n));case"Hours":return void(a?r.setUTCHours(n):r.setHours(n));case"Date":return void(a?r.setUTCDate(n):r.setDate(n));case"FullYear":break;default:return}o=n;i=e.month();s=e.date();s=s===29&&i===1&&!Qe(o)?28:s;void(a?r.setUTCFullYear(o,i,s):r.setFullYear(o,i,s))}function st(e){e=m(e);if(h(this[e]))return this[e]();return this}function lt(e,t){if(typeof e==="object"){e=Le(e);var n=Te(e),r,a=n.length;for(r=0;r=0){s=new Date(e+400,t,n,r,a,o,i);if(isFinite(s.getFullYear()))s.setFullYear(e)}else s=new Date(e,t,n,r,a,o,i);return s}function Tt(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 Ct(e,t,n){var r=7+t-n,a=(7+Tt(e,0,r).getUTCDay()-t)%7;return-a+r-1}function Et(e,t,n,r,a){var o=(7+n-r)%7,i=Ct(e,r,a),s=1+7*(t-1)+o+i,l,u;if(s<=0){l=e-1;u=tt(l)+s}else if(s>tt(e)){l=e+1;u=s-tt(e)}else{l=e;u=s}return{year:l,dayOfYear:u}}function Dt(e,t,n){var r=Ct(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1,o,i;if(a<1){i=e.year()-1;o=a+T(i,t,n)}else if(a>T(e.year(),t,n)){o=a-T(e.year(),t,n);i=e.year()+1}else{i=e.year();o=a}return{week:o,year:i}}function T(e,t,n){var r=Ct(e,t,n),a=Ct(e+1,t,n);return(tt(e)-r+a)/7}function Yt(e){return Dt(e,this._week.dow,this._week.doy).week}r("w",["ww",2],"wo","week"),r("W",["WW",2],"Wo","isoWeek"),a("w",n,Ve),a("ww",n,t),a("W",n,Ve),a("WW",n,t),qe(["w","ww","W","WW"],function(e,t,n,r){t[r.substr(0,1)]=_(e)});var Ot={dow:0,doy:6};function Pt(){return this._week.dow}function Nt(){return this._week.doy}function jt(e){var t=this.localeData().week(this);return e==null?t:this.add((e-t)*7,"d")}function At(e){var t=Dt(this,1,4).week;return e==null?t:this.add((e-t)*7,"d")}function Rt(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 Ht(e,t){if(typeof e==="string")return t.weekdaysParse(e)%7||7;return isNaN(e)?null:e}function It(e,t){return e.slice(t,7).concat(e.slice(0,t))}r("d",0,"do","day"),r("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),r("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),r("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),r("e",0,0,"weekday"),r("E",0,0,"isoWeekday"),a("d",n),a("e",n),a("E",n),a("dd",function(e,t){return t.weekdaysMinRegex(e)}),a("ddd",function(e,t){return t.weekdaysShortRegex(e)}),a("dddd",function(e,t){return t.weekdaysRegex(e)}),qe(["dd","ddd","dddd"],function(e,t,n,r){var a=n._locale.weekdaysParse(e,r,n._strict);if(a!=null)t.d=a;else p(n).invalidWeekday=e}),qe(["d","e","E"],function(e,t,n,r){t[r]=_(e)});var Ft="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),zt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Wt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Vt=We,Bt=We,Ut=We;function Kt(e,t){var n=i(this._weekdays)?this._weekdays:this._weekdays[e&&e!==true&&this._weekdays.isFormat.test(t)?"format":"standalone"];return e===true?It(n,this._week.dow):e?n[e.day()]:n}function $t(e){return e===true?It(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort}function Gt(e){return e===true?It(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin}function qt(e,t,n){var r,a,o,i=e.toLocaleLowerCase();if(!this._weekdaysParse){this._weekdaysParse=[];this._shortWeekdaysParse=[];this._minWeekdaysParse=[];for(r=0;r<7;++r){o=c([2e3,1]).day(r);this._minWeekdaysParse[r]=this.weekdaysMin(o,"").toLocaleLowerCase();this._shortWeekdaysParse[r]=this.weekdaysShort(o,"").toLocaleLowerCase();this._weekdaysParse[r]=this.weekdays(o,"").toLocaleLowerCase()}}if(n)if(t==="dddd"){a=S.call(this._weekdaysParse,i);return a!==-1?a:null}else if(t==="ddd"){a=S.call(this._shortWeekdaysParse,i);return a!==-1?a:null}else{a=S.call(this._minWeekdaysParse,i);return a!==-1?a:null}else if(t==="dddd"){a=S.call(this._weekdaysParse,i);if(a!==-1)return a;a=S.call(this._shortWeekdaysParse,i);if(a!==-1)return a;a=S.call(this._minWeekdaysParse,i);return a!==-1?a:null}else if(t==="ddd"){a=S.call(this._shortWeekdaysParse,i);if(a!==-1)return a;a=S.call(this._weekdaysParse,i);if(a!==-1)return a;a=S.call(this._minWeekdaysParse,i);return a!==-1?a:null}else{a=S.call(this._minWeekdaysParse,i);if(a!==-1)return a;a=S.call(this._weekdaysParse,i);if(a!==-1)return a;a=S.call(this._shortWeekdaysParse,i);return a!==-1?a:null}}function Jt(e,t,n){var r,a,o;if(this._weekdaysParseExact)return qt.call(this,e,t,n);if(!this._weekdaysParse){this._weekdaysParse=[];this._minWeekdaysParse=[];this._shortWeekdaysParse=[];this._fullWeekdaysParse=[]}for(r=0;r<7;r++){a=c([2e3,1]).day(r);if(n&&!this._fullWeekdaysParse[r]){this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(a,"").replace(".","\\.?")+"$","i");this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(a,"").replace(".","\\.?")+"$","i");this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(a,"").replace(".","\\.?")+"$","i")}if(!this._weekdaysParse[r]){o="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,"");this._weekdaysParse[r]=new RegExp(o.replace(".",""),"i")}if(n&&t==="dddd"&&this._fullWeekdaysParse[r].test(e))return r;else if(n&&t==="ddd"&&this._shortWeekdaysParse[r].test(e))return r;else if(n&&t==="dd"&&this._minWeekdaysParse[r].test(e))return r;else if(!n&&this._weekdaysParse[r].test(e))return r}}function Qt(e){if(!this.isValid())return e!=null?this:NaN;var t=ot(this,"Day");if(e!=null){e=Rt(e,this.localeData());return this.add(e-t,"d")}else return t}function Zt(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 Xt(e){if(!this.isValid())return e!=null?this:NaN;if(e!=null){var t=Ht(e,this.localeData());return this.day(this.day()%7?t:t-7)}else return this.day()||7}function en(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))rn.call(this);if(e)return this._weekdaysStrictRegex;else return this._weekdaysRegex}else{if(!l(this,"_weekdaysRegex"))this._weekdaysRegex=Vt;return this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex}}function tn(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))rn.call(this);if(e)return this._weekdaysShortStrictRegex;else return this._weekdaysShortRegex}else{if(!l(this,"_weekdaysShortRegex"))this._weekdaysShortRegex=Bt;return this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}}function nn(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))rn.call(this);if(e)return this._weekdaysMinStrictRegex;else return this._weekdaysMinRegex}else{if(!l(this,"_weekdaysMinRegex"))this._weekdaysMinRegex=Ut;return this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}}function rn(){function e(e,t){return t.length-e.length}var t=[],n=[],r=[],a=[],o,i,s,l,u;for(o=0;o<7;o++){i=c([2e3,1]).day(o);s=y(this.weekdaysMin(i,""));l=y(this.weekdaysShort(i,""));u=y(this.weekdays(i,""));t.push(s);n.push(l);r.push(u);a.push(s);a.push(l);a.push(u)}t.sort(e);n.sort(e);r.sort(e);a.sort(e);this._weekdaysRegex=new RegExp("^("+a.join("|")+")","i");this._weekdaysShortRegex=this._weekdaysRegex;this._weekdaysMinRegex=this._weekdaysRegex;this._weekdaysStrictRegex=new RegExp("^("+r.join("|")+")","i");this._weekdaysShortStrictRegex=new RegExp("^("+n.join("|")+")","i");this._weekdaysMinStrictRegex=new RegExp("^("+t.join("|")+")","i")}function an(){return this.hours()%12||12}function on(){return this.hours()||24}function sn(e,t){r(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function ln(e,t){return t._meridiemParse}function un(e){return(e+"").toLowerCase().charAt(0)==="p"}r("H",["HH",2],0,"hour"),r("h",["hh",2],0,an),r("k",["kk",2],0,on),r("hmm",0,0,function(){return""+an.apply(this)+o(this.minutes(),2)}),r("hmmss",0,0,function(){return""+an.apply(this)+o(this.minutes(),2)+o(this.seconds(),2)}),r("Hmm",0,0,function(){return""+this.hours()+o(this.minutes(),2)}),r("Hmmss",0,0,function(){return""+this.hours()+o(this.minutes(),2)+o(this.seconds(),2)}),sn("a",true),sn("A",false),a("a",ln),a("A",ln),a("H",n,Be),a("h",n,Ve),a("k",n,Ve),a("HH",n,t),a("hh",n,t),a("kk",n,t),a("hmm",Oe),a("hmmss",Pe),a("Hmm",Oe),a("Hmmss",Pe),v(["H","HH"],k),v(["k","kk"],function(e,t,n){var r=_(e);t[k]=r===24?0:r}),v(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e);n._meridiem=e}),v(["h","hh"],function(e,t,n){t[k]=_(e);p(n).bigHour=true}),v("hmm",function(e,t,n){var r=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r));p(n).bigHour=true}),v("hmmss",function(e,t,n){var r=e.length-4,a=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r,2));t[L]=_(e.substr(a));p(n).bigHour=true}),v("Hmm",function(e,t,n){var r=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r))}),v("Hmmss",function(e,t,n){var r=e.length-4,a=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r,2));t[L]=_(e.substr(a))});var cn,dn=at("Hours",true);function pn(e,t,n){if(e>11)return n?"pm":"PM";else return n?"am":"AM"}var fn={calendar:ae,longDateFormat:he,invalidDate:ye,ordinal:_e,dayOfMonthOrdinalParse:ve,relativeTime:we,months:dt,monthsShort:pt,week:Ot,weekdays:Ft,weekdaysMin:Wt,weekdaysShort:zt,meridiemParse:/[ap]\.?m?\.?/i},C={},hn={},mn;function yn(e,t){var n,r=Math.min(e.length,t.length);for(n=0;n0){a=bn(o.slice(0,n).join("-"));if(a)return a;if(r&&r.length>=n&&yn(o,r)>=n-1)break;n--}t++}return mn}function vn(e){return!!(e&&e.match("^[^/\\\\]*$"))}function bn(t){var e=null,n;if(C[t]===undefined&&typeof ci!=="undefined"&&ci&&ci.exports&&vn(t))try{e=mn._abbr;n=di;pi(355)("./"+t);wn(e)}catch(e){C[t]=null}return C[t]}function wn(e,t){var n;if(e){if(s(t))n=E(e);else n=Mn(e,t);if(n)mn=n;else if(typeof console!=="undefined"&&console.warn)console.warn("Locale "+e+" not found. Did you forget to load it?")}return mn._abbr}function Mn(e,t){if(t!==null){var n,r=fn;t.abbr=e;if(C[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.");r=C[e]._config}else if(t.parentLocale!=null)if(C[t.parentLocale]!=null)r=C[t.parentLocale]._config;else{n=bn(t.parentLocale);if(n!=null)r=n._config;else{if(!hn[t.parentLocale])hn[t.parentLocale]=[];hn[t.parentLocale].push({name:e,config:t});return null}}C[e]=new re(ne(r,t));if(hn[e])hn[e].forEach(function(e){Mn(e.name,e.config)});wn(e);return C[e]}else{delete C[e];return null}}function kn(e,t){if(t!=null){var n,r,a=fn;if(C[e]!=null&&C[e].parentLocale!=null)C[e].set(ne(C[e]._config,t));else{r=bn(e);if(r!=null)a=r._config;t=ne(a,t);if(r==null)t.abbr=e;n=new re(t);n.parentLocale=C[e];C[e]=n}wn(e)}else if(C[e]!=null)if(C[e].parentLocale!=null){C[e]=C[e].parentLocale;if(e===wn())wn(e)}else if(C[e]!=null)delete C[e];return C[e]}function E(e){var t;if(e&&e._locale&&e._locale._abbr)e=e._locale._abbr;if(!e)return mn;if(!i(e)){t=bn(e);if(t)return t;e=[e]}return _n(e)}function xn(){return X(C)}function Ln(e){var t,n=e._a;if(n&&p(e).overflow===-2){t=n[w]<0||n[w]>11?w:n[M]<1||n[M]>ct(n[b],n[w])?M:n[k]<0||n[k]>24||n[k]===24&&(n[x]!==0||n[L]!==0||n[Ze]!==0)?k:n[x]<0||n[x]>59?x:n[L]<0||n[L]>59?L:n[Ze]<0||n[Ze]>999?Ze:-1;if(p(e)._overflowDayOfYear&&(tM))t=M;if(p(e)._overflowWeeks&&t===-1)t=Xe;if(p(e)._overflowWeekday&&t===-1)t=et;p(e).overflow=t}return e}var Sn=/^\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)?)?$/,Tn=/^\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)?/,En=[["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]],Dn=[["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/]],Yn=/^\/?Date\((-?\d+)/i,On=/^(?:(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}))$/,Pn={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 Nn(e){var t,n,r=e._i,a=Sn.exec(r)||Tn.exec(r),o,i,s,l,u=En.length,c=Dn.length;if(a){p(e).iso=true;for(t=0,n=u;ttt(i)||e._dayOfYear===0)p(e)._overflowDayOfYear=true;n=Tt(i,0,e._dayOfYear);e._a[w]=n.getUTCMonth();e._a[M]=n.getUTCDate()}for(t=0;t<3&&e._a[t]==null;++t)e._a[t]=r[t]=a[t];for(;t<7;t++)e._a[t]=r[t]=e._a[t]==null?t===2?1:0:e._a[t];if(e._a[k]===24&&e._a[x]===0&&e._a[L]===0&&e._a[Ze]===0){e._nextDay=true;e._a[k]=0}e._d=(e._useUTC?Tt:St).apply(null,r);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[k]=24;if(e._w&&typeof e._w.d!=="undefined"&&e._w.d!==o)p(e).weekdayMismatch=true}function Un(e){var t,n,r,a,o,i,s,l,u;t=e._w;if(t.GG!=null||t.W!=null||t.E!=null){o=1;i=4;n=Wn(t.GG,e._a[b],Dt(D(),1,4).year);r=Wn(t.W,1);a=Wn(t.E,1);if(a<1||a>7)l=true}else{o=e._locale._week.dow;i=e._locale._week.doy;u=Dt(D(),o,i);n=Wn(t.gg,e._a[b],u.year);r=Wn(t.w,u.week);if(t.d!=null){a=t.d;if(a<0||a>6)l=true}else if(t.e!=null){a=t.e+o;if(t.e<0||t.e>6)l=true}else a=o}if(r<1||r>T(n,o,i))p(e)._overflowWeeks=true;else if(l!=null)p(e)._overflowWeekday=true;else{s=Et(n,r,a,o,i);e._a[b]=s.year;e._dayOfYear=s.dayOfYear}}function Kn(e){if(e._f===d.ISO_8601){Nn(e);return}if(e._f===d.RFC_2822){Fn(e);return}e._a=[];p(e).empty=true;var t=""+e._i,n,r,a,o,i,s=t.length,l=0,u,c;a=fe(e._f,e._locale).match(ie)||[];c=a.length;for(n=0;n0)p(e).unusedInput.push(i);t=t.slice(t.indexOf(r)+r.length);l+=r.length}if(ue[o]){if(r)p(e).empty=false;else p(e).unusedTokens.push(o);Je(o,r,e)}else if(e._strict&&!r)p(e).unusedTokens.push(o)}p(e).charsLeftOver=s-l;if(t.length>0)p(e).unusedInput.push(t);if(e._a[k]<=12&&p(e).bigHour===true&&e._a[k]>0)p(e).bigHour=undefined;p(e).parsedDateParts=e._a.slice(0);p(e).meridiem=e._meridiem;e._a[k]=$n(e._locale,e._a[k],e._meridiem);u=p(e).era;if(u!==null)e._a[b]=e._locale.erasConvertYear(u,e._a[b]);Bn(e);Ln(e)}function $n(e,t,n){var r;if(n==null)return t;if(e.meridiemHour!=null)return e.meridiemHour(t,n);else if(e.isPM!=null){r=e.isPM(n);if(r&&t<12)t+=12;if(!r&&t===12)t=0;return t}else return t}function Gn(e){var t,n,r,a,o,i,s=false,l=e._f.length;if(l===0){p(e).invalidFormat=true;e._d=new Date(NaN);return}for(a=0;athis?this:e;else return K()});function nr(e,t){var n,r;if(t.length===1&&i(t[0]))t=t[0];if(!t.length)return D();n=t[0];for(r=1;rthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Sr(){if(!s(this._isDSTShifted))return this._isDSTShifted;var e={},t;q(e,this);e=Qn(e);if(e._a){t=e._isUTC?c(e._a):D(e._a);this._isDSTShifted=this.isValid()&&fr(e._a,t.toArray())>0}else this._isDSTShifted=false;return this._isDSTShifted}function Tr(){return this.isValid()?!this._isUTC:false}function Cr(){return this.isValid()?this._isUTC:false}function Er(){return this.isValid()?this._isUTC&&this._offset===0:false}d.updateOffset=function(){};var Dr=/^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,Yr=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function Y(e,t){var n=e,r=null,a,o,i;if(dr(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(r=Dr.exec(e)){a=r[1]==="-"?-1:1;n={y:0,d:_(r[M])*a,h:_(r[k])*a,m:_(r[x])*a,s:_(r[L])*a,ms:_(pr(r[Ze]*1e3))*a}}else if(r=Yr.exec(e)){a=r[1]==="-"?-1:1;n={y:Or(r[2],a),M:Or(r[3],a),w:Or(r[4],a),d:Or(r[5],a),h:Or(r[6],a),m:Or(r[7],a),s:Or(r[8],a)}}else if(n==null)n={};else if(typeof n==="object"&&("from"in n||"to"in n)){i=Nr(D(n.from),D(n.to));n={};n.ms=i.milliseconds;n.M=i.months}o=new cr(n);if(dr(e)&&l(e,"_locale"))o._locale=e._locale;if(dr(e)&&l(e,"_isValid"))o._isValid=e._isValid;return o}function Or(e,t){var n=e&&parseFloat(e.replace(",","."));return(isNaN(n)?0:n)*t}function Pr(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 Nr(e,t){var n;if(!(e.isValid()&&t.isValid()))return{milliseconds:0,months:0};t=gr(t,e);if(e.isBefore(t))n=Pr(e,t);else{n=Pr(t,e);n.milliseconds=-n.milliseconds;n.months=-n.months}return n}function jr(a,o){return function(e,t){var n,r;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.");r=e;e=t;t=r}n=Y(e,t);Ar(this,n,a);return this}}function Ar(e,t,n,r){var a=t._milliseconds,o=pr(t._days),i=pr(t._months);if(!e.isValid())return;r=r==null?true:r;if(i)bt(e,ot(e,"Month")+i*n);if(o)it(e,"Date",ot(e,"Date")+o*n);if(a)e._d.setTime(e._d.valueOf()+a*n);if(r)d.updateOffset(e,o||i)}Y.fn=cr.prototype,Y.invalid=ur;var Rr=jr(1,"add"),Hr=jr(-1,"subtract");function Ir(e){return typeof e==="string"||e instanceof String}function Fr(e){return f(e)||z(e)||Ir(e)||u(e)||Wr(e)||zr(e)||e===null||e===undefined}function zr(e){var t=I(e)&&!F(e),n=false,r=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms"],a,o,i=r.length;for(a=0;an.valueOf();else return n.valueOf()9999)return pe(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",pe(n,"Z"));return pe(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")}function ra(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e="moment",t="",n,r,a,o;if(!this.isLocal()){e=this.utcOffset()===0?"moment.utc":"moment.parseZone";t="Z"}n="["+e+'("]';r=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY";a="-MM-DD[T]HH:mm:ss.SSS";o=t+'[")]';return this.format(n+r+a+o)}function aa(e){if(!e)e=this.isUtc()?d.defaultFormatUtc:d.defaultFormat;var t=pe(this,e);return this.localeData().postformat(t)}function oa(e,t){if(this.isValid()&&(f(e)&&e.isValid()||D(e).isValid()))return Y({to:this,from:e}).locale(this.locale()).humanize(!t);else return this.localeData().invalidDate()}function ia(e){return this.from(D(),e)}function sa(e,t){if(this.isValid()&&(f(e)&&e.isValid()||D(e).isValid()))return Y({from:this,to:e}).locale(this.locale()).humanize(!t);else return this.localeData().invalidDate()}function la(e){return this.to(D(),e)}function ua(e){var t;if(e===undefined)return this._locale._abbr;else{t=E(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 ca=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 da(){return this._locale}var pa=1e3,fa=60*pa,ha=60*fa,ma=(365*400+97)*24*ha;function ya(e,t){return(e%t+t)%t}function ga(e,t,n){if(e<100&&e>=0)return new Date(e+400,t,n)-ma;else return new Date(e,t,n).valueOf()}function _a(e,t,n){if(e<100&&e>=0)return Date.UTC(e+400,t,n)-ma;else return Date.UTC(e,t,n)}function va(e){var t,n;e=m(e);if(e===undefined||e==="millisecond"||!this.isValid())return this;n=this._isUTC?_a:ga;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-=ya(t+(this._isUTC?0:this.utcOffset()*fa),ha);break;case"minute":t=this._d.valueOf();t-=ya(t,fa);break;case"second":t=this._d.valueOf();t-=ya(t,pa);break}this._d.setTime(t);d.updateOffset(this,true);return this}function ba(e){var t,n;e=m(e);if(e===undefined||e==="millisecond"||!this.isValid())return this;n=this._isUTC?_a:ga;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+=ha-ya(t+(this._isUTC?0:this.utcOffset()*fa),ha)-1;break;case"minute":t=this._d.valueOf();t+=fa-ya(t,fa)-1;break;case"second":t=this._d.valueOf();t+=pa-ya(t,pa)-1;break}this._d.setTime(t);d.updateOffset(this,true);return this}function wa(){return this._d.valueOf()-(this._offset||0)*6e4}function Ma(){return Math.floor(this.valueOf()/1e3)}function ka(){return new Date(this.valueOf())}function xa(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]}function La(){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 Sa(){return this.isValid()?this.toISOString():null}function Ta(){return U(this)}function Ca(){return V({},p(this))}function Ea(){return p(this).overflow}function Da(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Ya(e,t){var n,r,a,o=this._eras||E("en")._eras;for(n=0,r=o.length;n=0)return o[r]}}function Pa(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 Na(){var e,t,n,r=this.localeData().eras();for(e=0,t=r.length;eo)t=o;return eo.call(this,e,t,n,r,a)}}function eo(e,t,n,r,a){var o=Et(e,t,n,r,a),i=Tt(o.year,0,o.dayOfYear);this.year(i.getUTCFullYear());this.month(i.getUTCMonth());this.date(i.getUTCDate());return this}function to(e){return e==null?Math.ceil((this.month()+1)/3):this.month((e-1)*3+this.month()%3)}r("N",0,0,"eraAbbr"),r("NN",0,0,"eraAbbr"),r("NNN",0,0,"eraAbbr"),r("NNNN",0,0,"eraName"),r("NNNNN",0,0,"eraNarrow"),r("y",["y",1],"yo","eraYear"),r("y",["yy",2],0,"eraYear"),r("y",["yyy",3],0,"eraYear"),r("y",["yyyy",4],0,"eraYear"),a("N",za),a("NN",za),a("NNN",za),a("NNNN",Wa),a("NNNNN",Va),v(["N","NN","NNN","NNNN","NNNNN"],function(e,t,n,r){var a=n._locale.erasParse(e,r,n._strict);if(a)p(n).era=a;else p(n).invalidEra=e}),a("y",Re),a("yy",Re),a("yyy",Re),a("yyyy",Re),a("yo",Ba),v(["y","yy","yyy","yyyy"],b),v(["yo"],function(e,t,n,r){var a;if(n._locale._eraYearOrdinalRegex)a=e.match(n._locale._eraYearOrdinalRegex);if(n._locale.eraYearOrdinalParse)t[b]=n._locale.eraYearOrdinalParse(e,a);else t[b]=parseInt(e,10)}),r(0,["gg",2],0,function(){return this.weekYear()%100}),r(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ka("gggg","weekYear"),Ka("ggggg","weekYear"),Ka("GGGG","isoWeekYear"),Ka("GGGGG","isoWeekYear"),a("G",He),a("g",He),a("GG",n,t),a("gg",n,t),a("GGGG",je,De),a("gggg",je,De),a("GGGGG",Ae,Ye),a("ggggg",Ae,Ye),qe(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,r){t[r.substr(0,2)]=_(e)}),qe(["gg","GG"],function(e,t,n,r){t[r]=d.parseTwoDigitYear(e)}),r("Q",0,"Qo","quarter"),a("Q",Ce),v("Q",function(e,t){t[w]=(_(e)-1)*3}),r("D",["DD",2],"Do","date"),a("D",n,Ve),a("DD",n,t),a("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),v(["D","DD"],M),v("Do",function(e,t){t[M]=_(e.match(n)[0])});var no=at("Date",true);function ro(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return e==null?t:this.add(e-t,"d")}r("DDD",["DDDD",3],"DDDo","dayOfYear"),a("DDD",Ne),a("DDDD",Ee),v(["DDD","DDDD"],function(e,t,n){n._dayOfYear=_(e)}),r("m",["mm",2],0,"minute"),a("m",n,Be),a("mm",n,t),v(["m","mm"],x);var ao=at("Minutes",false),oo=(r("s",["ss",2],0,"second"),a("s",n,Be),a("ss",n,t),v(["s","ss"],L),at("Seconds",false)),io,so;for(r("S",0,0,function(){return~~(this.millisecond()/100)}),r(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),r(0,["SSS",3],0,"millisecond"),r(0,["SSSS",4],0,function(){return this.millisecond()*10}),r(0,["SSSSS",5],0,function(){return this.millisecond()*100}),r(0,["SSSSSS",6],0,function(){return this.millisecond()*1e3}),r(0,["SSSSSSS",7],0,function(){return this.millisecond()*1e4}),r(0,["SSSSSSSS",8],0,function(){return this.millisecond()*1e5}),r(0,["SSSSSSSSS",9],0,function(){return this.millisecond()*1e6}),a("S",Ne,Ce),a("SS",Ne,t),a("SSS",Ne,Ee),io="SSSS";io.length<=9;io+="S")a(io,Re);function lo(e,t){t[Ze]=_(("0."+e)*1e3)}for(io="S";io.length<=9;io+="S")v(io,lo);function uo(){return this._isUTC?"UTC":""}function co(){return this._isUTC?"Coordinated Universal Time":""}so=at("Milliseconds",false),r("z",0,0,"zoneAbbr"),r("zz",0,0,"zoneName");var O=J.prototype;if(O.add=Rr,O.calendar=Ur,O.clone=Kr,O.diff=Xr,O.endOf=ba,O.format=aa,O.from=oa,O.fromNow=ia,O.to=sa,O.toNow=la,O.get=st,O.invalidAt=Ea,O.isAfter=$r,O.isBefore=Gr,O.isBetween=qr,O.isSame=Jr,O.isSameOrAfter=Qr,O.isSameOrBefore=Zr,O.isValid=Ta,O.lang=ca,O.locale=ua,O.localeData=da,O.max=tr,O.min=er,O.parsingFlags=Ca,O.set=lt,O.startOf=va,O.subtract=Hr,O.toArray=xa,O.toObject=La,O.toDate=ka,O.toISOString=na,O.inspect=ra,typeof Symbol!=="undefined"&&Symbol.for!=null)O[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"};function po(e){return D(e*1e3)}function fo(){return D.apply(null,arguments).parseZone()}function ho(e){return e}O.toJSON=Sa,O.toString=ta,O.unix=Ma,O.valueOf=wa,O.creationData=Da,O.eraName=Na,O.eraNarrow=ja,O.eraAbbr=Aa,O.eraYear=Ra,O.year=nt,O.isLeapYear=rt,O.weekYear=$a,O.isoWeekYear=Ga,O.quarter=O.quarters=to,O.month=wt,O.daysInMonth=Mt,O.week=O.weeks=jt,O.isoWeek=O.isoWeeks=At,O.weeksInYear=Qa,O.weeksInWeekYear=Za,O.isoWeeksInYear=qa,O.isoWeeksInISOWeekYear=Ja,O.date=no,O.day=O.days=Qt,O.weekday=Zt,O.isoWeekday=Xt,O.dayOfYear=ro,O.hour=O.hours=dn,O.minute=O.minutes=ao,O.second=O.seconds=oo,O.millisecond=O.milliseconds=so,O.utcOffset=vr,O.utc=wr,O.local=Mr,O.parseZone=kr,O.hasAlignedHourOffset=xr,O.isDST=Lr,O.isLocal=Tr,O.isUtcOffset=Cr,O.isUtc=Er,O.isUTC=Er,O.zoneAbbr=uo,O.zoneName=co,O.dates=e("dates accessor is deprecated. Use date instead.",no),O.months=e("months accessor is deprecated. Use month instead",wt),O.years=e("years accessor is deprecated. Use year instead",nt),O.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",br),O.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Sr);var P=re.prototype;function mo(e,t,n,r){var a=E(),o=c().set(r,t);return a[n](o,e)}function yo(e,t,n){if(u(e)){t=e;e=undefined}e=e||"";if(t!=null)return mo(e,t,n,"month");var r,a=[];for(r=0;r<12;r++)a[r]=mo(e,r,n,"month");return a}function go(e,t,n,r){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 a=E(),o=e?a._week.dow:0,i,s=[];if(n!=null)return mo(t,(n+o)%7,r,"day");for(i=0;i<7;i++)s[i]=mo(t,(i+o)%7,r,"day");return s}function _o(e,t){return yo(e,t,"months")}function vo(e,t){return yo(e,t,"monthsShort")}function bo(e,t,n){return go(e,t,n,"weekdays")}function wo(e,t,n){return go(e,t,n,"weekdaysShort")}function Mo(e,t,n){return go(e,t,n,"weekdaysMin")}P.calendar=oe,P.longDateFormat=me,P.invalidDate=ge,P.ordinal=be,P.preparse=ho,P.postformat=ho,P.relativeTime=Me,P.pastFuture=ke,P.set=te,P.eras=Ya,P.erasParse=Oa,P.erasConvertYear=Pa,P.erasAbbrRegex=Ia,P.erasNameRegex=Ha,P.erasNarrowRegex=Fa,P.months=yt,P.monthsShort=gt,P.monthsParse=vt,P.monthsRegex=xt,P.monthsShortRegex=kt,P.week=Yt,P.firstDayOfYear=Nt,P.firstDayOfWeek=Pt,P.weekdays=Kt,P.weekdaysMin=Gt,P.weekdaysShort=$t,P.weekdaysParse=Jt,P.weekdaysRegex=en,P.weekdaysShortRegex=tn,P.weekdaysMinRegex=nn,P.isPM=un,P.meridiem=pn,wn("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=_(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.",wn),d.langData=e("moment.langData is deprecated. Use moment.localeData instead.",E);var N=Math.abs;function ko(){var e=this._data;this._milliseconds=N(this._milliseconds);this._days=N(this._days);this._months=N(this._months);e.milliseconds=N(e.milliseconds);e.seconds=N(e.seconds);e.minutes=N(e.minutes);e.hours=N(e.hours);e.months=N(e.months);e.years=N(e.years);return this}function xo(e,t,n,r){var a=Y(t,n);e._milliseconds+=r*a._milliseconds;e._days+=r*a._days;e._months+=r*a._months;return e._bubble()}function Lo(e,t){return xo(this,e,t,1)}function So(e,t){return xo(this,e,t,-1)}function To(e){if(e<0)return Math.floor(e);else return Math.ceil(e)}function Co(){var e=this._milliseconds,t=this._days,n=this._months,r=this._data,a,o,i,s,l;if(!(e>=0&&t>=0&&n>=0||e<=0&&t<=0&&n<=0)){e+=To(Do(n)+t)*864e5;t=0;n=0}r.milliseconds=e%1e3;a=g(e/1e3);r.seconds=a%60;o=g(a/60);r.minutes=o%60;i=g(o/60);r.hours=i%24;t+=g(i/24);l=g(Eo(t));n+=l;t-=To(Do(l));s=g(n/12);n%=12;r.days=t;r.months=n;r.years=s;return this}function Eo(e){return e*4800/146097}function Do(e){return e*146097/4800}function Yo(e){if(!this.isValid())return NaN;var t,n,r=this._milliseconds;e=m(e);if(e==="month"||e==="quarter"||e==="year"){t=this._days+r/864e5;n=this._months+Eo(t);switch(e){case"month":return n;case"quarter":return n/3;case"year":return n/12}}else{t=this._days+Math.round(Do(this._months));switch(e){case"week":return t/7+r/6048e5;case"day":return t+r/864e5;case"hour":return t*24+r/36e5;case"minute":return t*1440+r/6e4;case"second":return t*86400+r/1e3;case"millisecond":return Math.floor(t*864e5)+r;default:throw new Error("Unknown unit "+e)}}}function Oo(e){return function(){return this.as(e)}}var Po=Oo("ms"),No=Oo("s"),jo=Oo("m"),Ao=Oo("h"),Ro=Oo("d"),Ho=Oo("w"),Io=Oo("M"),Fo=Oo("Q"),zo=Oo("y"),Wo=Po;function Vo(){return Y(this)}function Bo(e){e=m(e);return this.isValid()?this[e+"s"]():NaN}function Uo(e){return function(){return this.isValid()?this._data[e]:NaN}}var Ko=Uo("milliseconds"),$o=Uo("seconds"),Go=Uo("minutes"),qo=Uo("hours"),Jo=Uo("days"),Qo=Uo("months"),Zo=Uo("years");function Xo(){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,r,a){return a.relativeTime(t||1,!!n,e,r)}function ri(e,t,n,r){var a=Y(e).abs(),o=ei(a.as("s")),i=ei(a.as("m")),s=ei(a.as("h")),l=ei(a.as("d")),u=ei(a.as("M")),c=ei(a.as("w")),d=ei(a.as("y")),p=o<=n.ss&&["s",o]||o0;p[4]=r;return ni.apply(null,p)}function ai(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,r=ti,a,o;if(typeof e==="object"){t=e;e=false}if(typeof e==="boolean")n=e;if(typeof t==="object"){r=Object.assign({},ti,t);if(t.s!=null&&t.ss==null)r.ss=t.s-1}a=this.localeData();o=ri(this,!n,r,a);if(n)o=a.pastFuture(+this,o);return a.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),r,a,o,i,s=this.asSeconds(),l,u,c,d;if(!s)return"P0D";r=g(e/60);a=g(r/60);e%=60;r%=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":"")+(a||r||e?"T":"")+(a?d+a+"H":"")+(r?d+r+"M":"")+(e?d+i+"S":"")}var j=cr.prototype;return j.isValid=lr,j.abs=ko,j.add=Lo,j.subtract=So,j.as=Yo,j.asMilliseconds=Po,j.asSeconds=No,j.asMinutes=jo,j.asHours=Ao,j.asDays=Ro,j.asWeeks=Ho,j.asMonths=Io,j.asQuarters=Fo,j.asYears=zo,j.valueOf=Wo,j._bubble=Co,j.clone=Vo,j.get=Bo,j.milliseconds=Ko,j.seconds=$o,j.minutes=Go,j.hours=qo,j.days=Jo,j.weeks=Xo,j.months=Qo,j.years=Zo,j.humanize=ii,j.toISOString=ui,j.toString=ui,j.toJSON=ui,j.locale=ua,j.localeData=da,j.toIsoString=e("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ui),j.lang=ca,r("X",0,0,"unix"),r("x",0,0,"valueOf"),a("x",He),a("X",ze),v("X",function(e,t,n){n._d=new Date(parseFloat(e)*1e3)}),v("x",function(e,t,n){n._d=new Date(_(e))}), +ci.exports=function(){"use strict";var R,A;function d(){return R.apply(null,arguments)}function I(e){R=e}function i(e){return e instanceof Array||Object.prototype.toString.call(e)==="[object Array]"}function H(e){return e!=null&&Object.prototype.toString.call(e)==="[object Object]"}function l(e,t){return Object.prototype.hasOwnProperty.call(e,t)}function F(e){if(Object.getOwnPropertyNames)return Object.getOwnPropertyNames(e).length===0;else{var t;for(t in e)if(l(e,t))return false;return true}}function s(e){return e===void 0}function u(e){return typeof e==="number"||Object.prototype.toString.call(e)==="[object Number]"}function z(e){return e instanceof Date||Object.prototype.toString.call(e)==="[object Date]"}function W(e,t){var n=[],r,a=e.length;for(r=0;r>>0,r;for(r=0;r0)for(n=0;n=0;return(o?n?"+":"":"-")+Math.pow(10,Math.max(0,a)).toString().substr(1)+r}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 r(e,t,n,r){var a=r;if(typeof r==="string")a=function(){return this[r]()};if(e)ue[e]=a;if(t)ue[t[0]]=function(){return o(a.apply(this,arguments),t[1],t[2])};if(n)ue[n]=function(){return this.localeData().ordinal(a.apply(this,arguments),e)}}function ce(e){if(e.match(/\[[\s\S]/))return e.replace(/^\[|\]$/g,"");return e.replace(/\\/g,"")}function de(r){var a=r.match(ie),e,o;for(e=0,o=a.length;e=0&&se.test(e)){e=e.replace(se,r);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 ye="Invalid date";function ge(){return this._invalidDate}var _e="%d",ve=/\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,r){var a=this._relativeTime[n];return h(a)?a(e,t,n,r):a.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 xe={D:"date",dates:"date",date:"date",d:"day",days:"day",day:"day",e:"weekday",weekdays:"weekday",weekday:"weekday",E:"isoWeekday",isoweekdays:"isoWeekday",isoweekday:"isoWeekday",DDD:"dayOfYear",dayofyears:"dayOfYear",dayofyear:"dayOfYear",h:"hour",hours:"hour",hour:"hour",ms:"millisecond",milliseconds:"millisecond",millisecond:"millisecond",m:"minute",minutes:"minute",minute:"minute",M:"month",months:"month",month:"month",Q:"quarter",quarters:"quarter",quarter:"quarter",s:"second",seconds:"second",second:"second",gg:"weekYear",weekyears:"weekYear",weekyear:"weekYear",GG:"isoWeekYear",isoweekyears:"isoWeekYear",isoweekyear:"isoWeekYear",w:"week",weeks:"week",week:"week",W:"isoWeek",isoweeks:"isoWeek",isoweek:"isoWeek",y:"year",years:"year",year:"year"};function m(e){return typeof e==="string"?xe[e]||xe[e.toLowerCase()]:undefined}function Le(e){var t={},n,r;for(r in e)if(l(e,r)){n=m(r);if(n)t[n]=e[r]}return t}var Se={date:9,day:11,weekday:11,isoWeekday:11,dayOfYear:4,hour:13,millisecond:16,minute:14,month:8,quarter:7,second:15,weekYear:1,isoWeekYear:1,week:5,isoWeek:5,year:1};function Te(e){var t=[],n;for(n in e)if(l(e,n))t.push({unit:n,priority:Se[n]});t.sort(function(e,t){return e.priority-t.priority});return t}var Ce=/\d/,t=/\d\d/,Ee=/\d{3}/,De=/\d{4}/,Oe=/[+-]?\d{6}/,n=/\d\d?/,Ye=/\d\d\d\d?/,Pe=/\d\d\d\d\d\d?/,Ne=/\d{1,3}/,je=/\d{1,4}/,Re=/[+-]?\d{1,6}/,Ae=/\d+/,Ie=/[+-]?\d+/,He=/Z|[+-]\d\d:?\d\d/gi,Fe=/Z|[+-]\d\d(?::?\d\d)?/gi,ze=/[+-]?\d+(\.\d{1,3})?/,We=/[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i,Ve=/^[1-9]\d?/,Be=/^([1-9]\d|\d)/,Ue;function a(e,n,r){Ue[e]=h(n)?n:function(e,t){return e&&r?r:n}}function Ke(e,t){if(!l(Ue,e))return new RegExp($e(e));return Ue[e](t._strict,t._locale)}function $e(e){return y(e.replace("\\","").replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g,function(e,t,n,r,a){return t||n||r||a}))}function y(e){return e.replace(/[-\/\\^$*+?.()|[\]{}]/g,"\\$&")}function g(e){if(e<0)return Math.ceil(e)||0;else return Math.floor(e)}function _(e){var t=+e,n=0;if(t!==0&&isFinite(t))n=g(t);return n}var Ue={},Ge={};function v(e,n){var t,r=n,a;if(typeof e==="string")e=[e];if(u(n))r=function(e,t){t[n]=_(e)};a=e.length;for(t=0;t68?1900:2e3)};var nt=at("FullYear",true),S;function rt(){return Qe(this.year())}function at(t,n){return function(e){if(e!=null){it(this,t,e);d.updateOffset(this,n);return this}else return ot(this,t)}}function ot(e,t){if(!e.isValid())return NaN;var n=e._d,r=e._isUTC;switch(t){case"Milliseconds":return r?n.getUTCMilliseconds():n.getMilliseconds();case"Seconds":return r?n.getUTCSeconds():n.getSeconds();case"Minutes":return r?n.getUTCMinutes():n.getMinutes();case"Hours":return r?n.getUTCHours():n.getHours();case"Date":return r?n.getUTCDate():n.getDate();case"Day":return r?n.getUTCDay():n.getDay();case"Month":return r?n.getUTCMonth():n.getMonth();case"FullYear":return r?n.getUTCFullYear():n.getFullYear();default:return NaN}}function it(e,t,n){var r,a,o,i,s;if(!e.isValid()||isNaN(n))return;r=e._d;a=e._isUTC;switch(t){case"Milliseconds":return void(a?r.setUTCMilliseconds(n):r.setMilliseconds(n));case"Seconds":return void(a?r.setUTCSeconds(n):r.setSeconds(n));case"Minutes":return void(a?r.setUTCMinutes(n):r.setMinutes(n));case"Hours":return void(a?r.setUTCHours(n):r.setHours(n));case"Date":return void(a?r.setUTCDate(n):r.setDate(n));case"FullYear":break;default:return}o=n;i=e.month();s=e.date();s=s===29&&i===1&&!Qe(o)?28:s;void(a?r.setUTCFullYear(o,i,s):r.setFullYear(o,i,s))}function st(e){e=m(e);if(h(this[e]))return this[e]();return this}function lt(e,t){if(typeof e==="object"){e=Le(e);var n=Te(e),r,a=n.length;for(r=0;r=0){s=new Date(e+400,t,n,r,a,o,i);if(isFinite(s.getFullYear()))s.setFullYear(e)}else s=new Date(e,t,n,r,a,o,i);return s}function Tt(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 Ct(e,t,n){var r=7+t-n,a=(7+Tt(e,0,r).getUTCDay()-t)%7;return-a+r-1}function Et(e,t,n,r,a){var o=(7+n-r)%7,i=Ct(e,r,a),s=1+7*(t-1)+o+i,l,u;if(s<=0){l=e-1;u=tt(l)+s}else if(s>tt(e)){l=e+1;u=s-tt(e)}else{l=e;u=s}return{year:l,dayOfYear:u}}function Dt(e,t,n){var r=Ct(e.year(),t,n),a=Math.floor((e.dayOfYear()-r-1)/7)+1,o,i;if(a<1){i=e.year()-1;o=a+T(i,t,n)}else if(a>T(e.year(),t,n)){o=a-T(e.year(),t,n);i=e.year()+1}else{i=e.year();o=a}return{week:o,year:i}}function T(e,t,n){var r=Ct(e,t,n),a=Ct(e+1,t,n);return(tt(e)-r+a)/7}function Ot(e){return Dt(e,this._week.dow,this._week.doy).week}r("w",["ww",2],"wo","week"),r("W",["WW",2],"Wo","isoWeek"),a("w",n,Ve),a("ww",n,t),a("W",n,Ve),a("WW",n,t),qe(["w","ww","W","WW"],function(e,t,n,r){t[r.substr(0,1)]=_(e)});var Yt={dow:0,doy:6};function Pt(){return this._week.dow}function Nt(){return this._week.doy}function jt(e){var t=this.localeData().week(this);return e==null?t:this.add((e-t)*7,"d")}function Rt(e){var t=Dt(this,1,4).week;return e==null?t:this.add((e-t)*7,"d")}function At(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 Ht(e,t){return e.slice(t,7).concat(e.slice(0,t))}r("d",0,"do","day"),r("dd",0,0,function(e){return this.localeData().weekdaysMin(this,e)}),r("ddd",0,0,function(e){return this.localeData().weekdaysShort(this,e)}),r("dddd",0,0,function(e){return this.localeData().weekdays(this,e)}),r("e",0,0,"weekday"),r("E",0,0,"isoWeekday"),a("d",n),a("e",n),a("E",n),a("dd",function(e,t){return t.weekdaysMinRegex(e)}),a("ddd",function(e,t){return t.weekdaysShortRegex(e)}),a("dddd",function(e,t){return t.weekdaysRegex(e)}),qe(["dd","ddd","dddd"],function(e,t,n,r){var a=n._locale.weekdaysParse(e,r,n._strict);if(a!=null)t.d=a;else p(n).invalidWeekday=e}),qe(["d","e","E"],function(e,t,n,r){t[r]=_(e)});var Ft="Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),zt="Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),Wt="Su_Mo_Tu_We_Th_Fr_Sa".split("_"),Vt=We,Bt=We,Ut=We;function Kt(e,t){var n=i(this._weekdays)?this._weekdays:this._weekdays[e&&e!==true&&this._weekdays.isFormat.test(t)?"format":"standalone"];return e===true?Ht(n,this._week.dow):e?n[e.day()]:n}function $t(e){return e===true?Ht(this._weekdaysShort,this._week.dow):e?this._weekdaysShort[e.day()]:this._weekdaysShort}function Gt(e){return e===true?Ht(this._weekdaysMin,this._week.dow):e?this._weekdaysMin[e.day()]:this._weekdaysMin}function qt(e,t,n){var r,a,o,i=e.toLocaleLowerCase();if(!this._weekdaysParse){this._weekdaysParse=[];this._shortWeekdaysParse=[];this._minWeekdaysParse=[];for(r=0;r<7;++r){o=c([2e3,1]).day(r);this._minWeekdaysParse[r]=this.weekdaysMin(o,"").toLocaleLowerCase();this._shortWeekdaysParse[r]=this.weekdaysShort(o,"").toLocaleLowerCase();this._weekdaysParse[r]=this.weekdays(o,"").toLocaleLowerCase()}}if(n)if(t==="dddd"){a=S.call(this._weekdaysParse,i);return a!==-1?a:null}else if(t==="ddd"){a=S.call(this._shortWeekdaysParse,i);return a!==-1?a:null}else{a=S.call(this._minWeekdaysParse,i);return a!==-1?a:null}else if(t==="dddd"){a=S.call(this._weekdaysParse,i);if(a!==-1)return a;a=S.call(this._shortWeekdaysParse,i);if(a!==-1)return a;a=S.call(this._minWeekdaysParse,i);return a!==-1?a:null}else if(t==="ddd"){a=S.call(this._shortWeekdaysParse,i);if(a!==-1)return a;a=S.call(this._weekdaysParse,i);if(a!==-1)return a;a=S.call(this._minWeekdaysParse,i);return a!==-1?a:null}else{a=S.call(this._minWeekdaysParse,i);if(a!==-1)return a;a=S.call(this._weekdaysParse,i);if(a!==-1)return a;a=S.call(this._shortWeekdaysParse,i);return a!==-1?a:null}}function Jt(e,t,n){var r,a,o;if(this._weekdaysParseExact)return qt.call(this,e,t,n);if(!this._weekdaysParse){this._weekdaysParse=[];this._minWeekdaysParse=[];this._shortWeekdaysParse=[];this._fullWeekdaysParse=[]}for(r=0;r<7;r++){a=c([2e3,1]).day(r);if(n&&!this._fullWeekdaysParse[r]){this._fullWeekdaysParse[r]=new RegExp("^"+this.weekdays(a,"").replace(".","\\.?")+"$","i");this._shortWeekdaysParse[r]=new RegExp("^"+this.weekdaysShort(a,"").replace(".","\\.?")+"$","i");this._minWeekdaysParse[r]=new RegExp("^"+this.weekdaysMin(a,"").replace(".","\\.?")+"$","i")}if(!this._weekdaysParse[r]){o="^"+this.weekdays(a,"")+"|^"+this.weekdaysShort(a,"")+"|^"+this.weekdaysMin(a,"");this._weekdaysParse[r]=new RegExp(o.replace(".",""),"i")}if(n&&t==="dddd"&&this._fullWeekdaysParse[r].test(e))return r;else if(n&&t==="ddd"&&this._shortWeekdaysParse[r].test(e))return r;else if(n&&t==="dd"&&this._minWeekdaysParse[r].test(e))return r;else if(!n&&this._weekdaysParse[r].test(e))return r}}function Qt(e){if(!this.isValid())return e!=null?this:NaN;var t=ot(this,"Day");if(e!=null){e=At(e,this.localeData());return this.add(e-t,"d")}else return t}function Xt(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 Zt(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 en(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))rn.call(this);if(e)return this._weekdaysStrictRegex;else return this._weekdaysRegex}else{if(!l(this,"_weekdaysRegex"))this._weekdaysRegex=Vt;return this._weekdaysStrictRegex&&e?this._weekdaysStrictRegex:this._weekdaysRegex}}function tn(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))rn.call(this);if(e)return this._weekdaysShortStrictRegex;else return this._weekdaysShortRegex}else{if(!l(this,"_weekdaysShortRegex"))this._weekdaysShortRegex=Bt;return this._weekdaysShortStrictRegex&&e?this._weekdaysShortStrictRegex:this._weekdaysShortRegex}}function nn(e){if(this._weekdaysParseExact){if(!l(this,"_weekdaysRegex"))rn.call(this);if(e)return this._weekdaysMinStrictRegex;else return this._weekdaysMinRegex}else{if(!l(this,"_weekdaysMinRegex"))this._weekdaysMinRegex=Ut;return this._weekdaysMinStrictRegex&&e?this._weekdaysMinStrictRegex:this._weekdaysMinRegex}}function rn(){function e(e,t){return t.length-e.length}var t=[],n=[],r=[],a=[],o,i,s,l,u;for(o=0;o<7;o++){i=c([2e3,1]).day(o);s=y(this.weekdaysMin(i,""));l=y(this.weekdaysShort(i,""));u=y(this.weekdays(i,""));t.push(s);n.push(l);r.push(u);a.push(s);a.push(l);a.push(u)}t.sort(e);n.sort(e);r.sort(e);a.sort(e);this._weekdaysRegex=new RegExp("^("+a.join("|")+")","i");this._weekdaysShortRegex=this._weekdaysRegex;this._weekdaysMinRegex=this._weekdaysRegex;this._weekdaysStrictRegex=new RegExp("^("+r.join("|")+")","i");this._weekdaysShortStrictRegex=new RegExp("^("+n.join("|")+")","i");this._weekdaysMinStrictRegex=new RegExp("^("+t.join("|")+")","i")}function an(){return this.hours()%12||12}function on(){return this.hours()||24}function sn(e,t){r(e,0,0,function(){return this.localeData().meridiem(this.hours(),this.minutes(),t)})}function ln(e,t){return t._meridiemParse}function un(e){return(e+"").toLowerCase().charAt(0)==="p"}r("H",["HH",2],0,"hour"),r("h",["hh",2],0,an),r("k",["kk",2],0,on),r("hmm",0,0,function(){return""+an.apply(this)+o(this.minutes(),2)}),r("hmmss",0,0,function(){return""+an.apply(this)+o(this.minutes(),2)+o(this.seconds(),2)}),r("Hmm",0,0,function(){return""+this.hours()+o(this.minutes(),2)}),r("Hmmss",0,0,function(){return""+this.hours()+o(this.minutes(),2)+o(this.seconds(),2)}),sn("a",true),sn("A",false),a("a",ln),a("A",ln),a("H",n,Be),a("h",n,Ve),a("k",n,Ve),a("HH",n,t),a("hh",n,t),a("kk",n,t),a("hmm",Ye),a("hmmss",Pe),a("Hmm",Ye),a("Hmmss",Pe),v(["H","HH"],k),v(["k","kk"],function(e,t,n){var r=_(e);t[k]=r===24?0:r}),v(["a","A"],function(e,t,n){n._isPm=n._locale.isPM(e);n._meridiem=e}),v(["h","hh"],function(e,t,n){t[k]=_(e);p(n).bigHour=true}),v("hmm",function(e,t,n){var r=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r));p(n).bigHour=true}),v("hmmss",function(e,t,n){var r=e.length-4,a=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r,2));t[L]=_(e.substr(a));p(n).bigHour=true}),v("Hmm",function(e,t,n){var r=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r))}),v("Hmmss",function(e,t,n){var r=e.length-4,a=e.length-2;t[k]=_(e.substr(0,r));t[x]=_(e.substr(r,2));t[L]=_(e.substr(a))});var cn,dn=at("Hours",true);function pn(e,t,n){if(e>11)return n?"pm":"PM";else return n?"am":"AM"}var fn={calendar:ae,longDateFormat:he,invalidDate:ye,ordinal:_e,dayOfMonthOrdinalParse:ve,relativeTime:we,months:dt,monthsShort:pt,week:Yt,weekdays:Ft,weekdaysMin:Wt,weekdaysShort:zt,meridiemParse:/[ap]\.?m?\.?/i},C={},hn={},mn;function yn(e,t){var n,r=Math.min(e.length,t.length);for(n=0;n0){a=bn(o.slice(0,n).join("-"));if(a)return a;if(r&&r.length>=n&&yn(o,r)>=n-1)break;n--}t++}return mn}function vn(e){return!!(e&&e.match("^[^/\\\\]*$"))}function bn(t){var e=null,n;if(C[t]===undefined&&typeof ci!=="undefined"&&ci&&ci.exports&&vn(t))try{e=mn._abbr;n=di;pi(352)("./"+t);wn(e)}catch(e){C[t]=null}return C[t]}function wn(e,t){var n;if(e){if(s(t))n=E(e);else n=Mn(e,t);if(n)mn=n;else if(typeof console!=="undefined"&&console.warn)console.warn("Locale "+e+" not found. Did you forget to load it?")}return mn._abbr}function Mn(e,t){if(t!==null){var n,r=fn;t.abbr=e;if(C[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.");r=C[e]._config}else if(t.parentLocale!=null)if(C[t.parentLocale]!=null)r=C[t.parentLocale]._config;else{n=bn(t.parentLocale);if(n!=null)r=n._config;else{if(!hn[t.parentLocale])hn[t.parentLocale]=[];hn[t.parentLocale].push({name:e,config:t});return null}}C[e]=new re(ne(r,t));if(hn[e])hn[e].forEach(function(e){Mn(e.name,e.config)});wn(e);return C[e]}else{delete C[e];return null}}function kn(e,t){if(t!=null){var n,r,a=fn;if(C[e]!=null&&C[e].parentLocale!=null)C[e].set(ne(C[e]._config,t));else{r=bn(e);if(r!=null)a=r._config;t=ne(a,t);if(r==null)t.abbr=e;n=new re(t);n.parentLocale=C[e];C[e]=n}wn(e)}else if(C[e]!=null)if(C[e].parentLocale!=null){C[e]=C[e].parentLocale;if(e===wn())wn(e)}else if(C[e]!=null)delete C[e];return C[e]}function E(e){var t;if(e&&e._locale&&e._locale._abbr)e=e._locale._abbr;if(!e)return mn;if(!i(e)){t=bn(e);if(t)return t;e=[e]}return _n(e)}function xn(){return Z(C)}function Ln(e){var t,n=e._a;if(n&&p(e).overflow===-2){t=n[w]<0||n[w]>11?w:n[M]<1||n[M]>ct(n[b],n[w])?M:n[k]<0||n[k]>24||n[k]===24&&(n[x]!==0||n[L]!==0||n[Xe]!==0)?k:n[x]<0||n[x]>59?x:n[L]<0||n[L]>59?L:n[Xe]<0||n[Xe]>999?Xe:-1;if(p(e)._overflowDayOfYear&&(tM))t=M;if(p(e)._overflowWeeks&&t===-1)t=Ze;if(p(e)._overflowWeekday&&t===-1)t=et;p(e).overflow=t}return e}var Sn=/^\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)?)?$/,Tn=/^\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)?/,En=[["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]],Dn=[["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,Yn=/^(?:(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}))$/,Pn={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 Nn(e){var t,n,r=e._i,a=Sn.exec(r)||Tn.exec(r),o,i,s,l,u=En.length,c=Dn.length;if(a){p(e).iso=true;for(t=0,n=u;ttt(i)||e._dayOfYear===0)p(e)._overflowDayOfYear=true;n=Tt(i,0,e._dayOfYear);e._a[w]=n.getUTCMonth();e._a[M]=n.getUTCDate()}for(t=0;t<3&&e._a[t]==null;++t)e._a[t]=r[t]=a[t];for(;t<7;t++)e._a[t]=r[t]=e._a[t]==null?t===2?1:0:e._a[t];if(e._a[k]===24&&e._a[x]===0&&e._a[L]===0&&e._a[Xe]===0){e._nextDay=true;e._a[k]=0}e._d=(e._useUTC?Tt:St).apply(null,r);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[k]=24;if(e._w&&typeof e._w.d!=="undefined"&&e._w.d!==o)p(e).weekdayMismatch=true}function Un(e){var t,n,r,a,o,i,s,l,u;t=e._w;if(t.GG!=null||t.W!=null||t.E!=null){o=1;i=4;n=Wn(t.GG,e._a[b],Dt(D(),1,4).year);r=Wn(t.W,1);a=Wn(t.E,1);if(a<1||a>7)l=true}else{o=e._locale._week.dow;i=e._locale._week.doy;u=Dt(D(),o,i);n=Wn(t.gg,e._a[b],u.year);r=Wn(t.w,u.week);if(t.d!=null){a=t.d;if(a<0||a>6)l=true}else if(t.e!=null){a=t.e+o;if(t.e<0||t.e>6)l=true}else a=o}if(r<1||r>T(n,o,i))p(e)._overflowWeeks=true;else if(l!=null)p(e)._overflowWeekday=true;else{s=Et(n,r,a,o,i);e._a[b]=s.year;e._dayOfYear=s.dayOfYear}}function Kn(e){if(e._f===d.ISO_8601){Nn(e);return}if(e._f===d.RFC_2822){Fn(e);return}e._a=[];p(e).empty=true;var t=""+e._i,n,r,a,o,i,s=t.length,l=0,u,c;a=fe(e._f,e._locale).match(ie)||[];c=a.length;for(n=0;n0)p(e).unusedInput.push(i);t=t.slice(t.indexOf(r)+r.length);l+=r.length}if(ue[o]){if(r)p(e).empty=false;else p(e).unusedTokens.push(o);Je(o,r,e)}else if(e._strict&&!r)p(e).unusedTokens.push(o)}p(e).charsLeftOver=s-l;if(t.length>0)p(e).unusedInput.push(t);if(e._a[k]<=12&&p(e).bigHour===true&&e._a[k]>0)p(e).bigHour=undefined;p(e).parsedDateParts=e._a.slice(0);p(e).meridiem=e._meridiem;e._a[k]=$n(e._locale,e._a[k],e._meridiem);u=p(e).era;if(u!==null)e._a[b]=e._locale.erasConvertYear(u,e._a[b]);Bn(e);Ln(e)}function $n(e,t,n){var r;if(n==null)return t;if(e.meridiemHour!=null)return e.meridiemHour(t,n);else if(e.isPM!=null){r=e.isPM(n);if(r&&t<12)t+=12;if(!r&&t===12)t=0;return t}else return t}function Gn(e){var t,n,r,a,o,i,s=false,l=e._f.length;if(l===0){p(e).invalidFormat=true;e._d=new Date(NaN);return}for(a=0;athis?this:e;else return K()});function nr(e,t){var n,r;if(t.length===1&&i(t[0]))t=t[0];if(!t.length)return D();n=t[0];for(r=1;rthis.clone().month(0).utcOffset()||this.utcOffset()>this.clone().month(5).utcOffset()}function Sr(){if(!s(this._isDSTShifted))return this._isDSTShifted;var e={},t;q(e,this);e=Qn(e);if(e._a){t=e._isUTC?c(e._a):D(e._a);this._isDSTShifted=this.isValid()&&fr(e._a,t.toArray())>0}else this._isDSTShifted=false;return this._isDSTShifted}function Tr(){return this.isValid()?!this._isUTC:false}function Cr(){return this.isValid()?this._isUTC:false}function Er(){return this.isValid()?this._isUTC&&this._offset===0:false}d.updateOffset=function(){};var Dr=/^(-|\+)?(?:(\d*)[. ])?(\d+):(\d+)(?::(\d+)(\.\d*)?)?$/,Or=/^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/;function O(e,t){var n=e,r=null,a,o,i;if(dr(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(r=Dr.exec(e)){a=r[1]==="-"?-1:1;n={y:0,d:_(r[M])*a,h:_(r[k])*a,m:_(r[x])*a,s:_(r[L])*a,ms:_(pr(r[Xe]*1e3))*a}}else if(r=Or.exec(e)){a=r[1]==="-"?-1:1;n={y:Yr(r[2],a),M:Yr(r[3],a),w:Yr(r[4],a),d:Yr(r[5],a),h:Yr(r[6],a),m:Yr(r[7],a),s:Yr(r[8],a)}}else if(n==null)n={};else if(typeof n==="object"&&("from"in n||"to"in n)){i=Nr(D(n.from),D(n.to));n={};n.ms=i.milliseconds;n.M=i.months}o=new cr(n);if(dr(e)&&l(e,"_locale"))o._locale=e._locale;if(dr(e)&&l(e,"_isValid"))o._isValid=e._isValid;return o}function Yr(e,t){var n=e&&parseFloat(e.replace(",","."));return(isNaN(n)?0:n)*t}function Pr(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 Nr(e,t){var n;if(!(e.isValid()&&t.isValid()))return{milliseconds:0,months:0};t=gr(t,e);if(e.isBefore(t))n=Pr(e,t);else{n=Pr(t,e);n.milliseconds=-n.milliseconds;n.months=-n.months}return n}function jr(a,o){return function(e,t){var n,r;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.");r=e;e=t;t=r}n=O(e,t);Rr(this,n,a);return this}}function Rr(e,t,n,r){var a=t._milliseconds,o=pr(t._days),i=pr(t._months);if(!e.isValid())return;r=r==null?true:r;if(i)bt(e,ot(e,"Month")+i*n);if(o)it(e,"Date",ot(e,"Date")+o*n);if(a)e._d.setTime(e._d.valueOf()+a*n);if(r)d.updateOffset(e,o||i)}O.fn=cr.prototype,O.invalid=ur;var Ar=jr(1,"add"),Ir=jr(-1,"subtract");function Hr(e){return typeof e==="string"||e instanceof String}function Fr(e){return f(e)||z(e)||Hr(e)||u(e)||Wr(e)||zr(e)||e===null||e===undefined}function zr(e){var t=H(e)&&!F(e),n=false,r=["years","year","y","months","month","M","days","day","d","dates","date","D","hours","hour","h","minutes","minute","m","seconds","second","s","milliseconds","millisecond","ms"],a,o,i=r.length;for(a=0;an.valueOf();else return n.valueOf()9999)return pe(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",pe(n,"Z"));return pe(n,t?"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]":"YYYY-MM-DD[T]HH:mm:ss.SSSZ")}function ra(){if(!this.isValid())return"moment.invalid(/* "+this._i+" */)";var e="moment",t="",n,r,a,o;if(!this.isLocal()){e=this.utcOffset()===0?"moment.utc":"moment.parseZone";t="Z"}n="["+e+'("]';r=0<=this.year()&&this.year()<=9999?"YYYY":"YYYYYY";a="-MM-DD[T]HH:mm:ss.SSS";o=t+'[")]';return this.format(n+r+a+o)}function aa(e){if(!e)e=this.isUtc()?d.defaultFormatUtc:d.defaultFormat;var t=pe(this,e);return this.localeData().postformat(t)}function oa(e,t){if(this.isValid()&&(f(e)&&e.isValid()||D(e).isValid()))return O({to:this,from:e}).locale(this.locale()).humanize(!t);else return this.localeData().invalidDate()}function ia(e){return this.from(D(),e)}function sa(e,t){if(this.isValid()&&(f(e)&&e.isValid()||D(e).isValid()))return O({from:this,to:e}).locale(this.locale()).humanize(!t);else return this.localeData().invalidDate()}function la(e){return this.to(D(),e)}function ua(e){var t;if(e===undefined)return this._locale._abbr;else{t=E(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 ca=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 da(){return this._locale}var pa=1e3,fa=60*pa,ha=60*fa,ma=(365*400+97)*24*ha;function ya(e,t){return(e%t+t)%t}function ga(e,t,n){if(e<100&&e>=0)return new Date(e+400,t,n)-ma;else return new Date(e,t,n).valueOf()}function _a(e,t,n){if(e<100&&e>=0)return Date.UTC(e+400,t,n)-ma;else return Date.UTC(e,t,n)}function va(e){var t,n;e=m(e);if(e===undefined||e==="millisecond"||!this.isValid())return this;n=this._isUTC?_a:ga;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-=ya(t+(this._isUTC?0:this.utcOffset()*fa),ha);break;case"minute":t=this._d.valueOf();t-=ya(t,fa);break;case"second":t=this._d.valueOf();t-=ya(t,pa);break}this._d.setTime(t);d.updateOffset(this,true);return this}function ba(e){var t,n;e=m(e);if(e===undefined||e==="millisecond"||!this.isValid())return this;n=this._isUTC?_a:ga;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+=ha-ya(t+(this._isUTC?0:this.utcOffset()*fa),ha)-1;break;case"minute":t=this._d.valueOf();t+=fa-ya(t,fa)-1;break;case"second":t=this._d.valueOf();t+=pa-ya(t,pa)-1;break}this._d.setTime(t);d.updateOffset(this,true);return this}function wa(){return this._d.valueOf()-(this._offset||0)*6e4}function Ma(){return Math.floor(this.valueOf()/1e3)}function ka(){return new Date(this.valueOf())}function xa(){var e=this;return[e.year(),e.month(),e.date(),e.hour(),e.minute(),e.second(),e.millisecond()]}function La(){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 Sa(){return this.isValid()?this.toISOString():null}function Ta(){return U(this)}function Ca(){return V({},p(this))}function Ea(){return p(this).overflow}function Da(){return{input:this._i,format:this._f,locale:this._locale,isUTC:this._isUTC,strict:this._strict}}function Oa(e,t){var n,r,a,o=this._eras||E("en")._eras;for(n=0,r=o.length;n=0)return o[r]}}function Pa(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 Na(){var e,t,n,r=this.localeData().eras();for(e=0,t=r.length;eo)t=o;return eo.call(this,e,t,n,r,a)}}function eo(e,t,n,r,a){var o=Et(e,t,n,r,a),i=Tt(o.year,0,o.dayOfYear);this.year(i.getUTCFullYear());this.month(i.getUTCMonth());this.date(i.getUTCDate());return this}function to(e){return e==null?Math.ceil((this.month()+1)/3):this.month((e-1)*3+this.month()%3)}r("N",0,0,"eraAbbr"),r("NN",0,0,"eraAbbr"),r("NNN",0,0,"eraAbbr"),r("NNNN",0,0,"eraName"),r("NNNNN",0,0,"eraNarrow"),r("y",["y",1],"yo","eraYear"),r("y",["yy",2],0,"eraYear"),r("y",["yyy",3],0,"eraYear"),r("y",["yyyy",4],0,"eraYear"),a("N",za),a("NN",za),a("NNN",za),a("NNNN",Wa),a("NNNNN",Va),v(["N","NN","NNN","NNNN","NNNNN"],function(e,t,n,r){var a=n._locale.erasParse(e,r,n._strict);if(a)p(n).era=a;else p(n).invalidEra=e}),a("y",Ae),a("yy",Ae),a("yyy",Ae),a("yyyy",Ae),a("yo",Ba),v(["y","yy","yyy","yyyy"],b),v(["yo"],function(e,t,n,r){var a;if(n._locale._eraYearOrdinalRegex)a=e.match(n._locale._eraYearOrdinalRegex);if(n._locale.eraYearOrdinalParse)t[b]=n._locale.eraYearOrdinalParse(e,a);else t[b]=parseInt(e,10)}),r(0,["gg",2],0,function(){return this.weekYear()%100}),r(0,["GG",2],0,function(){return this.isoWeekYear()%100}),Ka("gggg","weekYear"),Ka("ggggg","weekYear"),Ka("GGGG","isoWeekYear"),Ka("GGGGG","isoWeekYear"),a("G",Ie),a("g",Ie),a("GG",n,t),a("gg",n,t),a("GGGG",je,De),a("gggg",je,De),a("GGGGG",Re,Oe),a("ggggg",Re,Oe),qe(["gggg","ggggg","GGGG","GGGGG"],function(e,t,n,r){t[r.substr(0,2)]=_(e)}),qe(["gg","GG"],function(e,t,n,r){t[r]=d.parseTwoDigitYear(e)}),r("Q",0,"Qo","quarter"),a("Q",Ce),v("Q",function(e,t){t[w]=(_(e)-1)*3}),r("D",["DD",2],"Do","date"),a("D",n,Ve),a("DD",n,t),a("Do",function(e,t){return e?t._dayOfMonthOrdinalParse||t._ordinalParse:t._dayOfMonthOrdinalParseLenient}),v(["D","DD"],M),v("Do",function(e,t){t[M]=_(e.match(n)[0])});var no=at("Date",true);function ro(e){var t=Math.round((this.clone().startOf("day")-this.clone().startOf("year"))/864e5)+1;return e==null?t:this.add(e-t,"d")}r("DDD",["DDDD",3],"DDDo","dayOfYear"),a("DDD",Ne),a("DDDD",Ee),v(["DDD","DDDD"],function(e,t,n){n._dayOfYear=_(e)}),r("m",["mm",2],0,"minute"),a("m",n,Be),a("mm",n,t),v(["m","mm"],x);var ao=at("Minutes",false),oo=(r("s",["ss",2],0,"second"),a("s",n,Be),a("ss",n,t),v(["s","ss"],L),at("Seconds",false)),io,so;for(r("S",0,0,function(){return~~(this.millisecond()/100)}),r(0,["SS",2],0,function(){return~~(this.millisecond()/10)}),r(0,["SSS",3],0,"millisecond"),r(0,["SSSS",4],0,function(){return this.millisecond()*10}),r(0,["SSSSS",5],0,function(){return this.millisecond()*100}),r(0,["SSSSSS",6],0,function(){return this.millisecond()*1e3}),r(0,["SSSSSSS",7],0,function(){return this.millisecond()*1e4}),r(0,["SSSSSSSS",8],0,function(){return this.millisecond()*1e5}),r(0,["SSSSSSSSS",9],0,function(){return this.millisecond()*1e6}),a("S",Ne,Ce),a("SS",Ne,t),a("SSS",Ne,Ee),io="SSSS";io.length<=9;io+="S")a(io,Ae);function lo(e,t){t[Xe]=_(("0."+e)*1e3)}for(io="S";io.length<=9;io+="S")v(io,lo);function uo(){return this._isUTC?"UTC":""}function co(){return this._isUTC?"Coordinated Universal Time":""}so=at("Milliseconds",false),r("z",0,0,"zoneAbbr"),r("zz",0,0,"zoneName");var Y=J.prototype;if(Y.add=Ar,Y.calendar=Ur,Y.clone=Kr,Y.diff=Zr,Y.endOf=ba,Y.format=aa,Y.from=oa,Y.fromNow=ia,Y.to=sa,Y.toNow=la,Y.get=st,Y.invalidAt=Ea,Y.isAfter=$r,Y.isBefore=Gr,Y.isBetween=qr,Y.isSame=Jr,Y.isSameOrAfter=Qr,Y.isSameOrBefore=Xr,Y.isValid=Ta,Y.lang=ca,Y.locale=ua,Y.localeData=da,Y.max=tr,Y.min=er,Y.parsingFlags=Ca,Y.set=lt,Y.startOf=va,Y.subtract=Ir,Y.toArray=xa,Y.toObject=La,Y.toDate=ka,Y.toISOString=na,Y.inspect=ra,typeof Symbol!=="undefined"&&Symbol.for!=null)Y[Symbol.for("nodejs.util.inspect.custom")]=function(){return"Moment<"+this.format()+">"};function po(e){return D(e*1e3)}function fo(){return D.apply(null,arguments).parseZone()}function ho(e){return e}Y.toJSON=Sa,Y.toString=ta,Y.unix=Ma,Y.valueOf=wa,Y.creationData=Da,Y.eraName=Na,Y.eraNarrow=ja,Y.eraAbbr=Ra,Y.eraYear=Aa,Y.year=nt,Y.isLeapYear=rt,Y.weekYear=$a,Y.isoWeekYear=Ga,Y.quarter=Y.quarters=to,Y.month=wt,Y.daysInMonth=Mt,Y.week=Y.weeks=jt,Y.isoWeek=Y.isoWeeks=Rt,Y.weeksInYear=Qa,Y.weeksInWeekYear=Xa,Y.isoWeeksInYear=qa,Y.isoWeeksInISOWeekYear=Ja,Y.date=no,Y.day=Y.days=Qt,Y.weekday=Xt,Y.isoWeekday=Zt,Y.dayOfYear=ro,Y.hour=Y.hours=dn,Y.minute=Y.minutes=ao,Y.second=Y.seconds=oo,Y.millisecond=Y.milliseconds=so,Y.utcOffset=vr,Y.utc=wr,Y.local=Mr,Y.parseZone=kr,Y.hasAlignedHourOffset=xr,Y.isDST=Lr,Y.isLocal=Tr,Y.isUtcOffset=Cr,Y.isUtc=Er,Y.isUTC=Er,Y.zoneAbbr=uo,Y.zoneName=co,Y.dates=e("dates accessor is deprecated. Use date instead.",no),Y.months=e("months accessor is deprecated. Use month instead",wt),Y.years=e("years accessor is deprecated. Use year instead",nt),Y.zone=e("moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/",br),Y.isDSTShifted=e("isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information",Sr);var P=re.prototype;function mo(e,t,n,r){var a=E(),o=c().set(r,t);return a[n](o,e)}function yo(e,t,n){if(u(e)){t=e;e=undefined}e=e||"";if(t!=null)return mo(e,t,n,"month");var r,a=[];for(r=0;r<12;r++)a[r]=mo(e,r,n,"month");return a}function go(e,t,n,r){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 a=E(),o=e?a._week.dow:0,i,s=[];if(n!=null)return mo(t,(n+o)%7,r,"day");for(i=0;i<7;i++)s[i]=mo(t,(i+o)%7,r,"day");return s}function _o(e,t){return yo(e,t,"months")}function vo(e,t){return yo(e,t,"monthsShort")}function bo(e,t,n){return go(e,t,n,"weekdays")}function wo(e,t,n){return go(e,t,n,"weekdaysShort")}function Mo(e,t,n){return go(e,t,n,"weekdaysMin")}P.calendar=oe,P.longDateFormat=me,P.invalidDate=ge,P.ordinal=be,P.preparse=ho,P.postformat=ho,P.relativeTime=Me,P.pastFuture=ke,P.set=te,P.eras=Oa,P.erasParse=Ya,P.erasConvertYear=Pa,P.erasAbbrRegex=Ha,P.erasNameRegex=Ia,P.erasNarrowRegex=Fa,P.months=yt,P.monthsShort=gt,P.monthsParse=vt,P.monthsRegex=xt,P.monthsShortRegex=kt,P.week=Ot,P.firstDayOfYear=Nt,P.firstDayOfWeek=Pt,P.weekdays=Kt,P.weekdaysMin=Gt,P.weekdaysShort=$t,P.weekdaysParse=Jt,P.weekdaysRegex=en,P.weekdaysShortRegex=tn,P.weekdaysMinRegex=nn,P.isPM=un,P.meridiem=pn,wn("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=_(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.",wn),d.langData=e("moment.langData is deprecated. Use moment.localeData instead.",E);var N=Math.abs;function ko(){var e=this._data;this._milliseconds=N(this._milliseconds);this._days=N(this._days);this._months=N(this._months);e.milliseconds=N(e.milliseconds);e.seconds=N(e.seconds);e.minutes=N(e.minutes);e.hours=N(e.hours);e.months=N(e.months);e.years=N(e.years);return this}function xo(e,t,n,r){var a=O(t,n);e._milliseconds+=r*a._milliseconds;e._days+=r*a._days;e._months+=r*a._months;return e._bubble()}function Lo(e,t){return xo(this,e,t,1)}function So(e,t){return xo(this,e,t,-1)}function To(e){if(e<0)return Math.floor(e);else return Math.ceil(e)}function Co(){var e=this._milliseconds,t=this._days,n=this._months,r=this._data,a,o,i,s,l;if(!(e>=0&&t>=0&&n>=0||e<=0&&t<=0&&n<=0)){e+=To(Do(n)+t)*864e5;t=0;n=0}r.milliseconds=e%1e3;a=g(e/1e3);r.seconds=a%60;o=g(a/60);r.minutes=o%60;i=g(o/60);r.hours=i%24;t+=g(i/24);l=g(Eo(t));n+=l;t-=To(Do(l));s=g(n/12);n%=12;r.days=t;r.months=n;r.years=s;return this}function Eo(e){return e*4800/146097}function Do(e){return e*146097/4800}function Oo(e){if(!this.isValid())return NaN;var t,n,r=this._milliseconds;e=m(e);if(e==="month"||e==="quarter"||e==="year"){t=this._days+r/864e5;n=this._months+Eo(t);switch(e){case"month":return n;case"quarter":return n/3;case"year":return n/12}}else{t=this._days+Math.round(Do(this._months));switch(e){case"week":return t/7+r/6048e5;case"day":return t+r/864e5;case"hour":return t*24+r/36e5;case"minute":return t*1440+r/6e4;case"second":return t*86400+r/1e3;case"millisecond":return Math.floor(t*864e5)+r;default:throw new Error("Unknown unit "+e)}}}function Yo(e){return function(){return this.as(e)}}var Po=Yo("ms"),No=Yo("s"),jo=Yo("m"),Ro=Yo("h"),Ao=Yo("d"),Io=Yo("w"),Ho=Yo("M"),Fo=Yo("Q"),zo=Yo("y"),Wo=Po;function Vo(){return O(this)}function Bo(e){e=m(e);return this.isValid()?this[e+"s"]():NaN}function Uo(e){return function(){return this.isValid()?this._data[e]:NaN}}var Ko=Uo("milliseconds"),$o=Uo("seconds"),Go=Uo("minutes"),qo=Uo("hours"),Jo=Uo("days"),Qo=Uo("months"),Xo=Uo("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,r,a){return a.relativeTime(t||1,!!n,e,r)}function ri(e,t,n,r){var a=O(e).abs(),o=ei(a.as("s")),i=ei(a.as("m")),s=ei(a.as("h")),l=ei(a.as("d")),u=ei(a.as("M")),c=ei(a.as("w")),d=ei(a.as("y")),p=o<=n.ss&&["s",o]||o0;p[4]=r;return ni.apply(null,p)}function ai(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,r=ti,a,o;if(typeof e==="object"){t=e;e=false}if(typeof e==="boolean")n=e;if(typeof t==="object"){r=Object.assign({},ti,t);if(t.s!=null&&t.ss==null)r.ss=t.s-1}a=this.localeData();o=ri(this,!n,r,a);if(n)o=a.pastFuture(+this,o);return a.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),r,a,o,i,s=this.asSeconds(),l,u,c,d;if(!s)return"P0D";r=g(e/60);a=g(r/60);e%=60;r%=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":"")+(a||r||e?"T":"")+(a?d+a+"H":"")+(r?d+r+"M":"")+(e?d+i+"S":"")}var j=cr.prototype;return j.isValid=lr,j.abs=ko,j.add=Lo,j.subtract=So,j.as=Oo,j.asMilliseconds=Po,j.asSeconds=No,j.asMinutes=jo,j.asHours=Ro,j.asDays=Ao,j.asWeeks=Io,j.asMonths=Ho,j.asQuarters=Fo,j.asYears=zo,j.valueOf=Wo,j._bubble=Co,j.clone=Vo,j.get=Bo,j.milliseconds=Ko,j.seconds=$o,j.minutes=Go,j.hours=qo,j.days=Jo,j.weeks=Zo,j.months=Qo,j.years=Xo,j.humanize=ii,j.toISOString=ui,j.toString=ui,j.toJSON=ui,j.locale=ua,j.localeData=da,j.toIsoString=e("toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)",ui),j.lang=ca,r("X",0,0,"unix"),r("x",0,0,"valueOf"),a("x",Ie),a("X",ze),v("X",function(e,t,n){n._d=new Date(parseFloat(e)*1e3)}),v("x",function(e,t,n){n._d=new Date(_(e))}), //! moment.js -d.version="2.30.1",H(D),d.fn=O,d.min=rr,d.max=ar,d.now=or,d.utc=c,d.unix=po,d.months=_o,d.isDate=z,d.locale=wn,d.invalid=K,d.duration=Y,d.isMoment=f,d.weekdays=bo,d.parseZone=fo,d.localeData=E,d.isDuration=dr,d.monthsShort=vo,d.weekdaysMin=Mo,d.defineLocale=Mn,d.updateLocale=kn,d.locales=xn,d.weekdaysShort=wo,d.normalizeUnits=m,d.relativeTimeRounding=ai,d.relativeTimeThreshold=oi,d.calendarFormat=Br,d.prototype=O,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,pi(92)(e))},function(e,P,N){"use strict";!function(e){var t,a=N(53);const n=Object.prototype["toString"],l=Object["getPrototypeOf"],r=(t=Object.create(null),e=>{e=n.call(e);return t[e]||(t[e]=e.slice(8,-1).toLowerCase())});var o=t=>(t=t.toLowerCase(),e=>r(e)===t),i=t=>e=>typeof e===t;const u=Array["isArray"],s=i("undefined");const c=o("ArrayBuffer");var d=i("string");const p=i("function"),f=i("number"),h=e=>null!==e&&"object"==typeof e;const m=e=>{var t;return"object"===r(e)&&!(null!==(t=l(e))&&t!==Object.prototype&&null!==Object.getPrototypeOf(t)||Symbol.toStringTag in e||Symbol.iterator in e)};var i=o("Date"),y=o("File"),g=o("Blob"),_=o("FileList"),v=o("URLSearchParams");function b(t,n,{allOwnKeys:r=!1}={}){if(null!=t){let e;var a;if("object"!=typeof t&&(t=[t]),u(t))for(e=0,a=t.length;e!s(e)&&e!==M;x="undefined"!=typeof Uint8Array&&l(Uint8Array);var x,L,e=e=>x&&e instanceof x,S=o("HTMLFormElement"),T=(L=[Object.prototype["hasOwnProperty"]][0],(e,t)=>L.call(e,t)),C=o("RegExp");const E=(r,a)=>{var e=Object.getOwnPropertyDescriptors(r);const o={};b(e,(e,t)=>{var n;!1!==(n=a(e,t,r))&&(o[t]=n||e)}),Object.defineProperties(r,o)};var D="abcdefghijklmnopqrstuvwxyz",Y="0123456789";const O={DIGIT:Y,ALPHA:D,ALPHA_DIGIT:D+D.toUpperCase()+Y};D=o("AsyncFunction");P.a={isArray:u,isArrayBuffer:c,isBuffer:function(e){return null!==e&&!s(e)&&null!==e.constructor&&!s(e.constructor)&&p(e.constructor.isBuffer)&&e.constructor.isBuffer(e)},isFormData:e=>{var t;return e&&("function"==typeof FormData&&e instanceof FormData||p(e.append)&&("formdata"===(t=r(e))||"object"===t&&p(e.toString)&&"[object FormData]"===e.toString()))},isArrayBufferView:function(e){let t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&c(e.buffer)},isString:d,isNumber:f,isBoolean:e=>!0===e||!1===e,isObject:h,isPlainObject:m,isUndefined:s,isDate:i,isFile:y,isBlob:g,isRegExp:C,isFunction:p,isStream:e=>h(e)&&p(e.pipe),isURLSearchParams:v,isTypedArray:e,isFileList:_,forEach:b,merge:function n(){const r=(k(this)&&this||{}).caseless,a={};var o=(e,t)=>{t=r&&w(a,t)||t,m(a[t])&&m(e)?a[t]=n(a[t],e):m(e)?a[t]=n({},e):u(e)?a[t]=e.slice():a[t]=e};for(let e=0,t=arguments.length;e(b(e,(e,t)=>{r&&p(e)?n[t]=Object(a.a)(e,r):n[t]=e},{allOwnKeys:t}),n),trim:e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,""),stripBOM:e=>e=65279===e.charCodeAt(0)?e.slice(1):e,inherits:(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},toFlatObject:(e,t,n,r)=>{var a,o;let i;var s={};if(t=t||{},null!=e)do{for(a=Object.getOwnPropertyNames(e),i=a.length;0{e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;e=e.indexOf(t,n);return-1!==e&&e===n},toArray:e=>{if(!e)return null;if(u(e))return e;let t=e.length;if(!f(t))return null;for(var n=new Array(t);0{for(var n=(e&&e[Symbol.iterator]).call(e);(r=n.next())&&!r.done;){var r=r.value;t.call(e,r[0],r[1])}},matchAll:(e,t)=>{for(var n,r=[];null!==(n=e.exec(t));)r.push(n);return r},isHTMLForm:S,hasOwnProperty:T,hasOwnProp:T,reduceDescriptors:E,freezeMethods:r=>{E(r,(e,t)=>{if(p(r)&&-1!==["arguments","caller","callee"].indexOf(t))return!1;var n=r[t];p(n)&&(e.enumerable=!1,"writable"in e?e.writable=!1:e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+t+"'")}))})},toObjectSet:(e,t)=>{const n={};var r=e=>{e.forEach(e=>{n[e]=!0})};return u(e)?r(e):r(String(e).split(t)),n},toCamelCase:e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(e,t,n){return t.toUpperCase()+n}),noop:()=>{},toFiniteNumber:(e,t)=>(e=+e,Number.isFinite(e)?e:t),findKey:w,global:M,isContextDefined:k,ALPHABET:O,generateString:(e=16,t=O.ALPHA_DIGIT)=>{let n="";for(var r=t["length"];e--;)n+=t[Math.random()*r|0];return n},isSpecCompliantForm:function(e){return!!(e&&p(e.append)&&"FormData"===e[Symbol.toStringTag]&&e[Symbol.iterator])},toJSONObject:e=>{const t=new Array(10),a=(e,n)=>{if(h(e)){if(0<=t.indexOf(e))return;if(!("toJSON"in e)){t[n]=e;const r=u(e)?[]:{};return b(e,(e,t)=>{e=a(e,n+1);s(e)||(r[t]=e)}),t[n]=void 0,r}}return e};return a(e,0)},isAsyncFn:D,isThenable:e=>e&&(h(e)||p(e))&&p(e.then)&&p(e.catch)}}.call(this,N(41))},function(e,t,n){"use strict";!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(300)},function(e,t,n){"use strict";function l(){var e=this.constructor.getDerivedStateFromProps(this.props,this.state);null!=e&&this.setState(e)}function u(t){this.setState(function(e){return null!=(e=this.constructor.getDerivedStateFromProps(t,e))?e:null}.bind(this))}function c(e,t){try{var n=this.props,r=this.state;this.props=e,this.state=t,this.__reactInternalSnapshotFlag=!0,this.__reactInternalSnapshot=this.getSnapshotBeforeUpdate(n,r)}finally{this.props=n,this.state=r}}function r(e){var t=e.prototype;if(!t||!t.isReactComponent)throw new Error("Can only polyfill class components");if("function"==typeof e.getDerivedStateFromProps||"function"==typeof t.getSnapshotBeforeUpdate){var n,r,a=null,o=null,i=null;if("function"==typeof t.componentWillMount?a="componentWillMount":"function"==typeof t.UNSAFE_componentWillMount&&(a="UNSAFE_componentWillMount"),"function"==typeof t.componentWillReceiveProps?o="componentWillReceiveProps":"function"==typeof t.UNSAFE_componentWillReceiveProps&&(o="UNSAFE_componentWillReceiveProps"),"function"==typeof t.componentWillUpdate?i="componentWillUpdate":"function"==typeof t.UNSAFE_componentWillUpdate&&(i="UNSAFE_componentWillUpdate"),null!==a||null!==o||null!==i)throw n=e.displayName||e.name,r="function"==typeof e.getDerivedStateFromProps?"getDerivedStateFromProps()":"getSnapshotBeforeUpdate()",Error("Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n"+n+" uses "+r+" but also contains the following legacy lifecycles:"+(null!==a?"\n "+a:"")+(null!==o?"\n "+o:"")+(null!==i?"\n "+i:"")+"\n\nThe above lifecycles should be removed. Learn more about this warning here:\nhttps://fb.me/react-async-component-lifecycle-hooks");if("function"==typeof e.getDerivedStateFromProps&&(t.componentWillMount=l,t.componentWillReceiveProps=u),"function"==typeof t.getSnapshotBeforeUpdate){if("function"!=typeof t.componentDidUpdate)throw new Error("Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype");t.componentWillUpdate=c;var s=t.componentDidUpdate;t.componentDidUpdate=function(e,t,n){n=this.__reactInternalSnapshotFlag?this.__reactInternalSnapshot:n;s.call(this,e,t,n)}}}return e}n.r(t),n.d(t,"polyfill",function(){return r}),c.__suppressDeprecationWarning=u.__suppressDeprecationWarning=l.__suppressDeprecationWarning=!0},function(e,t,n){"use strict";t.__esModule=!0;var r=i(n(309)),a=i(n(325)),o="function"==typeof a.default&&"symbol"==typeof r.default?function(e){return typeof e}:function(e){return e&&"function"==typeof a.default&&e.constructor===a.default&&e!==a.default.prototype?"symbol":typeof e};function i(e){return e&&e.__esModule?e:{default:e}}t.default="function"==typeof a.default&&"symbol"===o(r.default)?function(e){return void 0===e?"undefined":o(e)}:function(e){return e&&"function"==typeof a.default&&e.constructor===a.default&&e!==a.default.prototype?"symbol":void 0===e?"undefined":o(e)}},function(e,t,n){"use strict";function r(){return(r=Object.assign?Object.assign.bind():function(e){for(var t=1;t{r[e]={value:e}}),Object.defineProperties(l,r),Object.defineProperty(u,"isAxiosError",{value:!0}),l.from=(e,t,n,r,a,o)=>{var i=Object.create(u);return s.a.toFlatObject(e,i,function(e){return e!==Error.prototype},e=>"isAxiosError"!==e),l.call(i,e.message,t,n,r,a),i.cause=e,i.name=e.name,o&&Object.assign(i,o),i},t.a=l},function(e,t,n){"use strict";var r=n(44),a={childContextTypes:!0,contextType:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDefaultProps:!0,getDerivedStateFromError:!0,getDerivedStateFromProps:!0,mixins:!0,propTypes:!0,type:!0},d={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},o={$$typeof:!0,compare:!0,defaultProps:!0,displayName:!0,propTypes:!0,type:!0},i={};function p(e){return r.isMemo(e)?o:i[e.$$typeof]||a}i[r.ForwardRef]={$$typeof:!0,render:!0,defaultProps:!0,displayName:!0,propTypes:!0},i[r.Memo]=o;var f=Object.defineProperty,h=Object.getOwnPropertyNames,m=Object.getOwnPropertySymbols,y=Object.getOwnPropertyDescriptor,g=Object.getPrototypeOf,_=Object.prototype;e.exports=function e(t,n,r){if("string"!=typeof n){_&&(a=g(n))&&a!==_&&e(t,a,r);for(var a,o=h(n),i=(m&&(o=o.concat(m(n))),p(t)),s=p(n),l=0;l"+n()+""}}function ce(n,r){return function(){(e={})[m]=oe(r),e[b]="4.4.1";var e,t=M();return t&&(e.nonce=t),y.a.createElement("style",g({},e,{dangerouslySetInnerHTML:{__html:n()}}))}}function de(e){return function(){return Object.keys(e)}}function pe(e,t){return e.createTextNode(se(t))}function fe(e,t,n,r,a){return w&&!n?(n=me(e,t,r),(G?ge:ye)(n,a)):_e()}function he(e,t,n){for(var r=0,a=n.length;r>>16)&65535)<<16),r=1540483477*(65535&r)+((1540483477*(r>>>16)&65535)<<16)^1540483477*(65535&(t^=t>>>24))+((1540483477*(t>>>16)&65535)<<16),n-=4,++a;switch(n){case 3:r^=(255&e.charCodeAt(a+2))<<16;case 2:r^=(255&e.charCodeAt(a+1))<<8;case 1:r=1540483477*(65535&(r^=255&e.charCodeAt(a)))+((1540483477*(r>>>16)&65535)<<16)}return((r=1540483477*(65535&(r^=r>>>13))+((1540483477*(r>>>16)&65535)<<16))^r>>>15)>>>0}var E=52,Oe=function(e){return String.fromCharCode(e+(25<+~=|^:(),"'`-]+/g,Ie=/(^-|-$)/g;function Fe(e){return e.replace(He,"-").replace(Ie,"")}function Y(e){return"string"==typeof e&&!0}var ze={childContextTypes:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDerivedStateFromProps:!0,propTypes:!0,type:!0},We={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},Ve=((t={})[R.ForwardRef]={$$typeof:!0,render:!0},t),Be=Object.defineProperty,Ue=Object.getOwnPropertyNames,n=Object.getOwnPropertySymbols,Ke=void 0===n?function(){return[]}:n,$e=Object.getOwnPropertyDescriptor,Ge=Object.getPrototypeOf,qe=Object.prototype,Je=Array.prototype;function Qe(e,t,n){if("string"!=typeof t)for(var r,a,o=Ge(t),i=(o&&o!==qe&&Qe(e,o,n),Je.concat(Ue(t),Ke(t))),s=Ve[e.$$typeof]||ze,l=Ve[t.$$typeof]||ze,u=i.length;u--;)if(a=i[u],!(We[a]||n&&n[a]||l&&l[a]||s&&s[a])&&(r=$e(t,a)))try{Be(e,a,r)}catch(e){}}var Ze,Xe=Object(d.createContext)(),et=Xe.Consumer,tt=(Ze=d.Component,u(O,Ze),O.prototype.render=function(){return this.props.children?y.a.createElement(Xe.Consumer,null,this.renderInner):null},O.prototype.renderInner=function(e){e=this.getContext(this.props.theme,e);return y.a.createElement(Xe.Provider,{value:e},this.props.children)},O.prototype.getTheme=function(e,t){if(h(e))return e(t);if(null===e||Array.isArray(e)||"object"!==(void 0===e?"undefined":W(e)))throw new i(8);return g({},t,e)},O.prototype.getContext=function(e,t){return this.getTheme(e,t)},O);function O(e){l(this,O);e=c(this,Ze.call(this,e));return e.getContext=Object(H.a)(e.getContext.bind(e)),e.renderInner=e.renderInner.bind(e),e}var nt,rt=Object(d.createContext)(),at=rt.Consumer;nt=d.Component,u(P,nt),P.prototype.getContext=function(e,t){if(e)return e;if(t)return new S(t);throw new i(4)},P.prototype.render=function(){var e=this.props,t=e.children,n=e.sheet,e=e.target;return y.a.createElement(rt.Provider,{value:this.getContext(n,e)},t)};function P(e){l(this,P);e=c(this,nt.call(this,e));return e.getContext=Object(H.a)(e.getContext),e}var ot={};it=d.Component,u(N,it),N.prototype.render=function(){return y.a.createElement(at,null,this.renderOuter)},N.prototype.renderOuter=function(){var e=0=t?e:""+Array(t+1-r.length).join(n)+e},t={s:o,z:function(e){var t=-e.utcOffset(),n=Math.abs(t),r=Math.floor(n/60),a=n%60;return(t<=0?"+":"-")+o(r,2,"0")+":"+o(a,2,"0")},m:function e(t,n){if(t.date()1)return e(i[0])}else{var s=t.name;x[s]=t,a=s}return!r&&a&&(u=a),a||!r&&u},L=function(e,t){if(r(e))return e.clone();var n="object"==typeof t?t:{};return n.date=e,n.args=arguments,new s(n)},S=t,s=(S.l=a,S.i=r,S.w=function(e,t){return L(e,{locale:t.$L,utc:t.$u,x:t.$x,$offset:t.$offset})},function(){function e(e){this.$L=a(e.locale,null,!0),this.parse(e),this.$x=this.$x||e.x||{},this[n]=!0}var t=e.prototype;return t.parse=function(e){this.$d=function(e){var t=e.date,n=e.utc;if(null===t)return new Date(NaN);if(S.u(t))return new Date;if(t instanceof Date)return new Date(t);if("string"==typeof t&&!/Z$/i.test(t)){var r=t.match(i);if(r){var a=r[2]-1||0,o=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],a,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)):new Date(r[1],a,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)}}return new Date(t)}(e),this.init()},t.init=function(){var e=this.$d;this.$y=e.getFullYear(),this.$M=e.getMonth(),this.$D=e.getDate(),this.$W=e.getDay(),this.$H=e.getHours(),this.$m=e.getMinutes(),this.$s=e.getSeconds(),this.$ms=e.getMilliseconds()},t.$utils=function(){return S},t.isValid=function(){return!(this.$d.toString()===M)},t.isSame=function(e,t){var n=L(e);return this.startOf(t)<=n&&n<=this.endOf(t)},t.isAfter=function(e,t){return L(e)e?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="REPLACE",r=S(e,t,s(),u.location);o.confirmTransitionTo(r,n,a,function(e){e&&(u.entries[u.index]=r,i({action:n,location:r}))})},go:l,goBack:function(){l(-1)},goForward:function(){l(1)},canGo:function(e){return 0<=(e=u.index+e)&&e{e=n.call(e);return t[e]||(t[e]=e.slice(8,-1).toLowerCase())});var o=t=>(t=t.toLowerCase(),e=>r(e)===t),i=t=>e=>typeof e===t;let u=Array.isArray,s=i("undefined");let c=o("ArrayBuffer");var d=i("string");let p=i("function"),f=i("number"),h=e=>null!==e&&"object"==typeof e;let m=e=>{var t;return"object"===r(e)&&!(null!==(t=l(e))&&t!==Object.prototype&&null!==Object.getPrototypeOf(t)||Symbol.toStringTag in e||Symbol.iterator in e)};var i=o("Date"),y=o("File"),g=o("Blob"),_=o("FileList"),v=o("URLSearchParams"),[b,w,M,k]=["ReadableStream","Request","Response","Headers"].map(o);function x(t,n,{allOwnKeys:r=!1}={}){if(null!=t){let e;var a;if("object"!=typeof t&&(t=[t]),u(t))for(e=0,a=t.length;e!s(e)&&e!==S;C="undefined"!=typeof Uint8Array&&l(Uint8Array);var C,E,e=e=>C&&e instanceof C,D=o("HTMLFormElement"),O=(E=[Object.prototype.hasOwnProperty][0],(e,t)=>E.call(e,t)),R=o("RegExp");let Y=(r,a)=>{var e=Object.getOwnPropertyDescriptors(r);let o={};x(e,(e,t)=>{var n;!1!==(n=a(e,t,r))&&(o[t]=n||e)}),Object.defineProperties(r,o)};var P="abcdefghijklmnopqrstuvwxyz",N="0123456789";let j={DIGIT:N,ALPHA:P,ALPHA_DIGIT:P+P.toUpperCase()+N};P=o("AsyncFunction");A.a={isArray:u,isArrayBuffer:c,isBuffer:function(e){return null!==e&&!s(e)&&null!==e.constructor&&!s(e.constructor)&&p(e.constructor.isBuffer)&&e.constructor.isBuffer(e)},isFormData:e=>{var t;return e&&("function"==typeof FormData&&e instanceof FormData||p(e.append)&&("formdata"===(t=r(e))||"object"===t&&p(e.toString)&&"[object FormData]"===e.toString()))},isArrayBufferView:function(e){let t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&c(e.buffer)},isString:d,isNumber:f,isBoolean:e=>!0===e||!1===e,isObject:h,isPlainObject:m,isReadableStream:b,isRequest:w,isResponse:M,isHeaders:k,isUndefined:s,isDate:i,isFile:y,isBlob:g,isRegExp:R,isFunction:p,isStream:e=>h(e)&&p(e.pipe),isURLSearchParams:v,isTypedArray:e,isFileList:_,forEach:x,merge:function n(){let r=(T(this)&&this||{}).caseless,a={};var o=(e,t)=>{t=r&&L(a,t)||t,m(a[t])&&m(e)?a[t]=n(a[t],e):m(e)?a[t]=n({},e):u(e)?a[t]=e.slice():a[t]=e};for(let e=0,t=arguments.length;e(x(e,(e,t)=>{r&&p(e)?n[t]=Object(a.a)(e,r):n[t]=e},{allOwnKeys:t}),n),trim:e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,""),stripBOM:e=>e=65279===e.charCodeAt(0)?e.slice(1):e,inherits:(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),n&&Object.assign(e.prototype,n)},toFlatObject:(e,t,n,r)=>{var a,o;let i;var s={};if(t=t||{},null!=e)do{for(a=Object.getOwnPropertyNames(e),i=a.length;0{e=String(e),(void 0===n||n>e.length)&&(n=e.length),n-=t.length;e=e.indexOf(t,n);return-1!==e&&e===n},toArray:e=>{if(!e)return null;if(u(e))return e;let t=e.length;if(!f(t))return null;for(var n=new Array(t);0{for(var n=(e&&e[Symbol.iterator]).call(e);(r=n.next())&&!r.done;){var r=r.value;t.call(e,r[0],r[1])}},matchAll:(e,t)=>{for(var n,r=[];null!==(n=e.exec(t));)r.push(n);return r},isHTMLForm:D,hasOwnProperty:O,hasOwnProp:O,reduceDescriptors:Y,freezeMethods:r=>{Y(r,(e,t)=>{if(p(r)&&-1!==["arguments","caller","callee"].indexOf(t))return!1;var n=r[t];p(n)&&(e.enumerable=!1,"writable"in e?e.writable=!1:e.set||(e.set=()=>{throw Error("Can not rewrite read-only method '"+t+"'")}))})},toObjectSet:(e,t)=>{let n={};var r=e=>{e.forEach(e=>{n[e]=!0})};return u(e)?r(e):r(String(e).split(t)),n},toCamelCase:e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(e,t,n){return t.toUpperCase()+n}),noop:()=>{},toFiniteNumber:(e,t)=>null!=e&&Number.isFinite(e=+e)?e:t,findKey:L,global:S,isContextDefined:T,ALPHABET:j,generateString:(e=16,t=j.ALPHA_DIGIT)=>{let n="";for(var r=t.length;e--;)n+=t[Math.random()*r|0];return n},isSpecCompliantForm:function(e){return!!(e&&p(e.append)&&"FormData"===e[Symbol.toStringTag]&&e[Symbol.iterator])},toJSONObject:e=>{let t=new Array(10),a=(e,r)=>{if(h(e)){if(0<=t.indexOf(e))return;if(!("toJSON"in e)){t[r]=e;let n=u(e)?[]:{};return x(e,(e,t)=>{e=a(e,r+1);s(e)||(n[t]=e)}),t[r]=void 0,n}}return e};return a(e,0)},isAsyncFn:P,isThenable:e=>e&&(h(e)||p(e))&&p(e.then)&&p(e.catch)}}.call(this,I(42))},function(e,t,n){!function e(){if("undefined"!=typeof __REACT_DEVTOOLS_GLOBAL_HOOK__&&"function"==typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE)try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(e)}catch(e){console.error(e)}}(),e.exports=n(297)},function(e,t,n){function i(){var e=this.constructor.getDerivedStateFromProps(this.props,this.state);null!=e&&this.setState(e)}function s(t){this.setState(function(e){return null!=(e=this.constructor.getDerivedStateFromProps(t,e))?e:null}.bind(this))}function l(e,t){try{var n=this.props,r=this.state;this.props=e,this.state=t,this.__reactInternalSnapshotFlag=!0,this.__reactInternalSnapshot=this.getSnapshotBeforeUpdate(n,r)}finally{this.props=n,this.state=r}}function r(e){var t=e.prototype;if(!t||!t.isReactComponent)throw new Error("Can only polyfill class components");if("function"==typeof e.getDerivedStateFromProps||"function"==typeof t.getSnapshotBeforeUpdate){var n=null,r=null,a=null;if("function"==typeof t.componentWillMount?n="componentWillMount":"function"==typeof t.UNSAFE_componentWillMount&&(n="UNSAFE_componentWillMount"),"function"==typeof t.componentWillReceiveProps?r="componentWillReceiveProps":"function"==typeof t.UNSAFE_componentWillReceiveProps&&(r="UNSAFE_componentWillReceiveProps"),"function"==typeof t.componentWillUpdate?a="componentWillUpdate":"function"==typeof t.UNSAFE_componentWillUpdate&&(a="UNSAFE_componentWillUpdate"),null!==n||null!==r||null!==a)throw Error("Unsafe legacy lifecycles will not be called for components using new component APIs.\n\n"+(e.displayName||e.name)+" uses "+("function"==typeof e.getDerivedStateFromProps?"getDerivedStateFromProps()":"getSnapshotBeforeUpdate()")+" but also contains the following legacy lifecycles:"+(null!==n?"\n "+n:"")+(null!==r?"\n "+r:"")+(null!==a?"\n "+a:"")+"\n\nThe above lifecycles should be removed. Learn more about this warning here:\nhttps://fb.me/react-async-component-lifecycle-hooks");if("function"==typeof e.getDerivedStateFromProps&&(t.componentWillMount=i,t.componentWillReceiveProps=s),"function"==typeof t.getSnapshotBeforeUpdate){if("function"!=typeof t.componentDidUpdate)throw new Error("Cannot polyfill getSnapshotBeforeUpdate() for components that do not define componentDidUpdate() on the prototype");t.componentWillUpdate=l;var o=t.componentDidUpdate;t.componentDidUpdate=function(e,t,n){n=this.__reactInternalSnapshotFlag?this.__reactInternalSnapshot:n;o.call(this,e,t,n)}}}return e}n.r(t),n.d(t,"polyfill",function(){return r}),l.__suppressDeprecationWarning=s.__suppressDeprecationWarning=i.__suppressDeprecationWarning=!0},function(e,t,n){t.__esModule=!0;var r=i(n(306)),a=i(n(322)),o="function"==typeof a.default&&"symbol"==typeof r.default?function(e){return typeof e}:function(e){return e&&"function"==typeof a.default&&e.constructor===a.default&&e!==a.default.prototype?"symbol":typeof e};function i(e){return e&&e.__esModule?e:{default:e}}t.default="function"==typeof a.default&&"symbol"===o(r.default)?function(e){return void 0===e?"undefined":o(e)}:function(e){return e&&"function"==typeof a.default&&e.constructor===a.default&&e!==a.default.prototype?"symbol":void 0===e?"undefined":o(e)}},function(e,t,n){var s=n(9);function l(e,t,n,r,a){Error.call(this),Error.captureStackTrace?Error.captureStackTrace(this,this.constructor):this.stack=(new Error).stack,this.message=e,this.name="AxiosError",t&&(this.code=t),n&&(this.config=n),r&&(this.request=r),a&&(this.response=a)}s.a.inherits(l,Error,{toJSON:function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:s.a.toJSONObject(this.config),code:this.code,status:this.response&&this.response.status?this.response.status:null}}});let u=l.prototype,r={};["ERR_BAD_OPTION_VALUE","ERR_BAD_OPTION","ECONNABORTED","ETIMEDOUT","ERR_NETWORK","ERR_FR_TOO_MANY_REDIRECTS","ERR_DEPRECATED","ERR_BAD_RESPONSE","ERR_BAD_REQUEST","ERR_CANCELED","ERR_NOT_SUPPORT","ERR_INVALID_URL"].forEach(e=>{r[e]={value:e}}),Object.defineProperties(l,r),Object.defineProperty(u,"isAxiosError",{value:!0}),l.from=(e,t,n,r,a,o)=>{var i=Object.create(u);return s.a.toFlatObject(e,i,function(e){return e!==Error.prototype},e=>"isAxiosError"!==e),l.call(i,e.message,t,n,r,a),i.cause=e,i.name=e.name,o&&Object.assign(i,o),i},t.a=l},function(e,t,n){function r(){return(r=Object.assign?Object.assign.bind():function(e){for(var t=1;t"+n()+""}},ye=function(n,r){return function(){(e={})[m]=se(r),e[b]="4.4.1";var e,t=M();return t&&(e.nonce=t),y.a.createElement("style",g({},e,{dangerouslySetInnerHTML:{__html:n()}}))}},ge=function(e){return function(){return Object.keys(e)}},_e=function(d,p){function f(e){var t=s[e];return void 0!==t?t:(s[e]=m.length,m.push(0),x(h,e),s[e])}function e(){var e,t=ue(d).cssRules,n="";for(e in s){n+=pe(e);for(var r=s[e],a=fe(m,r),o=a-m[r];o>>16)&65535)<<16),r=1540483477*(65535&r)+((1540483477*(r>>>16)&65535)<<16)^1540483477*(65535&(t^=t>>>24))+((1540483477*(t>>>16)&65535)<<16),n-=4,++a;switch(n){case 3:r^=(255&e.charCodeAt(a+2))<<16;case 2:r^=(255&e.charCodeAt(a+1))<<8;case 1:r=1540483477*(65535&(r^=255&e.charCodeAt(a)))+((1540483477*(r>>>16)&65535)<<16)}return((r=1540483477*(65535&(r^=r>>>13))+((1540483477*(r>>>16)&65535)<<16))^r>>>15)>>>0}var E=52,je=function(e){return String.fromCharCode(e+(25<+~=|^:(),"'`-]+/g,We=/(^-|-$)/g;function Ve(e){return e.replace(ze,"-").replace(We,"")}function O(e){return"string"==typeof e&&!0}var Be={childContextTypes:!0,contextTypes:!0,defaultProps:!0,displayName:!0,getDerivedStateFromProps:!0,propTypes:!0,type:!0},Ue={name:!0,length:!0,prototype:!0,caller:!0,callee:!0,arguments:!0,arity:!0},Ke=((t={})[A.ForwardRef]={$$typeof:!0,render:!0},t),$e=Object.defineProperty,Ge=Object.getOwnPropertyNames,n=Object.getOwnPropertySymbols,qe=void 0===n?function(){return[]}:n,Je=Object.getOwnPropertyDescriptor,Qe=Object.getPrototypeOf,Xe=Object.prototype,Ze=Array.prototype;function et(e,t,n){if("string"!=typeof t)for(var r,a,o=Qe(t),i=(o&&o!==Xe&&et(e,o,n),Ze.concat(Ge(t),qe(t))),s=Ke[e.$$typeof]||Be,l=Ke[t.$$typeof]||Be,u=i.length;u--;)if(a=i[u],!(Ue[a]||n&&n[a]||l&&l[a]||s&&s[a])&&(r=Je(t,a)))try{$e(e,a,r)}catch(e){}}var tt,nt=Object(d.createContext)(),rt=nt.Consumer,at=(tt=d.Component,u(Y,tt),Y.prototype.render=function(){return this.props.children?y.a.createElement(nt.Consumer,null,this.renderInner):null},Y.prototype.renderInner=function(e){e=this.getContext(this.props.theme,e);return y.a.createElement(nt.Provider,{value:e},this.props.children)},Y.prototype.getTheme=function(e,t){if(h(e))return e(t);if(null===e||Array.isArray(e)||"object"!==(void 0===e?"undefined":W(e)))throw new i(8);return g({},t,e)},Y.prototype.getContext=function(e,t){return this.getTheme(e,t)},Y);function Y(e){l(this,Y);e=c(this,tt.call(this,e));return e.getContext=Object(I.a)(e.getContext.bind(e)),e.renderInner=e.renderInner.bind(e),e}var ot,it=Object(d.createContext)(),st=it.Consumer,lt=(ot=d.Component,u(P,ot),P.prototype.getContext=function(e,t){if(e)return e;if(t)return new S(t);throw new i(4)},P.prototype.render=function(){var e=this.props,t=e.children;return y.a.createElement(it.Provider,{value:this.getContext(e.sheet,e.target)},t)},P);function P(e){l(this,P);e=c(this,ot.call(this,e));return e.getContext=Object(I.a)(e.getContext),e}var ut={};ct=d.Component,u(N,ct),N.prototype.render=function(){return y.a.createElement(st,null,this.renderOuter)},N.prototype.renderOuter=function(){var e=0=t?e:""+Array(t+1-r.length).join(n)+e},t={s:o,z:function(e){var t=-e.utcOffset(),n=Math.abs(t),r=Math.floor(n/60),a=n%60;return(t<=0?"+":"-")+o(r,2,"0")+":"+o(a,2,"0")},m:function e(t,n){if(t.date()1)return e(i[0])}else{var s=t.name;x[s]=t,a=s}return!r&&a&&(u=a),a||!r&&u},L=function(e,t){if(r(e))return e.clone();var n="object"==typeof t?t:{};return n.date=e,n.args=arguments,new s(n)},S=t,s=(S.l=a,S.i=r,S.w=function(e,t){return L(e,{locale:t.$L,utc:t.$u,x:t.$x,$offset:t.$offset})},function(){function e(e){this.$L=a(e.locale,null,!0),this.parse(e),this.$x=this.$x||e.x||{},this[n]=!0}var t=e.prototype;return t.parse=function(e){this.$d=function(e){var t=e.date,n=e.utc;if(null===t)return new Date(NaN);if(S.u(t))return new Date;if(t instanceof Date)return new Date(t);if("string"==typeof t&&!/Z$/i.test(t)){var r=t.match(i);if(r){var a=r[2]-1||0,o=(r[7]||"0").substring(0,3);return n?new Date(Date.UTC(r[1],a,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)):new Date(r[1],a,r[3]||1,r[4]||0,r[5]||0,r[6]||0,o)}}return new Date(t)}(e),this.init()},t.init=function(){var e=this.$d;this.$y=e.getFullYear(),this.$M=e.getMonth(),this.$D=e.getDate(),this.$W=e.getDay(),this.$H=e.getHours(),this.$m=e.getMinutes(),this.$s=e.getSeconds(),this.$ms=e.getMilliseconds()},t.$utils=function(){return S},t.isValid=function(){return!(this.$d.toString()===M)},t.isSame=function(e,t){var n=L(e);return this.startOf(t)<=n&&n<=this.endOf(t)},t.isAfter=function(e,t){return L(e) @@ -19,12 +19,13 @@ d.version="2.30.1",H(D),d.fn=O,d.min=rr,d.max=ar,d.now=or,d.utc=c,d.unix=po,d.mo * Released under MIT license * Based on Underscore.js 1.8.3 * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors - */!function(){var zo,Wo="Expected a function",_i="__lodash_hash_undefined__",vi="__lodash_placeholder__",Vo=128,Bo=9007199254740991,bi=NaN,Uo=4294967295,wi=[["ary",Vo],["bind",1],["bindKey",2],["curry",8],["curryRight",16],["flip",512],["partial",32],["partialRight",64],["rearg",256]],Ko="[object Arguments]",Mi="[object Array]",$o="[object Boolean]",Go="[object Date]",ki="[object Error]",xi="[object Function]",Li="[object GeneratorFunction]",qo="[object Map]",Jo="[object Number]",Qo="[object Object]",Si="[object Promise]",Zo="[object RegExp]",Xo="[object Set]",ei="[object String]",Ti="[object Symbol]",ti="[object WeakMap]",ni="[object ArrayBuffer]",ri="[object DataView]",Ci="[object Float32Array]",Ei="[object Float64Array]",Di="[object Int8Array]",Yi="[object Int16Array]",Oi="[object Int32Array]",Pi="[object Uint8Array]",Ni="[object Uint8ClampedArray]",ji="[object Uint16Array]",Ai="[object Uint32Array]",Ri=/\b__p \+= '';/g,Hi=/\b(__p \+=) '' \+/g,Ii=/(__e\(.*?\)|\b__t\)) \+\n'';/g,Fi=/&(?:amp|lt|gt|quot|#39);/g,zi=/[&<>"']/g,Wi=RegExp(Fi.source),Vi=RegExp(zi.source),Bi=/<%-([\s\S]+?)%>/g,Ui=/<%([\s\S]+?)%>/g,Ki=/<%=([\s\S]+?)%>/g,$i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Gi=/^\w*$/,qi=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Ji=/[\\^$.*+?()[\]{}|]/g,Qi=RegExp(Ji.source),Zi=/^\s+/,o=/\s/,Xi=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,es=/\{\n\/\* \[wrapped with (.+)\] \*/,ts=/,? & /,ns=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,rs=/[()=,{}\[\]\/\s]/,as=/\\(\\)?/g,os=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,is=/\w*$/,ss=/^[-+]0x[0-9a-f]+$/i,ls=/^0b[01]+$/i,us=/^\[object .+?Constructor\]$/,cs=/^0o[0-7]+$/i,ds=/^(?:0|[1-9]\d*)$/,ps=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,fs=/($^)/,hs=/['\n\r\u2028\u2029\\]/g,i="\\ud800-\\udfff",s="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",e="\\u2700-\\u27bf",t="a-z\\xdf-\\xf6\\xf8-\\xff",n="A-Z\\xc0-\\xd6\\xd8-\\xde",l="\\ufe0e\\ufe0f",r="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",u="['’]",a="["+i+"]",c="["+r+"]",d="["+s+"]",p="["+e+"]",f="["+t+"]",r="[^"+i+r+"\\d+"+e+t+n+"]",e="\\ud83c[\\udffb-\\udfff]",t="[^"+i+"]",h="(?:\\ud83c[\\udde6-\\uddff]){2}",m="[\\ud800-\\udbff][\\udc00-\\udfff]",n="["+n+"]",y="\\u200d",g="(?:"+f+"|"+r+")",r="(?:"+n+"|"+r+")",_="(?:['’](?:d|ll|m|re|s|t|ve))?",v="(?:['’](?:D|LL|M|RE|S|T|VE))?",b="(?:"+d+"|"+e+")"+"?",w="["+l+"]?",w=w+b+("(?:"+y+"(?:"+[t,h,m].join("|")+")"+w+b+")*"),b="(?:"+[p,h,m].join("|")+")"+w,p="(?:"+[t+d+"?",d,h,m,a].join("|")+")",ms=RegExp(u,"g"),ys=RegExp(d,"g"),M=RegExp(e+"(?="+e+")|"+p+w,"g"),gs=RegExp([n+"?"+f+"+"+_+"(?="+[c,n,"$"].join("|")+")",r+"+"+v+"(?="+[c,n+g,"$"].join("|")+")",n+"?"+g+"+"+_,n+"+"+v,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])","\\d+",b].join("|"),"g"),k=RegExp("["+y+i+s+l+"]"),_s=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,vs=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],bs=-1,ai={},oi=(ai[Ci]=ai[Ei]=ai[Di]=ai[Yi]=ai[Oi]=ai[Pi]=ai[Ni]=ai[ji]=ai[Ai]=!0,ai[Ko]=ai[Mi]=ai[ni]=ai[$o]=ai[ri]=ai[Go]=ai[ki]=ai[xi]=ai[qo]=ai[Jo]=ai[Qo]=ai[Zo]=ai[Xo]=ai[ei]=ai[ti]=!1,{}),x=(oi[Ko]=oi[Mi]=oi[ni]=oi[ri]=oi[$o]=oi[Go]=oi[Ci]=oi[Ei]=oi[Di]=oi[Yi]=oi[Oi]=oi[qo]=oi[Jo]=oi[Qo]=oi[Zo]=oi[Xo]=oi[ei]=oi[Ti]=oi[Pi]=oi[Ni]=oi[ji]=oi[Ai]=!0,oi[ki]=oi[xi]=oi[ti]=!1,{"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"}),ws=parseFloat,Ms=parseInt,t="object"==typeof E&&E&&E.Object===Object&&E,h="object"==typeof self&&self&&self.Object===Object&&self,ii=t||h||Function("return this")(),m=O&&!O.nodeType&&O,L=m&&"object"==typeof D&&D&&!D.nodeType&&D,ks=L&&L.exports===m,S=ks&&t.process,a=function(){try{var e=L&&L.require&&L.require("util").types;return e?e:S&&S.binding&&S.binding("util")}catch(e){}}(),xs=a&&a.isArrayBuffer,Ls=a&&a.isDate,Ss=a&&a.isMap,Ts=a&&a.isRegExp,Cs=a&&a.isSet,Es=a&&a.isTypedArray;function si(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function Ds(e,t,n,r){for(var a=-1,o=null==e?0:e.length;++a":">",'"':""","'":"'"});function el(e){return"\\"+x[e]}function hi(e){return k.test(e)}function tl(e){var n=-1,r=Array(e.size);return e.forEach(function(e,t){r[++n]=[t,e]}),r}function nl(t,n){return function(e){return t(n(e))}}function mi(e,t){for(var n=-1,r=e.length,a=0,o=[];++n",""":'"',"'":"'"});var il=function a(e){var M=(e=null==e?ii:il.defaults(ii.Object(),e,il.pick(ii,vs))).Array,o=e.Date,O=e.Error,P=e.Function,N=e.Math,m=e.Object,j=e.RegExp,W=e.String,k=e.TypeError,V=M.prototype,B=P.prototype,U=m.prototype,K=e["__core-js_shared__"],$=B.toString,A=U.hasOwnProperty,G=0,q=(B=/[^.]+$/.exec(K&&K.keys&&K.keys.IE_PROTO||""))?"Symbol(src)_1."+B:"",J=U.toString,Q=$.call(m),Z=ii._,X=j("^"+$.call(A).replace(Ji,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),B=ks?e.Buffer:zo,t=e.Symbol,ee=e.Uint8Array,te=B?B.allocUnsafe:zo,ne=nl(m.getPrototypeOf,m),re=m.create,ae=U.propertyIsEnumerable,oe=V.splice,ie=t?t.isConcatSpreadable:zo,se=t?t.iterator:zo,le=t?t.toStringTag:zo,ue=function(){try{var e=Zn(m,"defineProperty");return e({},"",{}),e}catch(e){}}(),ce=e.clearTimeout!==ii.clearTimeout&&e.clearTimeout,de=o&&o.now!==ii.Date.now&&o.now,pe=e.setTimeout!==ii.setTimeout&&e.setTimeout,fe=N.ceil,he=N.floor,me=m.getOwnPropertySymbols,B=B?B.isBuffer:zo,ye=e.isFinite,ge=V.join,_e=nl(m.keys,m),x=N.max,L=N.min,ve=o.now,be=e.parseInt,we=N.random,Me=V.reverse,o=Zn(e,"DataView"),ke=Zn(e,"Map"),xe=Zn(e,"Promise"),Le=Zn(e,"Set"),e=Zn(e,"WeakMap"),Se=Zn(m,"create"),Te=e&&new e,Ce={},Ee=kr(o),De=kr(ke),Ye=kr(xe),Oe=kr(Le),Pe=kr(e),t=t?t.prototype:zo,Ne=t?t.valueOf:zo,je=t?t.toString:zo;function h(e){if(z(e)&&!F(e)&&!(e instanceof g)){if(e instanceof y)return e;if(A.call(e,"__wrapped__"))return xr(e)}return new y(e)}var Ae=function(e){if(!w(e))return{};if(re)return re(e);Re.prototype=e;e=new Re;return Re.prototype=zo,e};function Re(){}function He(){}function y(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=zo}function g(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=Uo,this.__views__=[]}function Ie(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t>>0,t>>>=0,M(a));++r>>1,i=e[o];null!==i&&!v(i)&&(n?i<=t:i>>0)?(e=f(e))&&("string"==typeof t||null!=t&&!Ta(t))&&!(t=u(t))&&hi(e)?ln(gi(e),0,n):e.split(t,n):[]},h.spread=function(n,r){if("function"!=typeof n)throw new k(Wo);return r=null==r?0:x(T(r),0),i(function(e){var t=e[r],e=ln(e,0,r);return t&&di(e,t),si(n,this,e)})},h.tail=function(e){var t=null==e?0:e.length;return t?s(e,1,t):[]},h.take=function(e,t,n){return e&&e.length?s(e,0,(t=n||t===zo?1:T(t))<0?0:t):[]},h.takeRight=function(e,t,n){var r=null==e?0:e.length;return r?s(e,(t=r-(t=n||t===zo?1:T(t)))<0?0:t,r):[]},h.takeRightWhile=function(e,t){return e&&e.length?Xt(e,d(t,3),!1,!0):[]},h.takeWhile=function(e,t){return e&&e.length?Xt(e,d(t,3)):[]},h.tap=function(e,t){return t(e),e},h.throttle=function(e,t,n){var r=!0,a=!0;if("function"!=typeof e)throw new k(Wo);return w(n)&&(r="leading"in n?!!n.leading:r,a="trailing"in n?!!n.trailing:a),la(e,t,{leading:r,maxWait:t,trailing:a})},h.thru=Br,h.toArray=Pa,h.toPairs=eo,h.toPairsIn=to,h.toPath=function(e){return F(e)?ci(e,Mr):v(e)?[e]:S(wr(f(e)))},h.toPlainObject=Aa,h.transform=function(e,r,a){var t,n=F(e),o=n||va(e)||Da(e);return r=d(r,4),null==a&&(t=e&&e.constructor,a=o?n?new t:[]:w(e)&&wa(t)?Ae(ne(e)):{}),(o?li:dt)(e,function(e,t,n){return r(a,e,t,n)}),a},h.unary=function(e){return aa(e,1)},h.union=Or,h.unionBy=Pr,h.unionWith=Nr,h.uniq=function(e){return e&&e.length?Jt(e):[]},h.uniqBy=function(e,t){return e&&e.length?Jt(e,d(t,2)):[]},h.uniqWith=function(e,t){return t="function"==typeof t?t:zo,e&&e.length?Jt(e,zo,t):[]},h.unset=function(e,t){return null==e||Qt(e,t)},h.unzip=jr,h.unzipWith=Ar,h.update=function(e,t,n){return null==e?e:Zt(e,t,an(n))},h.updateWith=function(e,t,n,r){return r="function"==typeof r?r:zo,null==e?e:Zt(e,t,an(n),r)},h.values=no,h.valuesIn=function(e){return null==e?[]:Gs(e,D(e))},h.without=Rr,h.words=ho,h.wrap=function(e,t){return pa(an(t),e)},h.xor=Hr,h.xorBy=Ir,h.xorWith=Fr,h.zip=zr,h.zipObject=function(e,t){return nn(e||[],t||[],Ge)},h.zipObjectDeep=function(e,t){return nn(e||[],t||[],Wt)},h.zipWith=Wr,h.entries=eo,h.entriesIn=to,h.extend=Ha,h.extendWith=Ia,ko(h,h),h.add=Po,h.attempt=mo,h.camelCase=ro,h.capitalize=ao,h.ceil=No,h.clamp=function(e,t,n){return n===zo&&(n=t,t=zo),n!==zo&&(n=(n=C(n))==n?n:0),t!==zo&&(t=(t=C(t))==t?t:0),et(C(e),t,n)},h.clone=function(e){return _(e,4)},h.cloneDeep=function(e){return _(e,5)},h.cloneDeepWith=function(e,t){return _(e,5,t="function"==typeof t?t:zo)},h.cloneWith=function(e,t){return _(e,4,t="function"==typeof t?t:zo)},h.conformsTo=function(e,t){return null==t||tt(e,t,E(t))},h.deburr=oo,h.defaultTo=function(e,t){return null==e||e!=e?t:e},h.divide=jo,h.endsWith=function(e,t,n){e=f(e),t=u(t);var r=e.length,r=n=n===zo?r:et(T(n),0,r);return 0<=(n-=t.length)&&e.slice(n,r)==t},h.eq=I,h.escape=function(e){return(e=f(e))&&Vi.test(e)?e.replace(zi,Xs):e},h.escapeRegExp=function(e){return(e=f(e))&&Qi.test(e)?e.replace(Ji,"\\$&"):e},h.every=function(e,t,n){return(F(e)?Os:it)(e,d(t=n&&p(e,t,n)?zo:t,3))},h.find=$r,h.findIndex=Lr,h.findKey=function(e,t){return Hs(e,d(t,3),dt)},h.findLast=Gr,h.findLastIndex=Sr,h.findLastKey=function(e,t){return Hs(e,d(t,3),pt)},h.floor=Ao,h.forEach=qr,h.forEachRight=Jr,h.forIn=function(e,t){return null==e?e:ut(e,d(t,3),D)},h.forInRight=function(e,t){return null==e?e:ct(e,d(t,3),D)},h.forOwn=function(e,t){return e&&dt(e,d(t,3))},h.forOwnRight=function(e,t){return e&&pt(e,d(t,3))},h.get=Ba,h.gt=ma,h.gte=ya,h.has=function(e,t){return null!=e&&tr(e,t,gt)},h.hasIn=Ua,h.head=Cr,h.identity=Y,h.includes=function(e,t,n,r){return e=c(e)?e:no(e),n=n&&!r?T(n):0,r=e.length,n<0&&(n=x(r+n,0)),Ea(e)?n<=r&&-1=L(t=t,n=n)&&e=this.__values__.length;return{done:e,value:e?zo:this.__values__[this.__index__++]}},h.prototype.plant=function(e){for(var t,n=this;n instanceof He;)var r=xr(n),a=(r.__index__=0,r.__values__=zo,t?a.__wrapped__=r:t=r,r),n=n.__wrapped__;return a.__wrapped__=e,t},h.prototype.reverse=function(){var e=this.__wrapped__;return e instanceof g?(e=e,(e=(e=this.__actions__.length?new g(this):e).reverse()).__actions__.push({func:Br,args:[Yr],thisArg:zo}),new y(e,this.__chain__)):this.thru(Yr)},h.prototype.toJSON=h.prototype.valueOf=h.prototype.value=function(){return en(this.__wrapped__,this.__actions__)},h.prototype.first=h.prototype.head,se&&(h.prototype[se]=function(){return this}),h}();ii._=il,(Y=function(){return il}.call(O,P,O,D))!==zo&&(D.exports=Y)}.call(this)}.call(this,P(41),P(92)(e))},function(e,t,n){var r=n(39),a=n(81),o=n(58),i=Object.defineProperty;t.f=n(25)?Object.defineProperty:function(e,t,n){if(r(e),t=o(t,!0),r(n),a)try{return i(e,t,n)}catch(e){}if("get"in n||"set"in n)throw TypeError("Accessors not supported!");return"value"in n&&(e[t]=n.value),e}},function(e,t){var n={}.hasOwnProperty;e.exports=function(e,t){return n.call(e,t)}},function(e,t){e.exports=function(e){return e&&e.__esModule?e:{default:e}},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){function m(e,t,n){var r,a,o,i=e&m.F,s=e&m.G,l=e&m.S,u=e&m.P,c=e&m.B,d=e&m.W,p=s?g:g[t]||(g[t]={}),f=p[w],h=s?y:l?y[t]:(y[t]||{})[w];for(r in n=s?t:n)(a=!i&&h&&void 0!==h[r])&&b(p,r)||(o=(a?h:n)[r],p[r]=s&&"function"!=typeof h[r]?n[r]:c&&a?_(o,y):d&&h[r]==o?function(r){function e(e,t,n){if(this instanceof r){switch(arguments.length){case 0:return new r;case 1:return new r(e);case 2:return new r(e,t)}return new r(e,t,n)}return r.apply(this,arguments)}return e[w]=r[w],e}(o):u&&"function"==typeof o?_(Function.call,o):o,u&&((p.virtual||(p.virtual={}))[r]=o,e&m.R)&&f&&!f[r]&&v(f,r,o))}var y=n(23),g=n(24),_=n(80),v=n(32),b=n(29),w="prototype";m.F=1,m.G=2,m.S=4,m.P=8,m.B=16,m.W=32,m.U=64,m.R=128,e.exports=m},function(e,t,n){var r=n(28),a=n(46);e.exports=n(25)?function(e,t,n){return r.f(e,t,a(1,n))}:function(e,t,n){return e[t]=n,e}},function(e,t){e.exports=function(e){return"object"==typeof e?null!==e:"function"==typeof e}},function(e,t,n){var r=n(85),a=n(57);e.exports=function(e){return r(a(e))}},function(e,t,n){var r=n(62)("wks"),a=n(48),o=n(23).Symbol,i="function"==typeof o;(e.exports=function(e){return r[e]||(r[e]=i&&o[e]||(i?o:a)("Symbol."+e))}).store=r},function(e,t,n){"use strict";var r=n(30),o=(Object.defineProperty(t,"__esModule",{value:!0}),t.format=function(){for(var e=arguments.length,t=new Array(e),n=0;ndocument.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?(r[l]=a(e),n=new r,r[l]=null,n[s]=e):n=u(),void 0===t?n:o(n,t)}},function(e,t,n){var r=n(62)("keys"),a=n(48);e.exports=function(e){return r[e]||(r[e]=a(e))}},function(e,t,n){var r=n(24),a=n(23),o="__core-js_shared__",i=a[o]||(a[o]={});(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:r.version,mode:n(45)?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){var r=n(28).f,a=n(29),o=n(35)("toStringTag");e.exports=function(e,t,n){e&&!a(e=n?e:e.prototype,o)&&r(e,o,{configurable:!0,value:t})}},function(e,t,n){var r=n(57);e.exports=function(e){return Object(r(e))}},function(e,t,n){t.f=n(35)},function(e,t,n){var r=n(23),a=n(24),o=n(45),i=n(66),s=n(28).f;e.exports=function(e){var t=a.Symbol||(a.Symbol=!o&&r.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:i.f(e)})}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){n=n(359)();e.exports=n;try{regeneratorRuntime=n}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=n:Function("r","regeneratorRuntime = r")(n)}},function(e,t){function l(e,t,n,r,a,o,i){try{var s=e[o](i),l=s.value}catch(e){return void n(e)}s.done?t(l):Promise.resolve(l).then(r,a)}e.exports=function(s){return function(){var e=this,i=arguments;return new Promise(function(t,n){var r=s.apply(e,i);function a(e){l(r,t,n,a,o,"next",e)}function o(e){l(r,t,n,a,o,"throw",e)}a(void 0)})}},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){var r=n(230);e.exports=function(e,t,n){return(t=r(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){"use strict";e.exports=n(305)},function(e,t,n){e.exports=function s(e){"use strict";var he=/^\0+/g,N=/[\0\r\f]/g,l=/: */g,u=/zoo|gra/,c=/([,: ])(transform)/g,d=/,+\s*(?![^(]*[)])/g,p=/ +\s*(?![^(]*[)])/g,me=/ *[\0] */g,h=/,\r+?/g,i=/([\t\r\n ])*\f?&/g,m=/:global\(((?:[^\(\)\[\]]*|\[.*\]|\([^\(\)]*\))*)\)/g,y=/\W+/g,ye=/@(k\w+)\s*(\S*)\s*/,ge=/::(place)/g,_e=/:(read-only)/g,g=/\s+(?=[{\];=:>])/g,_=/([[}=:>])\s+/g,v=/(\{[^{]+?);(?=\})/g,b=/\s{2,}/g,ve=/([^\(])(:+) */g,w=/[svh]\w+-[tblr]{2}/,be=/\(\s*(.*)\s*\)/g,r=/([\s\S]*?);/g,M=/-self|flex-/g,o=/[^]*?(:[rp][el]a[\w-]+)[^]*/,k=/stretch|:\s*\w+\-(?:conte|avail)/,x=/([^-])(image-set\()/,j="-webkit-",A="-moz-",R="-ms-",H=59,I=125,F=123,z=40,W=41,V=91,we=93,B=10,U=13,K=9,$=64,G=32,Me=38,q=45,L=95,J=42,Q=44,Z=58,X=39,ee=34,te=47,ne=62,re=43,ke=126,xe=0,Le=12,Se=11,Te=107,Ce=109,Ee=115,De=112,Ye=111,Oe=105,Pe=99,Ne=100,je=112,ae=1,oe=1,ie=0,se=1,le=1,Ae=1,S=0,Re=0,He=0,Ie=[],T=[],ue=0,C=null,E=-2,D=-1,Fe=0,ze=1,We=2,Ve=3,Be=0,ce=1,Ue="",de="",pe="";function Ke(e,t,n,r,a){for(var o,i,s=0,l=0,u=0,c=0,d=0,p=0,f=0,h=0,m=0,y=0,g=0,_=0,v=0,b=0,w=0,M=0,k=0,x=0,L=0,S=n.length,T=S-1,C="",E="",D="",Y="",O="",P="";w0)E=E.replace(N,"");if(E.trim().length>0){switch(f){case G:case K:case H:case U:case B:break;default:E+=n.charAt(w)}f=H}}if(1===k)switch(f){case F:case I:case H:case ee:case X:case z:case W:case Q:k=0;case K:case U:case B:case G:break;default:for(k=0,L=w,d=f,w--,f=H;L0)++w,f=d;case F:L=S}}switch(f){case F:for(d=(E=E.trim()).charCodeAt(0),g=1,L=++w;w0)E=E.replace(N,"");switch(p=E.charCodeAt(1)){case Ne:case Ce:case Ee:case q:o=t;break;default:o=Ie}if(L=(D=Ke(t,o,D,p,a+1)).length,He>0&&0===L)L=E.length;if(ue>0)if(o=$e(Ie,E,x),i=fe(Ve,D,o,t,oe,ae,L,p,a,r),E=o.join(""),void 0!==i)if(0===(L=(D=i.trim()).length))p=0,D="";if(L>0)switch(p){case Ee:E=E.replace(be,Je);case Ne:case Ce:case q:D=E+"{"+D+"}";break;case Te:if(D=(E=E.replace(ye,"$1 $2"+(ce>0?Ue:"")))+"{"+D+"}",1===le||2===le&&qe("@"+D,3))D="@"+j+D+"@"+D;else D="@"+D;break;default:if(D=E+D,r===je)Y+=D,D=""}else D="";break;default:D=Ke(t,$e(t,E,x),D,r,a+1)}O+=D,_=0,k=0,b=0,M=0,x=0,v=0,E="",D="",f=n.charCodeAt(++w);break;case I:case H:if((L=(E=(M>0?E.replace(N,""):E).trim()).length)>1){if(0===b)if((d=E.charCodeAt(0))===q||d>96&&d<123)L=(E=E.replace(" ",":")).length;if(ue>0)if(void 0!==(i=fe(ze,E,t,e,oe,ae,Y.length,r,a,r)))if(0===(L=(E=i.trim()).length))E="\0\0";switch(d=E.charCodeAt(0),p=E.charCodeAt(1),d){case xe:break;case $:if(p===Oe||p===Pe){P+=E+n.charAt(w);break}default:if(E.charCodeAt(L-1)===Z)break;Y+=Ge(E,d,p,E.charCodeAt(2))}}_=0,k=0,b=0,M=0,x=0,E="",f=n.charCodeAt(++w)}}switch(f){case U:case B:if(l+c+u+s+Re===0)switch(y){case W:case X:case ee:case $:case ke:case ne:case J:case re:case te:case q:case Z:case Q:case H:case F:case I:break;default:if(b>0)k=1}if(l===te)l=0;else if(se+_===0&&r!==Te&&E.length>0)M=1,E+="\0";if(ue*Be>0)fe(Fe,E,t,e,oe,ae,Y.length,r,a,r);ae=1,oe++;break;case H:case I:if(l+c+u+s===0){ae++;break}default:switch(ae++,C=n.charAt(w),f){case K:case G:if(c+s+l===0)switch(h){case Q:case Z:case K:case G:C="";break;default:if(f!==G)C=" "}break;case xe:C="\\0";break;case Le:C="\\f";break;case Se:C="\\v";break;case Me:if(c+l+s===0&&se>0)x=1,M=1,C="\f"+C;break;case 108:if(c+l+s+ie===0&&b>0)switch(w-b){case 2:if(h===De&&n.charCodeAt(w-3)===Z)ie=h;case 8:if(m===Ye)ie=m}break;case Z:if(c+l+s===0)b=w;break;case Q:if(l+u+c+s===0)M=1,C+="\r";break;case ee:case X:if(0===l)c=c===f?0:0===c?f:c;break;case V:if(c+l+u===0)s++;break;case we:if(c+l+u===0)s--;break;case W:if(c+l+s===0)u--;break;case z:if(c+l+s===0){if(0===_)switch(2*h+3*m){case 533:break;default:g=0,_=1}u++}break;case $:if(l+u+c+s+b+v===0)v=1;break;case J:case te:if(c+s+u>0)break;switch(l){case 0:switch(2*f+3*n.charCodeAt(w+1)){case 235:l=te;break;case 220:L=w,l=J}break;case J:if(f===te&&h===J&&L+2!==w){if(33===n.charCodeAt(L+2))Y+=n.substring(L,w+1);C="",l=0}}}if(0===l){if(se+c+s+v===0&&r!==Te&&f!==H)switch(f){case Q:case ke:case ne:case re:case W:case z:if(0===_){switch(h){case K:case G:case B:case U:C+="\0";break;default:C="\0"+C+(f===Q?"":"\0")}M=1}else switch(f){case z:if(b+7===w&&108===h)b=0;_=++g;break;case W:if(0==(_=--g))M=1,C+="\0"}break;case K:case G:switch(h){case xe:case F:case I:case H:case Q:case Le:case K:case G:case B:case U:break;default:if(0===_)M=1,C+="\0"}}if(E+=C,f!==G&&f!==K)y=f}}m=h,h=f,w++}if(L=Y.length,He>0)if(0===L&&0===O.length&&0===t[0].length==false)if(r!==Ce||1===t.length&&(se>0?de:pe)===t[0])L=t.join(",").length+2;if(L>0){if(o=0===se&&r!==Te?function(e){for(var t,n,r=0,a=e.length,o=Array(a);r1)continue;if(c=s.charCodeAt(s.length-1),d=n.charCodeAt(0),t="",0!==l)switch(c){case J:case ke:case ne:case re:case G:case z:break;default:t=" "}switch(d){case Me:n=t+de;case ke:case ne:case re:case G:case W:case z:break;case V:n=t+n+de;break;case Z:switch(2*n.charCodeAt(1)+3*n.charCodeAt(2)){case 530:if(Ae>0){n=t+n.substring(8,u-1);break}default:if(l<1||i[l-1].length<1)n=t+de+n}break;case Q:t="";default:if(u>1&&n.indexOf(":")>0)n=t+n.replace(ve,"$1"+de+"$2");else n=t+n+de}s+=n}o[r]=s.replace(N,"").trim()}return o}(t):t,ue>0)if(void 0!==(i=fe(We,Y,o,e,oe,ae,L,r,a,r))&&0===(Y=i).length)return P+Y+O;if(Y=o.join(",")+"{"+Y+"}",le*ie!=0){if(2===le&&!qe(Y,2))ie=0;switch(ie){case Ye:Y=Y.replace(_e,":"+A+"$1")+Y;break;case De:Y=Y.replace(ge,"::"+j+"input-$1")+Y.replace(ge,"::"+A+"$1")+Y.replace(ge,":"+R+"input-$1")+Y}ie=0}}return P+Y+O}function $e(e,t,n){var r=t.trim().split(h),a=r,o=r.length,i=e.length;switch(i){case 0:case 1:for(var s=0,l=0===i?"":e[0]+" ";s0&&se>0)return a.replace(m,"$1").replace(i,"$1"+pe);break;default:return e.trim()+a.replace(i,"$1"+e.trim())}default:if(n*se>0&&a.indexOf("\f")>0)return a.replace(i,(e.charCodeAt(0)===Z?"":"$1")+e.trim())}return e+a}function Ge(e,t,n,r){var a,o=0,i=e+";",s=2*t+3*n+4*r;if(944===s)return function(e){var t=e.length,n=e.indexOf(":",9)+1,r=e.substring(0,n).trim(),a=e.substring(n,t-1).trim();switch(e.charCodeAt(9)*ce){case 0:break;case q:if(110!==e.charCodeAt(10))break;default:for(var o=a.split((a="",d)),i=0,n=0,t=o.length;i$&&u<90||u>96&&u<123||u===L||u===q&&s.charCodeAt(1)!==q))switch(isNaN(parseFloat(s))+(-1!==s.indexOf("("))){case 1:switch(s){case"infinite":case"alternate":case"backwards":case"running":case"normal":case"forwards":case"both":case"none":case"linear":case"ease":case"ease-in":case"ease-out":case"ease-in-out":case"paused":case"reverse":case"alternate-reverse":case"inherit":case"initial":case"unset":case"step-start":case"step-end":break;default:s+=Ue}}l[n++]=s}a+=(0===i?"":",")+l.join(" ")}}if(a=r+a+";",1===le||2===le&&qe(a,1))return j+a+a;return a}(i);else if(0===le||2===le&&!qe(i,1))return i;switch(s){case 1015:return 97===i.charCodeAt(10)?j+i+i:i;case 951:return 116===i.charCodeAt(3)?j+i+i:i;case 963:return 110===i.charCodeAt(5)?j+i+i:i;case 1009:if(100!==i.charCodeAt(4))break;case 969:case 942:return j+i+i;case 978:return j+i+A+i+i;case 1019:case 983:return j+i+A+i+R+i+i;case 883:if(i.charCodeAt(8)===q)return j+i+i;if(i.indexOf("image-set(",11)>0)return i.replace(x,"$1"+j+"$2")+i;return i;case 932:if(i.charCodeAt(4)===q)switch(i.charCodeAt(5)){case 103:return j+"box-"+i.replace("-grow","")+j+i+R+i.replace("grow","positive")+i;case 115:return j+i+R+i.replace("shrink","negative")+i;case 98:return j+i+R+i.replace("basis","preferred-size")+i}return j+i+R+i+i;case 964:return j+i+R+"flex-"+i+i;case 1023:if(99!==i.charCodeAt(8))break;return a=i.substring(i.indexOf(":",15)).replace("flex-","").replace("space-between","justify"),j+"box-pack"+a+j+i+R+"flex-pack"+a+i;case 1005:return u.test(i)?i.replace(l,":"+j)+i.replace(l,":"+A)+i:i;case 1e3:switch(o=(a=i.substring(13).trim()).indexOf("-")+1,a.charCodeAt(0)+a.charCodeAt(o)){case 226:a=i.replace(w,"tb");break;case 232:a=i.replace(w,"tb-rl");break;case 220:a=i.replace(w,"lr");break;default:return i}return j+i+R+a+i;case 1017:if(-1===i.indexOf("sticky",9))return i;case 975:switch(o=(i=e).length-10,s=(a=(33===i.charCodeAt(o)?i.substring(0,o):i).substring(e.indexOf(":",7)+1).trim()).charCodeAt(0)+(0|a.charCodeAt(7))){case 203:if(a.charCodeAt(8)<111)break;case 115:i=i.replace(a,j+a)+";"+i;break;case 207:case 102:i=i.replace(a,j+(s>102?"inline-":"")+"box")+";"+i.replace(a,j+a)+";"+i.replace(a,R+a+"box")+";"+i}return i+";";case 938:if(i.charCodeAt(5)===q)switch(i.charCodeAt(6)){case 105:return a=i.replace("-items",""),j+i+j+"box-"+a+R+"flex-"+a+i;case 115:return j+i+R+"flex-item-"+i.replace(M,"")+i;default:return j+i+R+"flex-line-pack"+i.replace("align-content","").replace(M,"")+i}break;case 973:case 989:if(i.charCodeAt(3)!==q||122===i.charCodeAt(4))break;case 931:case 953:if(true===k.test(e))if(115===(a=e.substring(e.indexOf(":")+1)).charCodeAt(0))return Ge(e.replace("stretch","fill-available"),t,n,r).replace(":fill-available",":stretch");else return i.replace(a,j+a)+i.replace(a,A+a.replace("fill-",""))+i;break;case 962:if(i=j+i+(102===i.charCodeAt(5)?R+i:"")+i,n+r===211&&105===i.charCodeAt(13)&&i.indexOf("transform",10)>0)return i.substring(0,i.indexOf(";",27)+1).replace(c,"$1"+j+"$2")+i}return i}function qe(e,t){var n=e.indexOf(1===t?":":"{"),r=e.substring(0,3!==t?n:10),a=e.substring(n+1,e.length-1);return C(2!==t?r:r.replace(o,"$1"),a,t)}function Je(e,t){var n=Ge(t,t.charCodeAt(0),t.charCodeAt(1),t.charCodeAt(2));return n!==t+";"?n.replace(r," or ($1)").substring(4):"("+t+")"}function fe(e,t,n,r,a,o,i,s,l,u){for(var c,d=0,p=t;d0)Ue=n.replace(y,r===V?"":"-");if(r=1,1===se)pe=n;else de=n;var a,o=[pe];if(ue>0)if(void 0!==(a=fe(D,t,o,o,oe,ae,0,0,0,0))&&"string"==typeof a)t=a;var i=Ke(Ie,o,t,0,0);if(ue>0)if(void 0!==(a=fe(E,i,o,o,oe,ae,i.length,0,0,0))&&"string"!=typeof(i=a))r=0;return Ue="",pe="",de="",ie=0,oe=1,ae=1,S*r==0?i:i.replace(N,"").replace(g,"").replace(_,"$1").replace(v,"$1").replace(b," ")}if(f.use=function e(t){switch(t){case void 0:case null:ue=T.length=0;break;default:if("function"==typeof t)T[ue++]=t;else if("object"==typeof t)for(var n=0,r=t.length;n"']/g,Wi=RegExp(Fi.source),Vi=RegExp(zi.source),Bi=/<%-([\s\S]+?)%>/g,Ui=/<%([\s\S]+?)%>/g,Ki=/<%=([\s\S]+?)%>/g,$i=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,Gi=/^\w*$/,qi=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g,Ji=/[\\^$.*+?()[\]{}|]/g,Qi=RegExp(Ji.source),Xi=/^\s+/,o=/\s/,Zi=/\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/,es=/\{\n\/\* \[wrapped with (.+)\] \*/,ts=/,? & /,ns=/[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g,rs=/[()=,{}\[\]\/\s]/,as=/\\(\\)?/g,os=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,is=/\w*$/,ss=/^[-+]0x[0-9a-f]+$/i,ls=/^0b[01]+$/i,us=/^\[object .+?Constructor\]$/,cs=/^0o[0-7]+$/i,ds=/^(?:0|[1-9]\d*)$/,ps=/[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g,fs=/($^)/,hs=/['\n\r\u2028\u2029\\]/g,i="\\ud800-\\udfff",s="\\u0300-\\u036f\\ufe20-\\ufe2f\\u20d0-\\u20ff",e="\\u2700-\\u27bf",t="a-z\\xdf-\\xf6\\xf8-\\xff",n="A-Z\\xc0-\\xd6\\xd8-\\xde",l="\\ufe0e\\ufe0f",r="\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000",u="['’]",a="["+i+"]",c="["+r+"]",d="["+s+"]",p="["+e+"]",f="["+t+"]",r="[^"+i+r+"\\d+"+e+t+n+"]",e="\\ud83c[\\udffb-\\udfff]",t="[^"+i+"]",h="(?:\\ud83c[\\udde6-\\uddff]){2}",m="[\\ud800-\\udbff][\\udc00-\\udfff]",n="["+n+"]",y="\\u200d",g="(?:"+f+"|"+r+")",r="(?:"+n+"|"+r+")",_="(?:['’](?:d|ll|m|re|s|t|ve))?",v="(?:['’](?:D|LL|M|RE|S|T|VE))?",b="(?:"+d+"|"+e+")"+"?",w="["+l+"]?",w=w+b+("(?:"+y+"(?:"+[t,h,m].join("|")+")"+w+b+")*"),b="(?:"+[p,h,m].join("|")+")"+w,p="(?:"+[t+d+"?",d,h,m,a].join("|")+")",ms=RegExp(u,"g"),ys=RegExp(d,"g"),M=RegExp(e+"(?="+e+")|"+p+w,"g"),gs=RegExp([n+"?"+f+"+"+_+"(?="+[c,n,"$"].join("|")+")",r+"+"+v+"(?="+[c,n+g,"$"].join("|")+")",n+"?"+g+"+"+_,n+"+"+v,"\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])","\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])","\\d+",b].join("|"),"g"),k=RegExp("["+y+i+s+l+"]"),_s=/[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,vs=["Array","Buffer","DataView","Date","Error","Float32Array","Float64Array","Function","Int8Array","Int16Array","Int32Array","Map","Math","Object","Promise","RegExp","Set","String","Symbol","TypeError","Uint8Array","Uint8ClampedArray","Uint16Array","Uint32Array","WeakMap","_","clearTimeout","isFinite","parseInt","setTimeout"],bs=-1,ai={},oi=(ai[Ci]=ai[Ei]=ai[Di]=ai[Oi]=ai[Yi]=ai[Pi]=ai[Ni]=ai[ji]=ai[Ri]=!0,ai[Ko]=ai[Mi]=ai[ni]=ai[$o]=ai[ri]=ai[Go]=ai[ki]=ai[xi]=ai[qo]=ai[Jo]=ai[Qo]=ai[Xo]=ai[Zo]=ai[ei]=ai[ti]=!1,{}),x=(oi[Ko]=oi[Mi]=oi[ni]=oi[ri]=oi[$o]=oi[Go]=oi[Ci]=oi[Ei]=oi[Di]=oi[Oi]=oi[Yi]=oi[qo]=oi[Jo]=oi[Qo]=oi[Xo]=oi[Zo]=oi[ei]=oi[Ti]=oi[Pi]=oi[Ni]=oi[ji]=oi[Ri]=!0,oi[ki]=oi[xi]=oi[ti]=!1,{"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"}),ws=parseFloat,Ms=parseInt,t="object"==typeof E&&E&&E.Object===Object&&E,h="object"==typeof self&&self&&self.Object===Object&&self,ii=t||h||Function("return this")(),m=Y&&!Y.nodeType&&Y,L=m&&"object"==typeof D&&D&&!D.nodeType&&D,ks=L&&L.exports===m,S=ks&&t.process,a=function(){try{var e=L&&L.require&&L.require("util").types;return e?e:S&&S.binding&&S.binding("util")}catch(e){}}(),xs=a&&a.isArrayBuffer,Ls=a&&a.isDate,Ss=a&&a.isMap,Ts=a&&a.isRegExp,Cs=a&&a.isSet,Es=a&&a.isTypedArray;function si(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function Ds(e,t,n,r){for(var a=-1,o=null==e?0:e.length;++a":">",'"':""","'":"'"});function el(e){return"\\"+x[e]}function hi(e){return k.test(e)}function tl(e){var n=-1,r=Array(e.size);return e.forEach(function(e,t){r[++n]=[t,e]}),r}function nl(t,n){return function(e){return t(n(e))}}function mi(e,t){for(var n=-1,r=e.length,a=0,o=[];++n",""":'"',"'":"'"});var il=function a(e){var M=(e=null==e?ii:il.defaults(ii.Object(),e,il.pick(ii,vs))).Array,o=e.Date,Y=e.Error,P=e.Function,N=e.Math,m=e.Object,j=e.RegExp,W=e.String,k=e.TypeError,V=M.prototype,B=P.prototype,U=m.prototype,K=e["__core-js_shared__"],$=B.toString,R=U.hasOwnProperty,G=0,q=(B=/[^.]+$/.exec(K&&K.keys&&K.keys.IE_PROTO||""))?"Symbol(src)_1."+B:"",J=U.toString,Q=$.call(m),X=ii._,Z=j("^"+$.call(R).replace(Ji,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),B=ks?e.Buffer:zo,t=e.Symbol,ee=e.Uint8Array,te=B?B.allocUnsafe:zo,ne=nl(m.getPrototypeOf,m),re=m.create,ae=U.propertyIsEnumerable,oe=V.splice,ie=t?t.isConcatSpreadable:zo,se=t?t.iterator:zo,le=t?t.toStringTag:zo,ue=function(){try{var e=Xn(m,"defineProperty");return e({},"",{}),e}catch(e){}}(),ce=e.clearTimeout!==ii.clearTimeout&&e.clearTimeout,de=o&&o.now!==ii.Date.now&&o.now,pe=e.setTimeout!==ii.setTimeout&&e.setTimeout,fe=N.ceil,he=N.floor,me=m.getOwnPropertySymbols,B=B?B.isBuffer:zo,ye=e.isFinite,ge=V.join,_e=nl(m.keys,m),x=N.max,L=N.min,ve=o.now,be=e.parseInt,we=N.random,Me=V.reverse,o=Xn(e,"DataView"),ke=Xn(e,"Map"),xe=Xn(e,"Promise"),Le=Xn(e,"Set"),e=Xn(e,"WeakMap"),Se=Xn(m,"create"),Te=e&&new e,Ce={},Ee=kr(o),De=kr(ke),Oe=kr(xe),Ye=kr(Le),Pe=kr(e),t=t?t.prototype:zo,Ne=t?t.valueOf:zo,je=t?t.toString:zo;function h(e){if(z(e)&&!F(e)&&!(e instanceof g)){if(e instanceof y)return e;if(R.call(e,"__wrapped__"))return xr(e)}return new y(e)}var Re=function(e){if(!w(e))return{};if(re)return re(e);Ae.prototype=e;e=new Ae;return Ae.prototype=zo,e};function Ae(){}function Ie(){}function y(e,t){this.__wrapped__=e,this.__actions__=[],this.__chain__=!!t,this.__index__=0,this.__values__=zo}function g(e){this.__wrapped__=e,this.__actions__=[],this.__dir__=1,this.__filtered__=!1,this.__iteratees__=[],this.__takeCount__=Uo,this.__views__=[]}function He(e){var t=-1,n=null==e?0:e.length;for(this.clear();++t>>0,t>>>=0,M(a));++r>>1,i=e[o];null!==i&&!v(i)&&(n?i<=t:i>>0)?(e=f(e))&&("string"==typeof t||null!=t&&!Ta(t))&&!(t=u(t))&&hi(e)?ln(gi(e),0,n):e.split(t,n):[]},h.spread=function(n,r){if("function"!=typeof n)throw new k(Wo);return r=null==r?0:x(T(r),0),i(function(e){var t=e[r],e=ln(e,0,r);return t&&di(e,t),si(n,this,e)})},h.tail=function(e){var t=null==e?0:e.length;return t?s(e,1,t):[]},h.take=function(e,t,n){return e&&e.length?s(e,0,(t=n||t===zo?1:T(t))<0?0:t):[]},h.takeRight=function(e,t,n){var r=null==e?0:e.length;return r?s(e,(t=r-(t=n||t===zo?1:T(t)))<0?0:t,r):[]},h.takeRightWhile=function(e,t){return e&&e.length?Zt(e,d(t,3),!1,!0):[]},h.takeWhile=function(e,t){return e&&e.length?Zt(e,d(t,3)):[]},h.tap=function(e,t){return t(e),e},h.throttle=function(e,t,n){var r=!0,a=!0;if("function"!=typeof e)throw new k(Wo);return w(n)&&(r="leading"in n?!!n.leading:r,a="trailing"in n?!!n.trailing:a),la(e,t,{leading:r,maxWait:t,trailing:a})},h.thru=Br,h.toArray=Pa,h.toPairs=eo,h.toPairsIn=to,h.toPath=function(e){return F(e)?ci(e,Mr):v(e)?[e]:S(wr(f(e)))},h.toPlainObject=Ra,h.transform=function(e,r,a){var t,n=F(e),o=n||va(e)||Da(e);return r=d(r,4),null==a&&(t=e&&e.constructor,a=o?n?new t:[]:w(e)&&wa(t)?Re(ne(e)):{}),(o?li:dt)(e,function(e,t,n){return r(a,e,t,n)}),a},h.unary=function(e){return aa(e,1)},h.union=Yr,h.unionBy=Pr,h.unionWith=Nr,h.uniq=function(e){return e&&e.length?Jt(e):[]},h.uniqBy=function(e,t){return e&&e.length?Jt(e,d(t,2)):[]},h.uniqWith=function(e,t){return t="function"==typeof t?t:zo,e&&e.length?Jt(e,zo,t):[]},h.unset=function(e,t){return null==e||Qt(e,t)},h.unzip=jr,h.unzipWith=Rr,h.update=function(e,t,n){return null==e?e:Xt(e,t,an(n))},h.updateWith=function(e,t,n,r){return r="function"==typeof r?r:zo,null==e?e:Xt(e,t,an(n),r)},h.values=no,h.valuesIn=function(e){return null==e?[]:Gs(e,D(e))},h.without=Ar,h.words=ho,h.wrap=function(e,t){return pa(an(t),e)},h.xor=Ir,h.xorBy=Hr,h.xorWith=Fr,h.zip=zr,h.zipObject=function(e,t){return nn(e||[],t||[],Ge)},h.zipObjectDeep=function(e,t){return nn(e||[],t||[],Wt)},h.zipWith=Wr,h.entries=eo,h.entriesIn=to,h.extend=Ia,h.extendWith=Ha,ko(h,h),h.add=Po,h.attempt=mo,h.camelCase=ro,h.capitalize=ao,h.ceil=No,h.clamp=function(e,t,n){return n===zo&&(n=t,t=zo),n!==zo&&(n=(n=C(n))==n?n:0),t!==zo&&(t=(t=C(t))==t?t:0),et(C(e),t,n)},h.clone=function(e){return _(e,4)},h.cloneDeep=function(e){return _(e,5)},h.cloneDeepWith=function(e,t){return _(e,5,t="function"==typeof t?t:zo)},h.cloneWith=function(e,t){return _(e,4,t="function"==typeof t?t:zo)},h.conformsTo=function(e,t){return null==t||tt(e,t,E(t))},h.deburr=oo,h.defaultTo=function(e,t){return null==e||e!=e?t:e},h.divide=jo,h.endsWith=function(e,t,n){e=f(e),t=u(t);var r=e.length,r=n=n===zo?r:et(T(n),0,r);return 0<=(n-=t.length)&&e.slice(n,r)==t},h.eq=H,h.escape=function(e){return(e=f(e))&&Vi.test(e)?e.replace(zi,Zs):e},h.escapeRegExp=function(e){return(e=f(e))&&Qi.test(e)?e.replace(Ji,"\\$&"):e},h.every=function(e,t,n){return(F(e)?Ys:it)(e,d(t=n&&p(e,t,n)?zo:t,3))},h.find=$r,h.findIndex=Lr,h.findKey=function(e,t){return Is(e,d(t,3),dt)},h.findLast=Gr,h.findLastIndex=Sr,h.findLastKey=function(e,t){return Is(e,d(t,3),pt)},h.floor=Ro,h.forEach=qr,h.forEachRight=Jr,h.forIn=function(e,t){return null==e?e:ut(e,d(t,3),D)},h.forInRight=function(e,t){return null==e?e:ct(e,d(t,3),D)},h.forOwn=function(e,t){return e&&dt(e,d(t,3))},h.forOwnRight=function(e,t){return e&&pt(e,d(t,3))},h.get=Ba,h.gt=ma,h.gte=ya,h.has=function(e,t){return null!=e&&tr(e,t,gt)},h.hasIn=Ua,h.head=Cr,h.identity=O,h.includes=function(e,t,n,r){return e=c(e)?e:no(e),n=n&&!r?T(n):0,r=e.length,n<0&&(n=x(r+n,0)),Ea(e)?n<=r&&-1=L(t=t,n=n)&&e=this.__values__.length;return{done:e,value:e?zo:this.__values__[this.__index__++]}},h.prototype.plant=function(e){for(var t,n=this;n instanceof Ie;)var r=xr(n),a=(r.__index__=0,r.__values__=zo,t?a.__wrapped__=r:t=r,r),n=n.__wrapped__;return a.__wrapped__=e,t},h.prototype.reverse=function(){var e=this.__wrapped__;return e instanceof g?(e=e,(e=(e=this.__actions__.length?new g(this):e).reverse()).__actions__.push({func:Br,args:[Or],thisArg:zo}),new y(e,this.__chain__)):this.thru(Or)},h.prototype.toJSON=h.prototype.valueOf=h.prototype.value=function(){return en(this.__wrapped__,this.__actions__)},h.prototype.first=h.prototype.head,se&&(h.prototype[se]=function(){return this}),h}();ii._=il,(O=function(){return il}.call(Y,P,Y,D))!==zo&&(D.exports=O)}.call(this)}.call(this,P(42),P(91)(e))},function(e,t,n){n.d(t,"a",function(){return o});var r=!0,a="Invariant failed";function o(e,t){if(!e){if(r)throw new Error(a);e="function"==typeof t?t():t,t=e?"".concat(a,": ").concat(e):a;throw new Error(t)}}},function(e,t,n){n.d(t,"a",function(){return s}),n.d(t,"c",function(){return p}),n.d(t,"b",function(){return S}),n.d(t,"e",function(){return i}),n.d(t,"d",function(){return L});var b=n(14);function l(e){return"/"===e.charAt(0)}function u(e,t){for(var n=t,r=n+1,a=e.length;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=S(e,t,s(),u.location);o.confirmTransitionTo(n,"REPLACE",r,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)&&edocument.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?(r[l]=a(e),n=new r,r[l]=null,n[s]=e):n=u(),void 0===t?n:o(n,t)}},function(e,t,n){var r=n(61)("keys"),a=n(47);e.exports=function(e){return r[e]||(r[e]=a(e))}},function(e,t,n){var r=n(26),a=n(25),o="__core-js_shared__",i=a[o]||(a[o]={});(e.exports=function(e,t){return i[e]||(i[e]=void 0!==t?t:{})})("versions",[]).push({version:r.version,mode:n(44)?"pure":"global",copyright:"© 2020 Denis Pushkarev (zloirock.ru)"})},function(e,t){e.exports="constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf".split(",")},function(e,t,n){var r=n(29).f,a=n(30),o=n(37)("toStringTag");e.exports=function(e,t,n){e&&!a(e=n?e:e.prototype,o)&&r(e,o,{configurable:!0,value:t})}},function(e,t,n){var r=n(56);e.exports=function(e){return Object(r(e))}},function(e,t,n){t.f=n(37)},function(e,t,n){var r=n(25),a=n(26),o=n(44),i=n(65),s=n(29).f;e.exports=function(e){var t=a.Symbol||(a.Symbol=!o&&r.Symbol||{});"_"==e.charAt(0)||e in t||s(t,e,{value:i.f(e)})}},function(e,t){t.f=Object.getOwnPropertySymbols},function(e,t,n){n=n(356)();e.exports=n;try{regeneratorRuntime=n}catch(e){"object"==typeof globalThis?globalThis.regeneratorRuntime=n:Function("r","regeneratorRuntime = r")(n)}},function(e,t){function l(e,t,n,r,a,o,i){try{var s=e[o](i),l=s.value}catch(e){return n(e)}s.done?t(l):Promise.resolve(l).then(r,a)}e.exports=function(s){return function(){var e=this,i=arguments;return new Promise(function(t,n){var r=s.apply(e,i);function a(e){l(r,t,n,a,o,"next",e)}function o(e){l(r,t,n,a,o,"throw",e)}a(void 0)})}},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){var r=n(229);e.exports=function(e,t,n){return(t=r(t))in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t,n){e.exports=n(302)},function(e,t,n){e.exports=function s(e){"use strict";var he=/^\0+/g,N=/[\0\r\f]/g,l=/: */g,u=/zoo|gra/,c=/([,: ])(transform)/g,d=/,+\s*(?![^(]*[)])/g,p=/ +\s*(?![^(]*[)])/g,me=/ *[\0] */g,h=/,\r+?/g,i=/([\t\r\n ])*\f?&/g,m=/:global\(((?:[^\(\)\[\]]*|\[.*\]|\([^\(\)]*\))*)\)/g,y=/\W+/g,ye=/@(k\w+)\s*(\S*)\s*/,ge=/::(place)/g,_e=/:(read-only)/g,g=/\s+(?=[{\];=:>])/g,_=/([[}=:>])\s+/g,v=/(\{[^{]+?);(?=\})/g,b=/\s{2,}/g,ve=/([^\(])(:+) */g,w=/[svh]\w+-[tblr]{2}/,be=/\(\s*(.*)\s*\)/g,r=/([\s\S]*?);/g,M=/-self|flex-/g,o=/[^]*?(:[rp][el]a[\w-]+)[^]*/,k=/stretch|:\s*\w+\-(?:conte|avail)/,x=/([^-])(image-set\()/,j="-webkit-",R="-moz-",A="-ms-",I=59,H=125,F=123,z=40,W=41,V=91,we=93,B=10,U=13,K=9,$=64,G=32,Me=38,q=45,L=95,J=42,Q=44,X=58,Z=39,ee=34,te=47,ne=62,re=43,ke=126,xe=0,Le=12,Se=11,Te=107,Ce=109,Ee=115,De=112,Oe=111,Ye=105,Pe=99,Ne=100,je=112,ae=1,oe=1,ie=0,se=1,le=1,Re=1,S=0,Ae=0,Ie=0,He=[],T=[],ue=0,C=null,E=-2,D=-1,Fe=0,ze=1,We=2,Ve=3,Be=0,ce=1,Ue="",de="",pe="";function Ke(e,t,n,r,a){for(var o,i,s=0,l=0,u=0,c=0,d=0,p=0,f=0,h=0,m=0,y=0,g=0,_=0,v=0,b=0,w=0,M=0,k=0,x=0,L=0,S=n.length,T=S-1,C="",E="",D="",O="",Y="",P="";w0)E=E.replace(N,"");if(E.trim().length>0){switch(f){case G:case K:case I:case U:case B:break;default:E+=n.charAt(w)}f=I}}if(1===k)switch(f){case F:case H:case I:case ee:case Z:case z:case W:case Q:k=0;case K:case U:case B:case G:break;default:for(k=0,L=w,d=f,w--,f=I;L0)++w,f=d;case F:L=S}}switch(f){case F:for(d=(E=E.trim()).charCodeAt(0),g=1,L=++w;w0)E=E.replace(N,"");switch(p=E.charCodeAt(1)){case Ne:case Ce:case Ee:case q:o=t;break;default:o=He}if(L=(D=Ke(t,o,D,p,a+1)).length,Ie>0&&0===L)L=E.length;if(ue>0)if(o=$e(He,E,x),i=fe(Ve,D,o,t,oe,ae,L,p,a,r),E=o.join(""),void 0!==i)if(0===(L=(D=i.trim()).length))p=0,D="";if(L>0)switch(p){case Ee:E=E.replace(be,Je);case Ne:case Ce:case q:D=E+"{"+D+"}";break;case Te:if(D=(E=E.replace(ye,"$1 $2"+(ce>0?Ue:"")))+"{"+D+"}",1===le||2===le&&qe("@"+D,3))D="@"+j+D+"@"+D;else D="@"+D;break;default:if(D=E+D,r===je)O+=D,D=""}else D="";break;default:D=Ke(t,$e(t,E,x),D,r,a+1)}Y+=D,_=0,k=0,b=0,M=0,x=0,v=0,E="",D="",f=n.charCodeAt(++w);break;case H:case I:if((L=(E=(M>0?E.replace(N,""):E).trim()).length)>1){if(0===b)if((d=E.charCodeAt(0))===q||d>96&&d<123)L=(E=E.replace(" ",":")).length;if(ue>0)if(void 0!==(i=fe(ze,E,t,e,oe,ae,O.length,r,a,r)))if(0===(L=(E=i.trim()).length))E="\0\0";switch(d=E.charCodeAt(0),p=E.charCodeAt(1),d){case xe:break;case $:if(p===Ye||p===Pe){P+=E+n.charAt(w);break}default:if(E.charCodeAt(L-1)===X)break;O+=Ge(E,d,p,E.charCodeAt(2))}}_=0,k=0,b=0,M=0,x=0,E="",f=n.charCodeAt(++w)}}switch(f){case U:case B:if(l+c+u+s+Ae===0)switch(y){case W:case Z:case ee:case $:case ke:case ne:case J:case re:case te:case q:case X:case Q:case I:case F:case H:break;default:if(b>0)k=1}if(l===te)l=0;else if(se+_===0&&r!==Te&&E.length>0)M=1,E+="\0";if(ue*Be>0)fe(Fe,E,t,e,oe,ae,O.length,r,a,r);ae=1,oe++;break;case I:case H:if(l+c+u+s===0){ae++;break}default:switch(ae++,C=n.charAt(w),f){case K:case G:if(c+s+l===0)switch(h){case Q:case X:case K:case G:C="";break;default:if(f!==G)C=" "}break;case xe:C="\\0";break;case Le:C="\\f";break;case Se:C="\\v";break;case Me:if(c+l+s===0&&se>0)x=1,M=1,C="\f"+C;break;case 108:if(c+l+s+ie===0&&b>0)switch(w-b){case 2:if(h===De&&n.charCodeAt(w-3)===X)ie=h;case 8:if(m===Oe)ie=m}break;case X:if(c+l+s===0)b=w;break;case Q:if(l+u+c+s===0)M=1,C+="\r";break;case ee:case Z:if(0===l)c=c===f?0:0===c?f:c;break;case V:if(c+l+u===0)s++;break;case we:if(c+l+u===0)s--;break;case W:if(c+l+s===0)u--;break;case z:if(c+l+s===0){if(0===_)switch(2*h+3*m){case 533:break;default:g=0,_=1}u++}break;case $:if(l+u+c+s+b+v===0)v=1;break;case J:case te:if(c+s+u>0)break;switch(l){case 0:switch(2*f+3*n.charCodeAt(w+1)){case 235:l=te;break;case 220:L=w,l=J}break;case J:if(f===te&&h===J&&L+2!==w){if(33===n.charCodeAt(L+2))O+=n.substring(L,w+1);C="",l=0}}}if(0===l){if(se+c+s+v===0&&r!==Te&&f!==I)switch(f){case Q:case ke:case ne:case re:case W:case z:if(0===_){switch(h){case K:case G:case B:case U:C+="\0";break;default:C="\0"+C+(f===Q?"":"\0")}M=1}else switch(f){case z:if(b+7===w&&108===h)b=0;_=++g;break;case W:if(0==(_=--g))M=1,C+="\0"}break;case K:case G:switch(h){case xe:case F:case H:case I:case Q:case Le:case K:case G:case B:case U:break;default:if(0===_)M=1,C+="\0"}}if(E+=C,f!==G&&f!==K)y=f}}m=h,h=f,w++}if(L=O.length,Ie>0)if(0===L&&0===Y.length&&0===t[0].length==false)if(r!==Ce||1===t.length&&(se>0?de:pe)===t[0])L=t.join(",").length+2;if(L>0){if(o=0===se&&r!==Te?function(e){for(var t,n,r=0,a=e.length,o=Array(a);r1)continue;if(c=s.charCodeAt(s.length-1),d=n.charCodeAt(0),t="",0!==l)switch(c){case J:case ke:case ne:case re:case G:case z:break;default:t=" "}switch(d){case Me:n=t+de;case ke:case ne:case re:case G:case W:case z:break;case V:n=t+n+de;break;case X:switch(2*n.charCodeAt(1)+3*n.charCodeAt(2)){case 530:if(Re>0){n=t+n.substring(8,u-1);break}default:if(l<1||i[l-1].length<1)n=t+de+n}break;case Q:t="";default:if(u>1&&n.indexOf(":")>0)n=t+n.replace(ve,"$1"+de+"$2");else n=t+n+de}s+=n}o[r]=s.replace(N,"").trim()}return o}(t):t,ue>0)if(void 0!==(i=fe(We,O,o,e,oe,ae,L,r,a,r))&&0===(O=i).length)return P+O+Y;if(O=o.join(",")+"{"+O+"}",le*ie!=0){if(2===le&&!qe(O,2))ie=0;switch(ie){case Oe:O=O.replace(_e,":"+R+"$1")+O;break;case De:O=O.replace(ge,"::"+j+"input-$1")+O.replace(ge,"::"+R+"$1")+O.replace(ge,":"+A+"input-$1")+O}ie=0}}return P+O+Y}function $e(e,t,n){var r=t.trim().split(h),a=r,o=r.length,i=e.length;switch(i){case 0:case 1:for(var s=0,l=0===i?"":e[0]+" ";s0&&se>0)return a.replace(m,"$1").replace(i,"$1"+pe);break;default:return e.trim()+a.replace(i,"$1"+e.trim())}default:if(n*se>0&&a.indexOf("\f")>0)return a.replace(i,(e.charCodeAt(0)===X?"":"$1")+e.trim())}return e+a}function Ge(e,t,n,r){var a,o=0,i=e+";",s=2*t+3*n+4*r;if(944===s)return function(e){var t=e.length,n=e.indexOf(":",9)+1,r=e.substring(0,n).trim(),a=e.substring(n,t-1).trim();switch(e.charCodeAt(9)*ce){case 0:break;case q:if(110!==e.charCodeAt(10))break;default:for(var o=a.split((a="",d)),i=0,n=0,t=o.length;i$&&u<90||u>96&&u<123||u===L||u===q&&s.charCodeAt(1)!==q))switch(isNaN(parseFloat(s))+(-1!==s.indexOf("("))){case 1:switch(s){case"infinite":case"alternate":case"backwards":case"running":case"normal":case"forwards":case"both":case"none":case"linear":case"ease":case"ease-in":case"ease-out":case"ease-in-out":case"paused":case"reverse":case"alternate-reverse":case"inherit":case"initial":case"unset":case"step-start":case"step-end":break;default:s+=Ue}}l[n++]=s}a+=(0===i?"":",")+l.join(" ")}}if(a=r+a+";",1===le||2===le&&qe(a,1))return j+a+a;return a}(i);else if(0===le||2===le&&!qe(i,1))return i;switch(s){case 1015:return 97===i.charCodeAt(10)?j+i+i:i;case 951:return 116===i.charCodeAt(3)?j+i+i:i;case 963:return 110===i.charCodeAt(5)?j+i+i:i;case 1009:if(100!==i.charCodeAt(4))break;case 969:case 942:return j+i+i;case 978:return j+i+R+i+i;case 1019:case 983:return j+i+R+i+A+i+i;case 883:if(i.charCodeAt(8)===q)return j+i+i;if(i.indexOf("image-set(",11)>0)return i.replace(x,"$1"+j+"$2")+i;return i;case 932:if(i.charCodeAt(4)===q)switch(i.charCodeAt(5)){case 103:return j+"box-"+i.replace("-grow","")+j+i+A+i.replace("grow","positive")+i;case 115:return j+i+A+i.replace("shrink","negative")+i;case 98:return j+i+A+i.replace("basis","preferred-size")+i}return j+i+A+i+i;case 964:return j+i+A+"flex-"+i+i;case 1023:if(99!==i.charCodeAt(8))break;return a=i.substring(i.indexOf(":",15)).replace("flex-","").replace("space-between","justify"),j+"box-pack"+a+j+i+A+"flex-pack"+a+i;case 1005:return u.test(i)?i.replace(l,":"+j)+i.replace(l,":"+R)+i:i;case 1e3:switch(o=(a=i.substring(13).trim()).indexOf("-")+1,a.charCodeAt(0)+a.charCodeAt(o)){case 226:a=i.replace(w,"tb");break;case 232:a=i.replace(w,"tb-rl");break;case 220:a=i.replace(w,"lr");break;default:return i}return j+i+A+a+i;case 1017:if(-1===i.indexOf("sticky",9))return i;case 975:switch(o=(i=e).length-10,s=(a=(33===i.charCodeAt(o)?i.substring(0,o):i).substring(e.indexOf(":",7)+1).trim()).charCodeAt(0)+(0|a.charCodeAt(7))){case 203:if(a.charCodeAt(8)<111)break;case 115:i=i.replace(a,j+a)+";"+i;break;case 207:case 102:i=i.replace(a,j+(s>102?"inline-":"")+"box")+";"+i.replace(a,j+a)+";"+i.replace(a,A+a+"box")+";"+i}return i+";";case 938:if(i.charCodeAt(5)===q)switch(i.charCodeAt(6)){case 105:return a=i.replace("-items",""),j+i+j+"box-"+a+A+"flex-"+a+i;case 115:return j+i+A+"flex-item-"+i.replace(M,"")+i;default:return j+i+A+"flex-line-pack"+i.replace("align-content","").replace(M,"")+i}break;case 973:case 989:if(i.charCodeAt(3)!==q||122===i.charCodeAt(4))break;case 931:case 953:if(true===k.test(e))if(115===(a=e.substring(e.indexOf(":")+1)).charCodeAt(0))return Ge(e.replace("stretch","fill-available"),t,n,r).replace(":fill-available",":stretch");else return i.replace(a,j+a)+i.replace(a,R+a.replace("fill-",""))+i;break;case 962:if(i=j+i+(102===i.charCodeAt(5)?A+i:"")+i,n+r===211&&105===i.charCodeAt(13)&&i.indexOf("transform",10)>0)return i.substring(0,i.indexOf(";",27)+1).replace(c,"$1"+j+"$2")+i}return i}function qe(e,t){var n=e.indexOf(1===t?":":"{"),r=e.substring(0,3!==t?n:10),a=e.substring(n+1,e.length-1);return C(2!==t?r:r.replace(o,"$1"),a,t)}function Je(e,t){var n=Ge(t,t.charCodeAt(0),t.charCodeAt(1),t.charCodeAt(2));return n!==t+";"?n.replace(r," or ($1)").substring(4):"("+t+")"}function fe(e,t,n,r,a,o,i,s,l,u){for(var c,d=0,p=t;d0)Ue=n.replace(y,r===V?"":"-");if(r=1,1===se)pe=n;else de=n;var a,o=[pe];if(ue>0)if(void 0!==(a=fe(D,t,o,o,oe,ae,0,0,0,0))&&"string"==typeof a)t=a;var i=Ke(He,o,t,0,0);if(ue>0)if(void 0!==(a=fe(E,i,o,o,oe,ae,i.length,0,0,0))&&"string"!=typeof(i=a))r=0;return Ue="",pe="",de="",ie=0,oe=1,ae=1,S*r==0?i:i.replace(N,"").replace(g,"").replace(_,"$1").replace(v,"$1").replace(b," ")}if(f.use=function e(t){switch(t){case void 0:case null:ue=T.length=0;break;default:if("function"==typeof t)T[ue++]=t;else if("object"==typeof t)for(var n=0,r=t.length;na;)!i(r,n=t[a++])||~l(o,n)||o.push(n);return o}},function(e,t,n){var r=n(86);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==r(e)?e.split(""):Object(e)}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){var r=n(84),a=n(63).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,a)}},function(e,t,n){var r=n(49),a=n(46),o=n(34),i=n(58),s=n(29),l=n(81),u=Object.getOwnPropertyDescriptor;t.f=n(25)?u:function(e,t){if(e=o(e),t=i(t,!0),l)try{return u(e,t)}catch(e){}if(s(e,t))return a(!r.f.call(e,t),e[t])}},function(e,t,n){"use strict";t.__esModule=!0,t.default=t.EXITING=t.ENTERED=t.ENTERING=t.EXITED=t.UNMOUNTED=void 0;var r=function(e){{if(e&&e.__esModule)return e;var t,n={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&((t=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,r):{}).get||t.set?Object.defineProperty(n,r,t):n[r]=e[r]);return n.default=e,n}}(n(0)),o=s(n(1)),i=s(n(10)),a=n(11);n(90);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"),p=(t.ENTERED=d,"exiting"),n=(t.EXITING=p,function(a){var e;function t(e,t){var n,r=a.call(this,e,t)||this,t=t.transitionGroup,t=t&&!t.isMounting?e.enter:e.appear;return r.appearStatus=null,e.in?t?(n=u,r.appearStatus=c):n=d:n=e.unmountOnExit||e.mountOnEnter?l:u,r.state={status:n},r.nextCallback=null,r}e=a,(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=p)),this.updateStatus(!1,t)},n.componentWillUnmount=function(){this.cancelNextCallback()},n.getTimeouts=function(){var e,t,n=this.props.timeout,r=e=t=n;return null!=n&&"number"!=typeof n&&(r=n.exit,e=n.enter,t=void 0!==n.appear?n.appear:e),{exit:r,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,r=this.props.enter,a=this.context.transitionGroup?this.context.transitionGroup.isMounting:t,o=this.getTimeouts(),i=a?o.appear:o.enter;t||r?(this.props.onEnter(e,a),this.safeSetState({status:c},function(){n.props.onEntering(e,a),n.onTransitionEnd(e,i,function(){n.safeSetState({status:d},function(){n.props.onEntered(e,a)})})})):this.safeSetState({status:d},function(){n.props.onEntered(e)})},n.performExit=function(e){var t=this,n=this.props.exit,r=this.getTimeouts();n?(this.props.onExit(e),this.safeSetState({status:p},function(){t.props.onExiting(e),t.onTransitionEnd(e,r.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,r=!0;return this.nextCallback=function(e){r&&(r=!1,n.nextCallback=null,t(e))},this.nextCallback.cancel=function(){r=!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,r={},a=Object.keys(e),o=0;oa;)!i(r,n=t[a++])||~l(o,n)||o.push(n);return o}},function(e,t,n){var r=n(85);e.exports=Object("z").propertyIsEnumerable(0)?Object:function(e){return"String"==r(e)?e.split(""):Object(e)}},function(e,t){var n={}.toString;e.exports=function(e){return n.call(e).slice(8,-1)}},function(e,t,n){var r=n(83),a=n(62).concat("length","prototype");t.f=Object.getOwnPropertyNames||function(e){return r(e,a)}},function(e,t,n){var r=n(48),a=n(45),o=n(36),i=n(57),s=n(30),l=n(80),u=Object.getOwnPropertyDescriptor;t.f=n(27)?u:function(e,t){if(e=o(e),t=i(t,!0),l)try{return u(e,t)}catch(e){}if(s(e,t))return a(!r.f.call(e,t),e[t])}},function(e,t,n){t.__esModule=!0,t.default=t.EXITING=t.ENTERED=t.ENTERING=t.EXITED=t.UNMOUNTED=void 0;var r=function(e){{if(e&&e.__esModule)return e;var t,n={};if(null!=e)for(var r in e)Object.prototype.hasOwnProperty.call(e,r)&&((t=Object.defineProperty&&Object.getOwnPropertyDescriptor?Object.getOwnPropertyDescriptor(e,r):{}).get||t.set?Object.defineProperty(n,r,t):n[r]=e[r]);return n.default=e,n}}(n(0)),o=s(n(1)),i=s(n(10)),a=n(11);n(89);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"),p=(t.ENTERED=d,"exiting"),n=(t.EXITING=p,function(a){var e;function t(e,t){var n,r=a.call(this,e,t)||this,t=t.transitionGroup,t=t&&!t.isMounting?e.enter:e.appear;return r.appearStatus=null,e.in?t?(n=u,r.appearStatus=c):n=d:n=e.unmountOnExit||e.mountOnEnter?l:u,r.state={status:n},r.nextCallback=null,r}e=a,(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=p)),this.updateStatus(!1,t)},n.componentWillUnmount=function(){this.cancelNextCallback()},n.getTimeouts=function(){var e,t,n=this.props.timeout,r=e=t=n;return null!=n&&"number"!=typeof n&&(r=n.exit,e=n.enter,t=void 0!==n.appear?n.appear:e),{exit:r,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,r=this.props.enter,a=this.context.transitionGroup?this.context.transitionGroup.isMounting:t,o=this.getTimeouts(),i=a?o.appear:o.enter;t||r?(this.props.onEnter(e,a),this.safeSetState({status:c},function(){n.props.onEntering(e,a),n.onTransitionEnd(e,i,function(){n.safeSetState({status:d},function(){n.props.onEntered(e,a)})})})):this.safeSetState({status:d},function(){n.props.onEntered(e)})},n.performExit=function(e){var t=this,n=this.props.exit,r=this.getTimeouts();n?(this.props.onExit(e),this.safeSetState({status:p},function(){t.props.onExiting(e),t.onTransitionEnd(e,r.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,r=!0;return this.nextCallback=function(e){r&&(r=!1,n.nextCallback=null,t(e))},this.nextCallback.cancel=function(){r=!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,r={},a=Object.keys(e),o=0;o=20?"ste":"de")},week:{dow:1,doy:4}})}(n(8))},function(e,t,n){!function(e){"use strict"; //! moment.js locale configuration @@ -298,7 +299,7 @@ var t;e.defineLocale("zh-hk",{months:"一月_二月_三月_四月_五月_六月_ //! moment.js locale configuration var t;e.defineLocale("zh-mo",{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:"DD/MM/YYYY",LL:"YYYY年M月D日",LLL:"YYYY年M月D日 HH:mm",LLLL:"YYYY年M月D日dddd HH:mm",l:"D/M/YYYY",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 r=e*100+t;if(r<600)return"凌晨";else if(r<900)return"早上";else if(r<1130)return"上午";else if(r<1230)return"中午";else if(r<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(8))},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 r=e*100+t;if(r<600)return"凌晨";else if(r<900)return"早上";else if(r<1130)return"上午";else if(r<1230)return"中午";else if(r<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(8))},function(e,t,n){var r=n(42).default,a=n(366);e.exports=function(e){return e=a(e,"string"),"symbol"==r(e)?e:String(e)},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){function n(){return e.exports=n=Object.assign?Object.assign.bind():function(e){for(var t=1;t68?1900:2e3)},a=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,r=h.meridiem;if(r){for(var a=1;a<=24;a+=1)if(e.indexOf(r(a,0,t))>-1){n=a>12;break}}else n=e===(t?"pm":"PM");return n},p={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,a("seconds")],ss:[t,a("seconds")],m:[t,a("minutes")],mm:[t,a("minutes")],H:[t,a("hours")],h:[t,a("hours")],HH:[t,a("hours")],hh:[t,a("hours")],D:[t,a("day")],DD:[e,a("day")],Do:[n,function(e){var t=h.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var r=1;r<=31;r+=1)t(r).replace(/\[|\]/g,"")===e&&(this.day=r)}],M:[t,a("month")],MM:[e,a("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+/,a("year")],YY:[e,function(e){this.year=r(e)}],YYYY:[/\d{4}/,a("year")],Z:o,ZZ:o};function b(e){var t,a;t=e,a=h&&h.formats;for(var u=(e=t.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(e,t,n){var r=n&&n.toUpperCase();return t||a[n]||l[n]||a[r].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 r=b(t)(e),a=r.year,o=r.month,i=r.day,s=r.hours,l=r.minutes,u=r.seconds,c=r.milliseconds,d=r.zone,p=new Date,f=i||(a||o?1:p.getDate()),h=a||p.getFullYear(),m=0;a&&!o||(m=o>0?o-1:p.getMonth());var y=s||0,g=l||0,_=u||0,v=c||0;return d?new Date(Date.UTC(h,m,f,y,g,_,v+60*d.offset*1e3)):n?new Date(Date.UTC(h,m,f,y,g,_,v)):new Date(h,m,f,y,g,_,v)}catch(e){return new Date("")}}(t,a,n),this.init(),l&&!0!==l&&(this.$L=this.locale(l).$L),s&&t!=this.format(a)&&(this.$d=new Date("")),h={}}else if(a instanceof Array)for(var u=a.length,c=1;c<=u;c+=1){r[1]=a[c-1];var d=p.apply(this,r);if(d.isValid()){this.$d=d.$d,this.$L=d.$L,this.init();break}c===u&&(this.$d=new Date(""))}else f.call(this,e)}}}()},function(e,t,n){e.exports=function(){"use strict";return function(e,t,r){r.updateLocale=function(e,t){var n=r.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 r(e,t,n,r,a){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,r)});return a?(o=e.weekStart,i.map(function(e,t){return i[(t+(o||0))%7]})):i}function a(){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"):r(t,"months")},monthsShort:function(e){return e?e.format("MMM"):r(t,"monthsShort","months",3)},firstDayOfWeek:function(){return t.$locale().weekStart||0},weekdays:function(e){return e?e.format("dddd"):r(t,"weekdays")},weekdaysMin:function(e){return e?e.format("dd"):r(t,"weekdaysMin","weekdays",2)},weekdaysShort:function(e){return e?e.format("ddd"):r(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=a();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 r(a(),"months")},n.monthsShort=function(){return r(a(),"monthsShort","months",3)},n.weekdays=function(e){return r(a(),"weekdays",null,null,e)},n.weekdaysShort=function(e){return r(a(),"weekdaysShort","weekdays",3,e)},n.weekdaysMin=function(e){return r(a(),"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 r=n.add;n.add=function(e,t){return e=Number(e),this.$utils().p(t)===s?this.add(3*e,i):r.bind(this)(e,t)};var o=n.startOf;n.startOf=function(e,t){var n=this.$utils(),r=!!n.u(t)||t;if(n.p(e)===s){var a=this.quarter()-1;return r?this.month(3*a).startOf(i).startOf("day"):this.month(3*a+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 r=this.$utils(),a=(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 r.s(t.week(),"w"===e?1:2,"0");case"W":case"WW":return r.s(t.isoWeek(),"W"===e?1:2,"0");case"k":case"kk":return r.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)(a)}}}()},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),r=i(this).endOf(s);if(n.isBefore(r))return 1}var a=i(this).startOf(l).date(t).startOf(s).subtract(1,"millisecond"),o=this.diff(a,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){"use strict";var r=n(30),m=(Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,r(n(69))),i=r(n(360)),a=r(n(70)),o=r(n(42)),b=r(n(71)),w=r(n(231)),s=r(n(232)),l=r(n(233)),y=r(n(367)),M=n(376),u={state:"",valueName:"value",trigger:"onChange",inputValues:[]},r=function(){function r(e){var t=this,n=1=11?e:e+12;else if(t==="下午"||t==="晚上")return e+12},meridiem:function(e,t,n){var r=e*100+t;if(r<600)return"凌晨";else if(r<900)return"早上";else if(r<1130)return"上午";else if(r<1230)return"中午";else if(r<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(8))},function(e,t,n){var r=n(22).default,a=n(363);e.exports=function(e){return e=a(e,"string"),"symbol"==r(e)?e:e+""},e.exports.__esModule=!0,e.exports.default=e.exports},function(e,t){function n(){return e.exports=n=Object.assign?Object.assign.bind():function(e){for(var t=1;t68?1900:2e3)},a=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,r=h.meridiem;if(r){for(var a=1;a<=24;a+=1)if(e.indexOf(r(a,0,t))>-1){n=a>12;break}}else n=e===(t?"pm":"PM");return n},p={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,a("seconds")],ss:[t,a("seconds")],m:[t,a("minutes")],mm:[t,a("minutes")],H:[t,a("hours")],h:[t,a("hours")],HH:[t,a("hours")],hh:[t,a("hours")],D:[t,a("day")],DD:[e,a("day")],Do:[n,function(e){var t=h.ordinal,n=e.match(/\d+/);if(this.day=n[0],t)for(var r=1;r<=31;r+=1)t(r).replace(/\[|\]/g,"")===e&&(this.day=r)}],M:[t,a("month")],MM:[e,a("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+/,a("year")],YY:[e,function(e){this.year=r(e)}],YYYY:[/\d{4}/,a("year")],Z:o,ZZ:o};function b(e){var t,a;t=e,a=h&&h.formats;for(var u=(e=t.replace(/(\[[^\]]+])|(LTS?|l{1,4}|L{1,4})/g,function(e,t,n){var r=n&&n.toUpperCase();return t||a[n]||l[n]||a[r].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 r=b(t)(e),a=r.year,o=r.month,i=r.day,s=r.hours,l=r.minutes,u=r.seconds,c=r.milliseconds,d=r.zone,p=new Date,f=i||(a||o?1:p.getDate()),h=a||p.getFullYear(),m=0;a&&!o||(m=o>0?o-1:p.getMonth());var y=s||0,g=l||0,_=u||0,v=c||0;return d?new Date(Date.UTC(h,m,f,y,g,_,v+60*d.offset*1e3)):n?new Date(Date.UTC(h,m,f,y,g,_,v)):new Date(h,m,f,y,g,_,v)}catch(e){return new Date("")}}(t,a,n),this.init(),l&&!0!==l&&(this.$L=this.locale(l).$L),s&&t!=this.format(a)&&(this.$d=new Date("")),h={}}else if(a instanceof Array)for(var u=a.length,c=1;c<=u;c+=1){r[1]=a[c-1];var d=p.apply(this,r);if(d.isValid()){this.$d=d.$d,this.$L=d.$L,this.init();break}c===u&&(this.$d=new Date(""))}else f.call(this,e)}}}()},function(e,t,n){e.exports=function(){"use strict";return function(e,t,r){r.updateLocale=function(e,t){var n=r.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 r(e,t,n,r,a){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,r)});return a?(o=e.weekStart,i.map(function(e,t){return i[(t+(o||0))%7]})):i}function a(){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"):r(t,"months")},monthsShort:function(e){return e?e.format("MMM"):r(t,"monthsShort","months",3)},firstDayOfWeek:function(){return t.$locale().weekStart||0},weekdays:function(e){return e?e.format("dddd"):r(t,"weekdays")},weekdaysMin:function(e){return e?e.format("dd"):r(t,"weekdaysMin","weekdays",2)},weekdaysShort:function(e){return e?e.format("ddd"):r(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=a();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 r(a(),"months")},n.monthsShort=function(){return r(a(),"monthsShort","months",3)},n.weekdays=function(e){return r(a(),"weekdays",null,null,e)},n.weekdaysShort=function(e){return r(a(),"weekdaysShort","weekdays",3,e)},n.weekdaysMin=function(e){return r(a(),"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 r=n.add;n.add=function(e,t){return e=Number(e),this.$utils().p(t)===s?this.add(3*e,i):r.bind(this)(e,t)};var o=n.startOf;n.startOf=function(e,t){var n=this.$utils(),r=!!n.u(t)||t;if(n.p(e)===s){var a=this.quarter()-1;return r?this.month(3*a).startOf(i).startOf("day"):this.month(3*a+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 r=this.$utils(),a=(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 r.s(t.week(),"w"===e?1:2,"0");case"W":case"WW":return r.s(t.isoWeek(),"W"===e?1:2,"0");case"k":case"kk":return r.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)(a)}}}()},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),r=i(this).endOf(s);if(n.isBefore(r))return 1}var a=i(this).startOf(l).date(t).startOf(s).subtract(1,"millisecond"),o=this.diff(a,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){var r=n(31),m=(Object.defineProperty(t,"__esModule",{value:!0}),t.default=void 0,r(n(68))),i=r(n(357)),a=r(n(69)),o=r(n(22)),b=r(n(70)),w=r(n(230)),s=r(n(231)),l=r(n(232)),y=r(n(364)),M=n(373),u={state:"",valueName:"value",trigger:"onChange",inputValues:[]},r=function(){function r(e){var t=this,n=1