Skip to content

Commit fea2470

Browse files
committed
Merge branch 'master' into feature/config_cache_record_stats
# Conflicts: # CHANGES.md
2 parents a2d3805 + 4bdf66a commit fea2470

File tree

12 files changed

+249
-5
lines changed

12 files changed

+249
-5
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ Apollo 2.4.0
1212
* [Fix link namespace published items show missing some items](https://github.com/apolloconfig/apollo/pull/5240)
1313
* [Feature: Add limit and whitelist for namespace count per appid+cluster](https://github.com/apolloconfig/apollo/pull/5228)
1414
* [Feature support the observe status access-key for pre-check and logging only](https://github.com/apolloconfig/apollo/pull/5236)
15+
* [Feature add limit for items count per namespace](https://github.com/apolloconfig/apollo/pull/5227)
1516
* [Feature: Add ConfigService cache record stats function](https://github.com/apolloconfig/apollo/pull/5247)
1617
------------------
1718
All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)

apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java

+12-2
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package com.ctrip.framework.apollo.adminservice.controller;
1818

1919
import com.ctrip.framework.apollo.adminservice.aop.PreAcquireNamespaceLock;
20+
import com.ctrip.framework.apollo.biz.config.BizConfig;
2021
import com.ctrip.framework.apollo.biz.entity.Commit;
2122
import com.ctrip.framework.apollo.biz.entity.Item;
2223
import com.ctrip.framework.apollo.biz.entity.Namespace;
@@ -58,13 +59,14 @@ public class ItemController {
5859
private final NamespaceService namespaceService;
5960
private final CommitService commitService;
6061
private final ReleaseService releaseService;
62+
private final BizConfig bizConfig;
6163

62-
63-
public ItemController(final ItemService itemService, final NamespaceService namespaceService, final CommitService commitService, final ReleaseService releaseService) {
64+
public ItemController(final ItemService itemService, final NamespaceService namespaceService, final CommitService commitService, final ReleaseService releaseService, final BizConfig bizConfig) {
6465
this.itemService = itemService;
6566
this.namespaceService = namespaceService;
6667
this.commitService = commitService;
6768
this.releaseService = releaseService;
69+
this.bizConfig = bizConfig;
6870
}
6971

7072
@PreAcquireNamespaceLock
@@ -78,6 +80,14 @@ public ItemDTO create(@PathVariable("appId") String appId,
7880
if (managedEntity != null) {
7981
throw BadRequestException.itemAlreadyExists(entity.getKey());
8082
}
83+
84+
if (bizConfig.isItemNumLimitEnabled()) {
85+
int itemCount = itemService.findNonEmptyItemCount(entity.getNamespaceId());
86+
if (itemCount >= bizConfig.itemNumLimit()) {
87+
throw new BadRequestException("The maximum number of items (" + bizConfig.itemNumLimit() + ") for this namespace has been reached. Current item count is " + itemCount + ".");
88+
}
89+
}
90+
8191
entity = itemService.save(entity);
8292
dto = BeanUtils.transform(ItemDTO.class, entity);
8393
commitService.createCommit(appId, clusterName, namespaceName, new ConfigChangeContentBuilder().createItem(entity).build(),

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java

+11
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ public class BizConfig extends RefreshableConfig {
4141

4242
private static final int DEFAULT_MAX_NAMESPACE_NUM = 200;
4343

44+
private static final int DEFAULT_MAX_ITEM_NUM = 1000;
45+
4446
private static final int DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL = 60; //60s
4547
private static final int DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL = 60; //60s
4648
private static final int DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL = 1; //1s
@@ -117,6 +119,15 @@ public Set<String> namespaceNumLimitWhite() {
117119
return Sets.newHashSet(getArrayProperty("namespace.num.limit.white", new String[0]));
118120
}
119121

122+
public boolean isItemNumLimitEnabled() {
123+
return getBooleanProperty("item.num.limit.enabled", false);
124+
}
125+
126+
public int itemNumLimit() {
127+
int limit = getIntProperty("item.num.limit", DEFAULT_MAX_ITEM_NUM);
128+
return checkInt(limit, 5, Integer.MAX_VALUE, DEFAULT_MAX_ITEM_NUM);
129+
}
130+
120131
public Map<Long, Integer> namespaceValueLengthLimitOverride() {
121132
String namespaceValueLengthOverrideString = getValue("namespace.value.length.limit.override");
122133
Map<Long, Integer> namespaceValueLengthOverride = Maps.newHashMap();

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java

+3
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,7 @@ public interface ItemRepository extends PagingAndSortingRepository<Item, Long> {
6464
@Query("update Item set IsDeleted = true, DeletedAt = ROUND(UNIX_TIMESTAMP(NOW(4))*1000), DataChange_LastModifiedBy = ?2 where NamespaceId = ?1 and IsDeleted = false")
6565
int deleteByNamespaceId(long namespaceId, String operator);
6666

67+
@Query("select count(*) from Item where namespaceId = :namespaceId and key <>''")
68+
int countByNamespaceIdAndFilterKeyEmpty(@Param("namespaceId") long namespaceId);
69+
6770
}

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java

+4
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,10 @@ public List<Item> findItemsModifiedAfterDate(long namespaceId, Date date) {
135135
return itemRepository.findByNamespaceIdAndDataChangeLastModifiedTimeGreaterThan(namespaceId, date);
136136
}
137137

138+
public int findNonEmptyItemCount(long namespaceId) {
139+
return itemRepository.countByNamespaceIdAndFilterKeyEmpty(namespaceId);
140+
}
141+
138142
public Page<Item> findItemsByKey(String key, Pageable pageable) {
139143
return itemRepository.findByKey(key, pageable);
140144
}

apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemSetService.java

+18-3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717
package com.ctrip.framework.apollo.biz.service;
1818

19+
import com.ctrip.framework.apollo.biz.config.BizConfig;
1920
import com.ctrip.framework.apollo.biz.entity.Audit;
2021
import com.ctrip.framework.apollo.biz.entity.Item;
2122
import com.ctrip.framework.apollo.biz.entity.Namespace;
@@ -25,6 +26,7 @@
2526
import com.ctrip.framework.apollo.common.exception.BadRequestException;
2627
import com.ctrip.framework.apollo.common.exception.NotFoundException;
2728
import com.ctrip.framework.apollo.common.utils.BeanUtils;
29+
import com.ctrip.framework.apollo.core.utils.StringUtils;
2830
import java.util.List;
2931
import org.springframework.stereotype.Service;
3032
import org.springframework.transaction.annotation.Transactional;
@@ -38,20 +40,23 @@ public class ItemSetService {
3840
private final CommitService commitService;
3941
private final ItemService itemService;
4042
private final NamespaceService namespaceService;
43+
private final BizConfig bizConfig;
4144

4245
public ItemSetService(
4346
final AuditService auditService,
4447
final CommitService commitService,
4548
final ItemService itemService,
46-
final NamespaceService namespaceService) {
49+
final NamespaceService namespaceService,
50+
final BizConfig bizConfig) {
4751
this.auditService = auditService;
4852
this.commitService = commitService;
4953
this.itemService = itemService;
5054
this.namespaceService = namespaceService;
55+
this.bizConfig = bizConfig;
5156
}
5257

5358
@Transactional
54-
public ItemChangeSets updateSet(Namespace namespace, ItemChangeSets changeSets){
59+
public ItemChangeSets updateSet(Namespace namespace, ItemChangeSets changeSets) {
5560
return updateSet(namespace.getAppId(), namespace.getClusterName(), namespace.getNamespaceName(), changeSets);
5661
}
5762

@@ -64,6 +69,16 @@ public ItemChangeSets updateSet(String appId, String clusterName,
6469
throw NotFoundException.namespaceNotFound(appId, clusterName, namespaceName);
6570
}
6671

72+
if (bizConfig.isItemNumLimitEnabled()) {
73+
int itemCount = itemService.findNonEmptyItemCount(namespace.getId());
74+
int createItemCount = (int) changeSet.getCreateItems().stream().filter(item -> !StringUtils.isEmpty(item.getKey())).count();
75+
int deleteItemCount = (int) changeSet.getDeleteItems().stream().filter(item -> !StringUtils.isEmpty(item.getKey())).count();
76+
itemCount = itemCount + createItemCount - deleteItemCount;
77+
if (itemCount > bizConfig.itemNumLimit()) {
78+
throw new BadRequestException("The maximum number of items (" + bizConfig.itemNumLimit() + ") for this namespace has been reached. Current item count is " + itemCount + ".");
79+
}
80+
}
81+
6782
String operator = changeSet.getDataChangeLastModifiedBy();
6883
ConfigChangeContentBuilder configChangeContentBuilder = new ConfigChangeContentBuilder();
6984

@@ -84,7 +99,7 @@ public ItemChangeSets updateSet(String appId, String clusterName,
8499

85100
if (configChangeContentBuilder.hasContent()) {
86101
commitService.createCommit(appId, clusterName, namespaceName, configChangeContentBuilder.build(),
87-
changeSet.getDataChangeLastModifiedBy());
102+
changeSet.getDataChangeLastModifiedBy());
88103
}
89104

90105
return changeSet;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
/*
2+
* Copyright 2024 Apollo Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
package com.ctrip.framework.apollo.biz.service;
18+
19+
import static org.mockito.Mockito.when;
20+
21+
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
22+
import com.ctrip.framework.apollo.biz.config.BizConfig;
23+
import com.ctrip.framework.apollo.biz.entity.Item;
24+
import com.ctrip.framework.apollo.biz.entity.Namespace;
25+
import com.ctrip.framework.apollo.common.dto.ItemChangeSets;
26+
import com.ctrip.framework.apollo.common.dto.ItemDTO;
27+
import com.ctrip.framework.apollo.common.exception.BadRequestException;
28+
import org.junit.Assert;
29+
import org.junit.Test;
30+
import org.springframework.beans.factory.annotation.Autowired;
31+
import org.springframework.boot.test.mock.mockito.MockBean;
32+
import org.springframework.test.context.jdbc.Sql;
33+
34+
public class ItemSetServiceTest extends AbstractIntegrationTest {
35+
36+
@MockBean
37+
private BizConfig bizConfig;
38+
39+
@Autowired
40+
private ItemService itemService;
41+
@Autowired
42+
private NamespaceService namespaceService;
43+
44+
@Autowired
45+
private ItemSetService itemSetService;
46+
47+
@Test
48+
@Sql(scripts = "/sql/itemset-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
49+
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
50+
public void testUpdateSetWithoutItemNumLimit() {
51+
52+
when(bizConfig.itemKeyLengthLimit()).thenReturn(128);
53+
when(bizConfig.itemValueLengthLimit()).thenReturn(20000);
54+
55+
when(bizConfig.isItemNumLimitEnabled()).thenReturn(false);
56+
when(bizConfig.itemNumLimit()).thenReturn(5);
57+
58+
Namespace namespace = namespaceService.findOne(1L);
59+
60+
ItemChangeSets changeSets = new ItemChangeSets();
61+
changeSets.addCreateItem(buildNormalItem(0L, namespace.getId(), "k6", "v6", "test item num limit", 6));
62+
changeSets.addCreateItem(buildNormalItem(0L, namespace.getId(), "k7", "v7", "test item num limit", 7));
63+
64+
try {
65+
itemSetService.updateSet(namespace, changeSets);
66+
} catch (Exception e) {
67+
Assert.fail();
68+
}
69+
70+
int size = itemService.findNonEmptyItemCount(namespace.getId());
71+
Assert.assertEquals(7, size);
72+
73+
}
74+
75+
@Test
76+
@Sql(scripts = "/sql/itemset-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
77+
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
78+
public void testUpdateSetWithItemNumLimit() {
79+
80+
when(bizConfig.itemKeyLengthLimit()).thenReturn(128);
81+
when(bizConfig.itemValueLengthLimit()).thenReturn(20000);
82+
83+
when(bizConfig.isItemNumLimitEnabled()).thenReturn(true);
84+
when(bizConfig.itemNumLimit()).thenReturn(5);
85+
86+
Namespace namespace = namespaceService.findOne(1L);
87+
Item item9901 = itemService.findOne(9901);
88+
Item item9902 = itemService.findOne(9902);
89+
90+
ItemChangeSets changeSets = new ItemChangeSets();
91+
changeSets.addUpdateItem(buildNormalItem(item9901.getId(), item9901.getNamespaceId(), item9901.getKey(), item9901.getValue() + " update", item9901.getComment(), item9901.getLineNum()));
92+
changeSets.addDeleteItem(buildNormalItem(item9902.getId(), item9902.getNamespaceId(), item9902.getKey(), item9902.getValue() + " update", item9902.getComment(), item9902.getLineNum()));
93+
changeSets.addCreateItem(buildNormalItem(0L, item9901.getNamespaceId(), "k6", "v6", "test item num limit", 6));
94+
changeSets.addCreateItem(buildNormalItem(0L, item9901.getNamespaceId(), "k7", "v7", "test item num limit", 7));
95+
96+
try {
97+
itemSetService.updateSet(namespace, changeSets);
98+
Assert.fail();
99+
} catch (Exception e) {
100+
Assert.assertTrue(e instanceof BadRequestException);
101+
}
102+
103+
int size = itemService.findNonEmptyItemCount(namespace.getId());
104+
Assert.assertEquals(5, size);
105+
106+
}
107+
108+
@Test
109+
@Sql(scripts = "/sql/itemset-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
110+
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
111+
public void testUpdateSetWithItemNumLimit2() {
112+
113+
when(bizConfig.itemKeyLengthLimit()).thenReturn(128);
114+
when(bizConfig.itemValueLengthLimit()).thenReturn(20000);
115+
116+
when(bizConfig.isItemNumLimitEnabled()).thenReturn(true);
117+
when(bizConfig.itemNumLimit()).thenReturn(5);
118+
119+
Namespace namespace = namespaceService.findOne(1L);
120+
Item item9901 = itemService.findOne(9901);
121+
Item item9902 = itemService.findOne(9902);
122+
123+
ItemChangeSets changeSets = new ItemChangeSets();
124+
changeSets.addUpdateItem(buildNormalItem(item9901.getId(), item9901.getNamespaceId(), item9901.getKey(), item9901.getValue() + " update", item9901.getComment(), item9901.getLineNum()));
125+
changeSets.addDeleteItem(buildNormalItem(item9902.getId(), item9902.getNamespaceId(), item9902.getKey(), item9902.getValue() + " update", item9902.getComment(), item9902.getLineNum()));
126+
changeSets.addCreateItem(buildNormalItem(0L, item9901.getNamespaceId(), "k6", "v6", "test item num limit", 6));
127+
128+
try {
129+
itemSetService.updateSet(namespace, changeSets);
130+
} catch (Exception e) {
131+
Assert.fail();
132+
}
133+
134+
int size = itemService.findNonEmptyItemCount(namespace.getId());
135+
Assert.assertEquals(5, size);
136+
137+
}
138+
139+
140+
private ItemDTO buildNormalItem(Long id, Long namespaceId, String key, String value, String comment, int lineNum) {
141+
ItemDTO item = new ItemDTO(key, value, comment, lineNum);
142+
item.setId(id);
143+
item.setNamespaceId(namespaceId);
144+
return item;
145+
}
146+
147+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
--
2+
-- Copyright 2024 Apollo Authors
3+
--
4+
-- Licensed under the Apache License, Version 2.0 (the "License");
5+
-- you may not use this file except in compliance with the License.
6+
-- You may obtain a copy of the License at
7+
--
8+
-- http://www.apache.org/licenses/LICENSE-2.0
9+
--
10+
-- Unless required by applicable law or agreed to in writing, software
11+
-- distributed under the License is distributed on an "AS IS" BASIS,
12+
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
-- See the License for the specific language governing permissions and
14+
-- limitations under the License.
15+
--
16+
17+
INSERT INTO "Namespace" (`Id`, `AppId`, `ClusterName`, `NamespaceName`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`)VALUES(1,'testApp', 'default', 'application', 0, 'apollo', 'apollo');
18+
19+
INSERT INTO "Item" (`Id`, `NamespaceId`, "Key", "Type", "Value", `Comment`, `LineNum`)
20+
VALUES
21+
(9901, 1, 'k1', 0, 'v1', '', 1),
22+
(9902, 1, 'k2', 2, 'v2', '', 2),
23+
(9903, 1, 'k3', 0, 'v3', '', 3),
24+
(9904, 1, 'k4', 0, 'v4', '', 4),
25+
(9905, 1, 'k5', 0, 'v5', '', 5);

doc/images/item-num-limit-enabled.png

31.4 KB
Loading

doc/images/item-num-limit.png

30 KB
Loading

docs/en/portal/apollo-user-guide.md

+12
Original file line numberDiff line numberDiff line change
@@ -531,6 +531,18 @@ Starting from version 2.4.0, apollo-portal provides the function of checking the
531531
![item-num-limit](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/namespace-num-limit-white.png)
532532

533533

534+
## 6.5 Limitation on the number of configuration items in a single namespace
535+
Starting from version 2.4.0, apollo-portal provides the function of limiting the number of configuration items in a single namespace. This function is disabled by default and needs to be enabled by configuring the system `item.num.limit.enabled`. At the same time, the system parameter `item.num.limit` is provided to dynamically configure the upper limit of the number of items in a single Namespace.
536+
537+
**Setting method:**
538+
1. Log in to the Apollo Configuration Center interface with a super administrator account
539+
2. Go to the `Administrator Tools - System Parameters - ConfigDB Configuration Management` page and add or modify the `item.num.limit.enabled` configuration item to true/false to enable/disable this function.
540+
541+
![item-num-limit-enabled](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/item-num-limit-enabled.png)
542+
3. Go to the `Admin Tools - System Parameters - ConfigDB Configuration Management` page and add or modify the `item.num.limit` configuration item to configure the upper limit of the number of items under a single Namespace.
543+
544+
![item-num-limit](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/item-num-limit.png)
545+
534546

535547
# VII. Best practices
536548

docs/zh/portal/apollo-user-guide.md

+16
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,22 @@ Apollo从1.6.0版本开始增加访问密钥机制,从而只有经过身份验
502502

503503

504504

505+
## 6.5 单个命名空间下的配置项数量限制
506+
从2.4.0版本开始,apollo-portal提供了限制单个命名空间下的配置项数量的功能,此功能默认关闭,需要配置系统 `item.num.limit.enabled` 开启,同时提供了系统参数`item.num.limit`来动态配置单个Namespace下的item数量上限值
507+
508+
**设置方法:**
509+
1. 用超级管理员账号登录到Apollo配置中心的界面
510+
2. 进入`管理员工具 - 系统参数 - ConfigDB 配置管理`页面新增或修改`item.num.limit.enabled`配置项为true/false 即可开启/关闭此功能
511+
512+
![item-num-limit-enabled](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/item-num-limit-enabled.png)
513+
514+
3. 进入`管理员工具 - 系统参数 - ConfigDB 配置管理`页面新增或修改`item.num.limit`配置项来配置单个Namespace下的item数量上限值
515+
516+
![item-num-limit](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/item-num-limit.png)
517+
518+
519+
520+
505521
# 七、最佳实践
506522

507523
## 7.1 安全相关

0 commit comments

Comments
 (0)