Commit d3e0fdb7 by Jason Song Committed by GitHub

Merge pull request #536 from lepdou/delete_namespace

delete namespace
parents 202363b5 a684730f
package com.ctrip.framework.apollo.adminservice.controller;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.service.AppNamespaceService;
import com.ctrip.framework.apollo.biz.service.NamespaceService;
import com.ctrip.framework.apollo.common.dto.AppNamespaceDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
......@@ -9,16 +12,22 @@ import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
public class AppNamespaceController {
@Autowired
private AppNamespaceService appNamespaceService;
@Autowired
private NamespaceService namespaceService;
@RequestMapping(value = "/apps/{appId}/appnamespaces", method = RequestMethod.POST)
public AppNamespaceDTO create(@RequestBody AppNamespaceDTO appNamespace) {
......@@ -40,4 +49,17 @@ public class AppNamespaceController {
}
@RequestMapping(value = "/appnamespaces/{publicNamespaceName}/namespaces", method = RequestMethod.GET)
public List<NamespaceDTO> findPublicAppNamespaceAllNamespaces(@PathVariable String publicNamespaceName, Pageable pageable) {
List<Namespace> namespaces = namespaceService.findPublicAppNamespaceAllNamespaces(publicNamespaceName, pageable);
return BeanUtils.batchTransform(NamespaceDTO.class, namespaces);
}
@RequestMapping(value = "/appnamespaces/{publicNamespaceName}/associated-namespaces/count", method = RequestMethod.GET)
public int countPublicAppNamespaceAssociatedNamespaces(@PathVariable String publicNamespaceName) {
return namespaceService.countPublicAppNamespaceAssociatedNamespaces(publicNamespaceName);
}
}
......@@ -2,6 +2,8 @@ package com.ctrip.framework.apollo.biz.repository;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.PagingAndSortingRepository;
......@@ -19,4 +21,9 @@ public interface NamespaceRepository extends PagingAndSortingRepository<Namespac
int batchDelete(String appId, String clusterName, String operator);
List<Namespace> findByAppIdAndNamespaceName(String appId, String namespaceName);
List<Namespace> findByNamespaceName(String namespaceName, Pageable page);
int countByNamespaceNameAndAppIdNot(String namespaceName, String appId);
}
......@@ -8,6 +8,7 @@ import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.repository.AppNamespaceRepository;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.common.exception.ServiceException;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.ConfigConsts;
......@@ -54,12 +55,13 @@ public class AppNamespaceService {
return appNamespaceRepository.findByNameInAndIsPublicTrue(namespaceNames);
}
public List<AppNamespace> findPrivateAppNamespace(String appId){
public List<AppNamespace> findPrivateAppNamespace(String appId) {
return appNamespaceRepository.findByAppIdAndIsPublic(appId, false);
}
public AppNamespace findOne(String appId, String namespaceName){
Preconditions.checkArgument(!StringUtils.isContainEmpty(appId, namespaceName), "appId or Namespace must not be null");
public AppNamespace findOne(String appId, String namespaceName) {
Preconditions
.checkArgument(!StringUtils.isContainEmpty(appId, namespaceName), "appId or Namespace must not be null");
return appNamespaceRepository.findByAppIdAndName(appId, namespaceName);
}
......@@ -90,7 +92,7 @@ public class AppNamespaceService {
}
@Transactional
public AppNamespace createAppNamespace(AppNamespace appNamespace){
public AppNamespace createAppNamespace(AppNamespace appNamespace) {
String createBy = appNamespace.getDataChangeCreatedBy();
if (!isAppNamespaceNameUnique(appNamespace.getAppId(), appNamespace.getName())) {
throw new ServiceException("appnamespace not unique");
......@@ -109,12 +111,13 @@ public class AppNamespaceService {
return appNamespace;
}
public AppNamespace update(AppNamespace appNamespace){
public AppNamespace update(AppNamespace appNamespace) {
AppNamespace managedNs = appNamespaceRepository.findByAppIdAndName(appNamespace.getAppId(), appNamespace.getName());
BeanUtils.copyEntityProperties(appNamespace, managedNs);
managedNs = appNamespaceRepository.save(managedNs);
auditService.audit(AppNamespace.class.getSimpleName(), managedNs.getId(), Audit.OP.UPDATE, managedNs.getDataChangeLastModifiedBy());
auditService.audit(AppNamespace.class.getSimpleName(), managedNs.getId(), Audit.OP.UPDATE,
managedNs.getDataChangeLastModifiedBy());
return managedNs;
}
......
......@@ -18,12 +18,14 @@ import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.util.Collections;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
......@@ -117,7 +119,46 @@ public class NamespaceService {
//and default cluster's namespace exist but never published.
//return custom cluster's namespace
return namespace;
}
public List<Namespace> findPublicAppNamespaceAllNamespaces(String namespaceName, Pageable page) {
AppNamespace publicAppNamespace = appNamespaceService.findPublicNamespaceByName(namespaceName);
if (publicAppNamespace == null) {
throw new BadRequestException(
String.format("Public appNamespace not exists. NamespaceName = %s", namespaceName));
}
List<Namespace> namespaces = namespaceRepository.findByNamespaceName(namespaceName, page);
return filterChildNamespace(namespaces);
}
private List<Namespace> filterChildNamespace(List<Namespace> namespaces) {
List<Namespace> result = new LinkedList<>();
if (CollectionUtils.isEmpty(namespaces)) {
return result;
}
for (Namespace namespace : namespaces) {
if (!isChildNamespace(namespace)) {
result.add(namespace);
}
}
return result;
}
public int countPublicAppNamespaceAssociatedNamespaces(String publicNamespaceName) {
AppNamespace publicAppNamespace = appNamespaceService.findPublicNamespaceByName(publicNamespaceName);
if (publicAppNamespace == null) {
throw new BadRequestException(
String.format("Public appNamespace not exists. NamespaceName = %s", publicNamespaceName));
}
return namespaceRepository.countByNamespaceNameAndAppIdNot(publicNamespaceName, publicAppNamespace.getAppId());
}
public List<Namespace> findNamespaces(String appId, String clusterName) {
......
......@@ -12,6 +12,7 @@ import com.ctrip.framework.apollo.biz.service.ClusterServiceTest;
import com.ctrip.framework.apollo.biz.service.InstanceServiceTest;
import com.ctrip.framework.apollo.biz.service.NamespaceBranchServiceTest;
import com.ctrip.framework.apollo.biz.service.NamespacePublishInfoTest;
import com.ctrip.framework.apollo.biz.service.NamespaceServiceIntegrationTest;
import com.ctrip.framework.apollo.biz.service.NamespaceServiceTest;
import com.ctrip.framework.apollo.biz.service.ReleaseCreationTest;
import com.ctrip.framework.apollo.biz.service.ReleaseServiceTest;
......@@ -39,8 +40,9 @@ import org.junit.runners.Suite.SuiteClasses;
NamespaceBranchServiceTest.class,
ReleaseCreationTest.class,
NamespacePublishInfoTest.class,
NamespaceServiceTest.class,
BizConfigTest.class
NamespaceServiceIntegrationTest.class,
BizConfigTest.class,
NamespaceServiceTest.class
})
public class AllTests {
......
package com.ctrip.framework.apollo.biz;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ServerConfig;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
public class MockBeanFactory {
public static Namespace mockNamespace(String appId, String clusterName, String namespaceName) {
Namespace instance = new Namespace();
instance.setAppId(appId);
instance.setClusterName(clusterName);
instance.setNamespaceName(namespaceName);
return instance;
}
public static AppNamespace mockAppNamespace(String appId, String name, boolean isPublic) {
AppNamespace instance = new AppNamespace();
instance.setAppId(appId);
instance.setName(name);
instance.setPublic(isPublic);
return instance;
}
public static ServerConfig mockServerConfig(String key, String value, String cluster) {
ServerConfig instance = new ServerConfig();
instance.setKey(key);
instance.setValue(value);
instance.setCluster(cluster);
return instance;
}
public static Release mockRelease(long releaseId, String releaseKey, String appId,
String clusterName, String groupName, String configurations) {
Release instance = new Release();
instance.setId(releaseId);
instance.setReleaseKey(releaseKey);
instance.setAppId(appId);
instance.setClusterName(clusterName);
instance.setNamespaceName(groupName);
instance.setConfigurations(configurations);
return instance;
}
}
......@@ -3,6 +3,7 @@ package com.ctrip.framework.apollo.biz.service;
import com.google.common.collect.Lists;
import com.ctrip.framework.apollo.biz.AbstractUnitTest;
import com.ctrip.framework.apollo.biz.MockBeanFactory;
import com.ctrip.framework.apollo.biz.entity.ServerConfig;
import com.ctrip.framework.apollo.biz.repository.ServerConfigRepository;
import com.ctrip.framework.apollo.core.ConfigConsts;
......@@ -45,19 +46,19 @@ public class BizDBPropertySourceTest extends AbstractUnitTest {
//cluster config
String cluster = "cluster";
configs.add(createServerConfig(clusterConfigKey, clusterConfigValue, cluster));
configs.add(MockBeanFactory.mockServerConfig(clusterConfigKey, clusterConfigValue, cluster));
String dc = "dc";
configs.add(createServerConfig(clusterConfigKey, clusterConfigValue + "dc", dc));
configs.add(createServerConfig(clusterConfigKey, clusterConfigValue + ConfigConsts.CLUSTER_NAME_DEFAULT,
configs.add(MockBeanFactory.mockServerConfig(clusterConfigKey, clusterConfigValue + "dc", dc));
configs.add(MockBeanFactory.mockServerConfig(clusterConfigKey, clusterConfigValue + ConfigConsts.CLUSTER_NAME_DEFAULT,
ConfigConsts.CLUSTER_NAME_DEFAULT));
//dc config
configs.add(createServerConfig(dcConfigKey, dcConfigValue, dc));
configs.add(createServerConfig(dcConfigKey, dcConfigValue + ConfigConsts.CLUSTER_NAME_DEFAULT,
configs.add(MockBeanFactory.mockServerConfig(dcConfigKey, dcConfigValue, dc));
configs.add(MockBeanFactory.mockServerConfig(dcConfigKey, dcConfigValue + ConfigConsts.CLUSTER_NAME_DEFAULT,
ConfigConsts.CLUSTER_NAME_DEFAULT));
//default config
configs.add(createServerConfig(defaultKey, defaultValue, ConfigConsts.CLUSTER_NAME_DEFAULT));
configs.add(MockBeanFactory.mockServerConfig(defaultKey, defaultValue, ConfigConsts.CLUSTER_NAME_DEFAULT));
System.setProperty(ConfigConsts.APOLLO_CLUSTER_KEY, cluster);
......@@ -99,14 +100,5 @@ public class BizDBPropertySourceTest extends AbstractUnitTest {
assertNull(propertySource.getProperty("noKey"));
}
private ServerConfig createServerConfig(String key, String value, String cluster) {
ServerConfig config = new ServerConfig();
config.setKey(key);
config.setValue(value);
config.setCluster(cluster);
return config;
}
}
package com.ctrip.framework.apollo.biz.service;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.biz.repository.InstanceConfigRepository;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.jdbc.Sql;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
public class NamespaceServiceIntegrationTest extends AbstractIntegrationTest {
@Autowired
private NamespaceService namespaceService;
@Autowired
private ItemService itemService;
@Autowired
private CommitService commitService;
@Autowired
private AppNamespaceService appNamespaceService;
@Autowired
private ClusterService clusterService;
@Autowired
private ReleaseService releaseService;
@Autowired
private ReleaseHistoryService releaseHistoryService;
@Autowired
private InstanceConfigRepository instanceConfigRepository;
private String testApp = "testApp";
private String testCluster = "default";
private String testChildCluster = "child-cluster";
private String testPrivateNamespace = "application";
private String testUser = "apollo";
@Test
@Sql(scripts = "/sql/namespace-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testDeleteNamespace() {
Namespace namespace = new Namespace();
namespace.setAppId(testApp);
namespace.setClusterName(testCluster);
namespace.setNamespaceName(testPrivateNamespace);
namespace.setId(1);
namespaceService.deleteNamespace(namespace, testUser);
List<Item> items = itemService.findItems(testApp, testCluster, testPrivateNamespace);
List<Commit> commits = commitService.find(testApp, testCluster, testPrivateNamespace, new PageRequest(0, 10));
AppNamespace appNamespace = appNamespaceService.findOne(testApp, testPrivateNamespace);
List<Cluster> childClusters = clusterService.findChildClusters(testApp, testCluster);
InstanceConfig instanceConfig = instanceConfigRepository.findOne(1L);
List<Release> parentNamespaceReleases = releaseService.findActiveReleases(testApp, testCluster,
testPrivateNamespace,
new PageRequest(0, 10));
List<Release> childNamespaceReleases = releaseService.findActiveReleases(testApp, testChildCluster,
testPrivateNamespace,
new PageRequest(0, 10));
Page<ReleaseHistory> releaseHistories =
releaseHistoryService
.findReleaseHistoriesByNamespace(testApp, testCluster, testPrivateNamespace, new PageRequest(0, 10));
assertEquals(0, items.size());
assertEquals(0, commits.size());
assertNotNull(appNamespace);
assertEquals(0, childClusters.size());
assertEquals(0, parentNamespaceReleases.size());
assertEquals(0, childNamespaceReleases.size());
assertTrue(!releaseHistories.hasContent());
assertNull(instanceConfig);
}
}
package com.ctrip.framework.apollo.biz.service;
import com.ctrip.framework.apollo.biz.AbstractIntegrationTest;
import com.ctrip.framework.apollo.biz.entity.Cluster;
import com.ctrip.framework.apollo.biz.entity.Commit;
import com.ctrip.framework.apollo.biz.entity.InstanceConfig;
import com.ctrip.framework.apollo.biz.entity.Item;
import com.ctrip.framework.apollo.biz.AbstractUnitTest;
import com.ctrip.framework.apollo.biz.MockBeanFactory;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.entity.ReleaseHistory;
import com.ctrip.framework.apollo.biz.repository.InstanceConfigRepository;
import com.ctrip.framework.apollo.biz.repository.NamespaceRepository;
import com.ctrip.framework.apollo.common.entity.AppNamespace;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.ConfigConsts;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.jdbc.Sql;
import org.springframework.data.domain.Pageable;
import java.util.Arrays;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when;
public class NamespaceServiceTest extends AbstractIntegrationTest {
public class NamespaceServiceTest extends AbstractUnitTest {
@Mock
private AppNamespaceService appNamespaceService;
@Mock
private NamespaceRepository namespaceRepository;
@Autowired
@Spy
@InjectMocks
private NamespaceService namespaceService;
@Autowired
private ItemService itemService;
@Autowired
private CommitService commitService;
@Autowired
private AppNamespaceService appNamespaceService;
@Autowired
private ClusterService clusterService;
@Autowired
private ReleaseService releaseService;
@Autowired
private ReleaseHistoryService releaseHistoryService;
@Autowired
private InstanceConfigRepository instanceConfigRepository;
private String testApp = "testApp";
private String testCluster = "default";
private String testChildCluster = "child-cluster";
private String testPrivateNamespace = "application";
private String testUser = "apollo";
private String testPublicAppNamespace = "publicAppNamespace";
@Test(expected = BadRequestException.class)
public void testFindPublicAppNamespaceWithWrongNamespace() {
Pageable page = new PageRequest(0, 10);
when(appNamespaceService.findPublicNamespaceByName(testPublicAppNamespace)).thenReturn(null);
namespaceService.findPublicAppNamespaceAllNamespaces(testPublicAppNamespace, page);
}
@Test
@Sql(scripts = "/sql/namespace-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD)
@Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)
public void testDeleteNamespace() {
Namespace namespace = new Namespace();
namespace.setAppId(testApp);
namespace.setClusterName(testCluster);
namespace.setNamespaceName(testPrivateNamespace);
namespace.setId(1);
namespaceService.deleteNamespace(namespace, testUser);
List<Item> items = itemService.findItems(testApp, testCluster, testPrivateNamespace);
List<Commit> commits = commitService.find(testApp, testCluster, testPrivateNamespace, new PageRequest(0, 10));
AppNamespace appNamespace = appNamespaceService.findOne(testApp, testPrivateNamespace);
List<Cluster> childClusters = clusterService.findChildClusters(testApp, testCluster);
InstanceConfig instanceConfig = instanceConfigRepository.findOne(1L);
List<Release> parentNamespaceReleases = releaseService.findActiveReleases(testApp, testCluster,
testPrivateNamespace,
new PageRequest(0, 10));
List<Release> childNamespaceReleases = releaseService.findActiveReleases(testApp, testChildCluster,
testPrivateNamespace,
new PageRequest(0, 10));
Page<ReleaseHistory> releaseHistories =
releaseHistoryService
.findReleaseHistoriesByNamespace(testApp, testCluster, testPrivateNamespace, new PageRequest(0, 10));
assertEquals(0, items.size());
assertEquals(0, commits.size());
assertNotNull(appNamespace);
assertEquals(0, childClusters.size());
assertEquals(0, parentNamespaceReleases.size());
assertEquals(0, childNamespaceReleases.size());
assertTrue(!releaseHistories.hasContent());
assertNull(instanceConfig);
public void testFindPublicAppNamespace() {
AppNamespace publicAppNamespace = MockBeanFactory.mockAppNamespace(null, testPublicAppNamespace, true);
when(appNamespaceService.findPublicNamespaceByName(testPublicAppNamespace)).thenReturn(publicAppNamespace);
Namespace firstParentNamespace =
MockBeanFactory.mockNamespace("app", ConfigConsts.CLUSTER_NAME_DEFAULT, testPublicAppNamespace);
Namespace secondParentNamespace =
MockBeanFactory.mockNamespace("app1", ConfigConsts.CLUSTER_NAME_DEFAULT, testPublicAppNamespace);
Namespace childNamespace =
MockBeanFactory.mockNamespace("app2", ConfigConsts.CLUSTER_NAME_DEFAULT, testPublicAppNamespace);
Pageable page = new PageRequest(0, 10);
when(namespaceRepository.findByNamespaceName(testPublicAppNamespace, page))
.thenReturn(Arrays.asList(firstParentNamespace, secondParentNamespace));
doReturn(false).when(namespaceService).isChildNamespace(firstParentNamespace);
doReturn(false).when(namespaceService).isChildNamespace(secondParentNamespace);
doReturn(true).when(namespaceService).isChildNamespace(childNamespace);
List<Namespace> namespaces = namespaceService.findPublicAppNamespaceAllNamespaces(testPublicAppNamespace, page);
assertEquals(2, namespaces.size());
}
}
......@@ -4,6 +4,7 @@ import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.AbstractUnitTest;
import com.ctrip.framework.apollo.biz.MockBeanFactory;
import com.ctrip.framework.apollo.biz.entity.Release;
import com.ctrip.framework.apollo.biz.repository.ReleaseRepository;
import com.ctrip.framework.apollo.common.exception.BadRequestException;
......@@ -116,9 +117,8 @@ public class ReleaseServiceTest extends AbstractUnitTest {
String someReleaseKey = "someKey";
String someValidConfiguration = "{\"apollo.bar\": \"foo\"}";
Release
someRelease =
assembleRelease(someReleaseId, someReleaseKey, someAppId, someClusterName,
Release someRelease =
MockBeanFactory.mockRelease(someReleaseId, someReleaseKey, someAppId, someClusterName,
someNamespaceName,
someValidConfiguration);
......@@ -189,18 +189,5 @@ public class ReleaseServiceTest extends AbstractUnitTest {
assertEquals(someReleases, result);
}
private Release assembleRelease(long releaseId, String releaseKey, String appId,
String clusterName,
String groupName, String configurations) {
Release release = new Release();
release.setId(releaseId);
release.setReleaseKey(releaseKey);
release.setAppId(appId);
release.setClusterName(clusterName);
release.setNamespaceName(groupName);
release.setConfigurations(configurations);
return release;
}
}
......@@ -2,6 +2,7 @@ package com.ctrip.framework.apollo.biz.utils;
import com.google.common.collect.Sets;
import com.ctrip.framework.apollo.biz.MockBeanFactory;
import com.ctrip.framework.apollo.biz.entity.Namespace;
import org.junit.Test;
......@@ -29,8 +30,8 @@ public class ReleaseKeyGeneratorTest {
String anotherAppId = "anotherAppId";
Namespace namespace = assembleNamespace(someAppId, someCluster, someNamespace);
Namespace anotherNamespace = assembleNamespace(anotherAppId, someCluster, someNamespace);
Namespace namespace = MockBeanFactory.mockNamespace(someAppId, someCluster, someNamespace);
Namespace anotherNamespace = MockBeanFactory.mockNamespace(anotherAppId, someCluster, someNamespace);
int generateTimes = 50000;
Set<String> releaseKeys = Sets.newConcurrentHashSet();
......@@ -63,11 +64,4 @@ public class ReleaseKeyGeneratorTest {
};
}
private Namespace assembleNamespace(String appId, String cluster, String namespaceName) {
Namespace namespace = new Namespace();
namespace.setAppId(appId);
namespace.setClusterName(cluster);
namespace.setNamespaceName(namespaceName);
return namespace;
}
}
......@@ -20,6 +20,7 @@ import com.ctrip.framework.apollo.core.enums.Env;
import org.springframework.boot.actuate.health.Health;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
......@@ -81,10 +82,12 @@ public class AdminServiceAPI {
NamespaceDTO.class, appId, clusterName, namespaceName);
}
public NamespaceDTO findPublicNamespaceForAssociatedNamespace(Env env, String appId, String clusterName, String namespaceName) {
public NamespaceDTO findPublicNamespaceForAssociatedNamespace(Env env, String appId, String clusterName,
String namespaceName) {
return
restTemplate.get(env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/associated-public-namespace",
NamespaceDTO.class, appId, clusterName, namespaceName);
restTemplate
.get(env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/associated-public-namespace",
NamespaceDTO.class, appId, clusterName, namespaceName);
}
public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) {
......@@ -109,6 +112,22 @@ public class AdminServiceAPI {
return restTemplate.get(env, "apps/{appId}/namespaces/publish_info", typeReference, appId).getBody();
}
public List<NamespaceDTO> getPublicAppNamespaceAllNamespaces(Env env, String publicNamespaceName,
int page, int size) {
NamespaceDTO[] namespaceDTOs =
restTemplate.get(env, "/appnamespaces/{publicNamespaceName}/namespaces?page={page}&size={size}",
NamespaceDTO[].class, publicNamespaceName, page, size);
return Arrays.asList(namespaceDTOs);
}
public int countPublicAppNamespaceAssociatedNamespaces(Env env, String publicNamesapceName) {
Integer count =
restTemplate.get(env, "/appnamespaces/{publicNamespaceName}/associated-namespaces/count", Integer.class,
publicNamesapceName);
return count == null ? 0 : count;
}
}
@Service
......@@ -230,7 +249,8 @@ public class AdminServiceAPI {
}
public ReleaseDTO createRelease(String appId, Env env, String clusterName, String namespace,
String releaseName, String releaseComment, String operator, boolean isEmergencyPublish) {
String releaseName, String releaseComment, String operator,
boolean isEmergencyPublish) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.parseMediaType(MediaType.APPLICATION_FORM_URLENCODED_VALUE + ";charset=UTF-8"));
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
......
......@@ -21,14 +21,16 @@ public class PermissionValidator {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.MODIFY_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName));
}
public boolean hasReleaseNamespacePermission(String appId, String namespaceName) {
return rolePermissionService.userHasPermission(userInfoHolder.getUser().getUserId(),
PermissionType.RELEASE_NAMESPACE,
RoleUtils.buildNamespaceTargetId(appId, namespaceName));
}
public boolean hasDeleteNamespacePermission(String appId) {
return hasAssignRolePermission(appId) || isSuperAdmin();
}
public boolean hasOperateNamespacePermission(String appId, String namespaceName){
......
......@@ -35,6 +35,7 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
......@@ -45,7 +46,7 @@ import static com.ctrip.framework.apollo.common.utils.RequestPrecondition.checkM
@RestController
public class NamespaceController {
Logger logger = LoggerFactory.getLogger(NamespaceController.class);
private static final Logger logger = LoggerFactory.getLogger(NamespaceController.class);
@Autowired
private AppService appService;
......@@ -130,11 +131,13 @@ public class NamespaceController {
return ResponseEntity.ok().build();
}
@PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
@PreAuthorize(value = "@permissionValidator.hasDeleteNamespacePermission(#appId)")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName:.+}", method = RequestMethod.DELETE)
public ResponseEntity<Void> deleteNamespace(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName) {
namespaceService.deleteNamespace(appId, Env.valueOf(env), clusterName, namespaceName);
return ResponseEntity.ok().build();
}
......@@ -183,4 +186,14 @@ public class NamespaceController {
return namespaceService.getNamespacesPublishInfo(appId);
}
@RequestMapping(value = "/envs/{env}/appnamespaces/{publicNamespaceName}/namespaces", method = RequestMethod.GET)
public List<NamespaceDTO> getPublicAppNamespaceAllNamespaces(@PathVariable String env,
@PathVariable String publicNamespaceName,
@RequestParam(name = "page", defaultValue = "0") int page,
@RequestParam(name = "size", defaultValue = "10") int size) {
return namespaceService.getPublicAppNamespaceAllNamespaces(Env.fromString(env), publicNamespaceName, page, size);
}
}
......@@ -9,7 +9,6 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.component.ItemsComparator;
import com.ctrip.framework.apollo.portal.component.PermissionValidator;
import com.ctrip.framework.apollo.portal.constant.CatEventType;
import com.ctrip.framework.apollo.portal.entity.bo.NamespaceBO;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
......@@ -37,8 +36,6 @@ public class NamespaceBranchService {
private AdminServiceAPI.NamespaceBranchAPI namespaceBranchAPI;
@Autowired
private ReleaseService releaseService;
@Autowired
private PermissionValidator permissionValidator;
@Transactional
......
package com.ctrip.framework.apollo.portal.service;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
......@@ -37,19 +38,23 @@ public class NamespaceService {
private Gson gson = new Gson();
@Autowired
private PortalConfig portalConfig;
@Autowired
private PortalSettings portalSettings;
@Autowired
private UserInfoHolder userInfoHolder;
@Autowired
private AdminServiceAPI.NamespaceAPI namespaceAPI;
@Autowired
private ItemService itemService;
@Autowired
private ReleaseService releaseService;
@Autowired
private AdminServiceAPI.NamespaceAPI namespaceAPI;
@Autowired
private AppNamespaceService appNamespaceService;
@Autowired
private PortalConfig portalConfig;
private InstanceService instanceService;
@Autowired
private PortalSettings portalSettings;
private NamespaceBranchService branchService;
public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) {
......@@ -69,9 +74,27 @@ public class NamespaceService {
@Transactional
public void deleteNamespace(String appId, Env env, String clusterName, String namespaceName) {
//1. check private namespace
AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(appId, namespaceName);
if (appNamespace != null && !appNamespace.isPublic()) {
throw new BadRequestException("private namespace can not be deleted");
throw new BadRequestException("Private namespace can not be deleted");
}
//2. check parent namespace has not instances
if (namespaceHasInstances(appId, env, clusterName, namespaceName)) {
throw new BadRequestException("Can not delete namespace because namespace has active instances");
}
//3. check child namespace has not instances
NamespaceDTO childNamespace = branchService.findBranchBaseInfo(appId, env, clusterName, namespaceName);
if (childNamespace != null &&
namespaceHasInstances(appId, env, childNamespace.getClusterName(), namespaceName)) {
throw new BadRequestException("Can not delete namespace because namespace's branch has active instances");
}
//4. check public namespace has not associated namespace
if (appNamespace != null && publicAppNamespaceHasAssociatedNamespace(namespaceName, env)) {
throw new BadRequestException("Can not delete public namespace which has associated namespaces");
}
String operator = userInfoHolder.getUser().getUserId();
......@@ -79,7 +102,6 @@ public class NamespaceService {
namespaceAPI.deleteNamespace(env, appId, clusterName, namespaceName, operator);
}
public NamespaceDTO loadNamespaceBaseInfo(String appId, Env env, String clusterName, String namespaceName) {
NamespaceDTO namespace = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName);
if (namespace == null) {
......@@ -115,6 +137,11 @@ public class NamespaceService {
return namespaceBOs;
}
public List<NamespaceDTO> getPublicAppNamespaceAllNamespaces(Env env, String publicNamespaceName, int page,
int size) {
return namespaceAPI.getPublicAppNamespaceAllNamespaces(env, publicNamespaceName, page, size);
}
public NamespaceBO loadNamespaceBO(String appId, Env env, String clusterName, String namespaceName) {
NamespaceDTO namespace = namespaceAPI.loadNamespace(appId, env, clusterName, namespaceName);
if (namespace == null) {
......@@ -123,10 +150,18 @@ public class NamespaceService {
return transformNamespace2BO(env, namespace);
}
public boolean namespaceHasInstances(String appId, Env env, String clusterName, String namespaceName) {
return instanceService.getInstanceCountByNamepsace(appId, env, clusterName, namespaceName) > 0;
}
public boolean publicAppNamespaceHasAssociatedNamespace(String publicNamespaceName, Env env) {
return namespaceAPI.countPublicAppNamespaceAssociatedNamespaces(env, publicNamespaceName) > 0;
}
public NamespaceBO findPublicNamespaceForAssociatedNamespace(Env env, String appId,
String clusterName, String namespaceName) {
NamespaceDTO namespace = namespaceAPI.findPublicNamespaceForAssociatedNamespace(env, appId, clusterName, namespaceName);
NamespaceDTO namespace =
namespaceAPI.findPublicNamespaceForAssociatedNamespace(env, appId, clusterName, namespaceName);
return transformNamespace2BO(env, namespace);
}
......@@ -135,7 +170,7 @@ public class NamespaceService {
Map<String, Map<String, Boolean>> result = Maps.newHashMap();
Set<Env> envs = portalConfig.publishTipsSupportedEnvs();
for (Env env: envs) {
for (Env env : envs) {
if (portalSettings.isEnvActive(env)) {
result.put(env.toString(), namespaceAPI.getNamespacePublishInfo(env, appId));
}
......
......@@ -231,6 +231,8 @@
<publishdenymodal env="pageContext.env"></publishdenymodal>
<deletenamespacemodal env="pageContext.env"></deletenamespacemodal>
<apolloconfirmdialog apollo-dialog-id="'deleteConfirmDialog'" apollo-title="'删除配置'"
apollo-detail="'确定要删除配置吗?'"
apollo-show-cancel-btn="true" apollo-confirm="deleteItem"></apolloconfirmdialog>
......@@ -274,6 +276,31 @@
apollo-detail="'灰度版本还没有配置任何灰度规则,请配置灰度规则'">
</apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'deleteNamespaceDenyForMasterInstanceDialog'"
apollo-title="'删除Namespace警告信息'"
apollo-detail="'发现有 <b>' + deleteNamespaceContext.namespace.instancesCount +
'</b> 个实例正在使用Namespace(' + deleteNamespaceContext.namespace.baseInfo.namespaceName +
'),删除Namespace将导致实例获取不到配置。<br>
请到 <ins>“实例列表”</ins> 确认实例信息,如已确认删除Namespace将不会导致实例异常,
请发送邮件至<ins> <a href=\'mailto:rdkjapollo@ctrip.com\'>Apollo团队(rdkjapollo@ctrip.com)</a></ins> 删除Namespace。'"
apollo-confirm="continueDeleteNamespace">
</apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'deleteNamespaceDenyForBranchInstanceDialog'"
apollo-title="'删除Namespace警告信息'"
apollo-detail="'发现有 <b>' + deleteNamespaceContext.namespace.branch.latestReleaseInstances.total
+ '</b> 个实例正在使用Namespace(' + deleteNamespaceContext.namespace.baseInfo.namespaceName +
')灰度版本的配置,删除Namespace将导致实例获取不到配置。<br>
请到 <ins>“灰度版本” => “实例列表”</ins> 确认实例信息,如已确认删除Namespace将不会导致实例异常,
请发送邮件至<ins> <a href=\'mailto:rdkjapollo@ctrip.com\'>Apollo团队(rdkjapollo@ctrip.com)</a></ins> 删除Namespace。'"
apollo-confirm="continueDeleteNamespace">
</apolloconfirmdialog>
<apolloconfirmdialog apollo-dialog-id="'deleteNamespaceDenyForPublicNamespaceDialog'"
apollo-title="'删除Namespace失败提示'"
apollo-detail="deleteNamespaceContext.detailReason">
</apolloconfirmdialog>
<div class="modal fade" id="createBranchTips" tabindex="-1" role="dialog">
<div class="modal-dialog" role="document">
......@@ -375,6 +402,7 @@
<script type="application/javascript" src="scripts/directive/gray-release-rules-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/merge-and-publish-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/publish-deny-modal-directive.js"></script>
<script type="application/javascript" src="scripts/directive/delete-namespace-modal-directive.js"></script>
<!--controller-->
<script type="application/javascript" src="scripts/controller/config/ConfigNamespaceController.js"></script>
......
......@@ -337,6 +337,36 @@ function controller($rootScope, $scope, toastr, AppUtil, EventManager, ConfigSer
}
EventManager.subscribe(EventManager.EventType.DELETE_NAMESPACE_FAILED, function (context) {
$scope.deleteNamespaceContext = context;
if (context.reason == 'master_instance') {
AppUtil.showModal('#deleteNamespaceDenyForMasterInstanceDialog');
} else if (context.reason == 'branch_instance') {
AppUtil.showModal('#deleteNamespaceDenyForBranchInstanceDialog');
} else if (context.reason == 'public_namespace') {
var otherAppAssociatedNamespaces = context.otherAppAssociatedNamespaces;
var namespaceTips = [];
otherAppAssociatedNamespaces.forEach(function (namespace) {
var appId = namespace.appId;
var clusterName = namespace.clusterName;
var url = '/config.html?#/appid=' + appId + '&env=' + $scope.pageContext.env + '&cluster='
+ clusterName;
namespaceTips.push("<a target='_blank' href=\'" + url + "\'>AppId = " + appId + ", 集群 = " + clusterName
+ ", Namespace = " + namespace.namespaceName + "</a>");
});
$scope.deleteNamespaceContext.detailReason =
"以下应用已关联此公共Namespace,必须先删除全部已关联的Namespace才能删除公共Namespace。<br>"
+ namespaceTips.join("<br>");
AppUtil.showModal('#deleteNamespaceDenyForPublicNamespaceDialog');
}
});
new Clipboard('.clipboard');
}
......
directive_module.directive('deletenamespacemodal', deleteNamespaceModalDirective);
function deleteNamespaceModalDirective($window, $q, toastr, AppUtil, EventManager,
PermissionService, UserService, NamespaceService) {
return {
restrict: 'E',
templateUrl: '../../views/component/delete-namespace-modal.html',
transclude: true,
replace: true,
scope: {
env: '='
},
link: function (scope) {
scope.doDeleteNamespace = doDeleteNamespace;
EventManager.subscribe(EventManager.EventType.PRE_DELETE_NAMESPACE, function (context) {
var toDeleteNamespace = context.namespace;
scope.toDeleteNamespace = toDeleteNamespace;
//1. check namespace is not private
if (!checkNotPrivateNamespace(toDeleteNamespace)) {
return;
}
//2. check operator has master permission
checkPermission(toDeleteNamespace).then(function () {
//3. check namespace's master branch has not instances
if (!checkMasterInstance(toDeleteNamespace)) {
return;
}
//4. check namespace's gray branch has not instances
if (!checkBranchInstance(toDeleteNamespace)) {
return;
}
if (toDeleteNamespace.isLinkedNamespace) {
showDeleteNamespaceConfirmDialog();
} else {
//5. check public namespace has not associated namespace
checkPublicNamespace(toDeleteNamespace).then(function () {
showDeleteNamespaceConfirmDialog();
});
}
})
});
function checkNotPrivateNamespace(namespace) {
if (!namespace.isPublic) {
toastr.error("不能删除私有的Namespace", "删除失败");
return false;
}
return true;
}
function checkPermission(namespace) {
var d = $q.defer();
UserService.load_user().then(function (currentUser) {
var isAppMasterUser = false;
PermissionService.get_app_role_users(namespace.baseInfo.appId)
.then(function (appRoleUsers) {
var masterUsers = [];
appRoleUsers.masterUsers.forEach(function (user) {
masterUsers.push(user.userId);
if (currentUser.userId == user.userId) {
isAppMasterUser = true;
}
});
scope.masterUsers = masterUsers;
scope.isAppMasterUser = isAppMasterUser;
if (!isAppMasterUser) {
toastr.error("您没有项目管理员权限,只有管理员才能删除Namespace,请找项目管理员 [" + scope.masterUsers.join(",")
+ "] 删除Namespace", "删除失败");
d.reject();
} else {
d.resolve();
}
});
});
return d.promise;
}
function checkMasterInstance(namespace) {
if (namespace.instancesCount > 0) {
EventManager.emit(EventManager.EventType.DELETE_NAMESPACE_FAILED, {
namespace: namespace,
reason: 'master_instance'
});
return false;
}
return true;
}
function checkBranchInstance(namespace) {
if (namespace.hasBranch && namespace.branch.latestReleaseInstances.total > 0) {
EventManager.emit(EventManager.EventType.DELETE_NAMESPACE_FAILED, {
namespace: namespace,
reason: 'branch_instance'
});
return false;
}
return true;
}
function checkPublicNamespace(namespace) {
var d = $q.defer();
var publicAppId = namespace.baseInfo.appId;
NamespaceService.getPublicAppNamespaceAllNamespaces(scope.env,
namespace.baseInfo.namespaceName,
0, 20)
.then(function (associatedNamespaces) {
var otherAppAssociatedNamespaces = [];
associatedNamespaces.forEach(function (associatedNamespace) {
if (associatedNamespace.appId != publicAppId) {
otherAppAssociatedNamespaces.push(associatedNamespace);
}
});
if (otherAppAssociatedNamespaces.length) {
EventManager.emit(EventManager.EventType.DELETE_NAMESPACE_FAILED, {
namespace: namespace,
reason: 'public_namespace',
otherAppAssociatedNamespaces: otherAppAssociatedNamespaces
});
d.reject();
} else {
d.resolve();
}
});
return d.promise;
}
function showDeleteNamespaceConfirmDialog() {
AppUtil.showModal('#deleteNamespaceModal');
}
function doDeleteNamespace() {
var toDeleteNamespace = scope.toDeleteNamespace;
NamespaceService.deleteNamespace(toDeleteNamespace.baseInfo.appId, scope.env,
toDeleteNamespace.baseInfo.clusterName,
toDeleteNamespace.baseInfo.namespaceName)
.then(function () {
toastr.success("删除成功");
setTimeout(function () {
$window.location.reload();
}, 1000);
}, function (result) {
AppUtil.showErrorMsg(result, "删除失败");
})
}
}
}
}
......@@ -213,7 +213,7 @@ directive_module.directive('apollorequiredfield', function ($compile, $window) {
});
/** 确认框 */
directive_module.directive('apolloconfirmdialog', function ($compile, $window) {
directive_module.directive('apolloconfirmdialog', function ($compile, $window, $sce) {
return {
restrict: 'E',
templateUrl: '../../views/component/confirm-dialog.html',
......@@ -225,15 +225,26 @@ directive_module.directive('apolloconfirmdialog', function ($compile, $window) {
detail: '=apolloDetail',
showCancelBtn: '=apolloShowCancelBtn',
doConfirm: '=apolloConfirm',
confirmBtnText: '=?',
cancel: '='
},
link: function (scope, element, attrs) {
scope.$watch("detail", function () {
scope.detailAsHtml = $sce.trustAsHtml(scope.detail);
});
if (!scope.confirmBtnText) {
scope.confirmBtnText = '确认';
}
scope.confirm = function () {
if (scope.doConfirm) {
scope.doConfirm();
}
}
};
}
}
......
......@@ -62,6 +62,8 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
scope.addRuleItem = addRuleItem;
scope.editRuleItem = editRuleItem;
scope.deleteNamespace = deleteNamespace;
var subscriberId = EventManager.subscribe(EventManager.EventType.UPDATE_GRAY_RELEASE_RULES,
function (context) {
useRules(context.branch);
......@@ -81,11 +83,13 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
function initNamespace(namespace, viewType) {
namespace.hasBranch = false;
namespace.currentOperateBranch = 'master';
namespace.isBranch = false;
namespace.isLinkedNamespace =
namespace.isPublic ? namespace.parentAppId != namespace.baseInfo.appId : false;
namespace.showSearchInput = false;
namespace.displayControl = {
currentOperateBranch: 'master',
showSearchInput: false
};
namespace.viewItems = namespace.items;
namespace.isPropertiesFormat = namespace.format == 'properties';
namespace.isTextEditing = false;
......@@ -336,7 +340,7 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
if (branchName != 'master') {
initRules(scope.namespace.branch);
}
scope.namespace.currentOperateBranch = branchName;
scope.namespace.displayControl.currentOperateBranch = branchName;
//save to local storage
var operateBranchStorage = JSON.parse(localStorage.getItem(operate_branch_storage_key));
......@@ -398,6 +402,7 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
}
function loadInstanceInfo(namespace) {
var size = 20;
if (namespace.isBranch) {
size = 2000;
......@@ -780,6 +785,10 @@ function directive($window, toastr, AppUtil, EventManager, PermissionService, Na
function rollback(namespace) {
EventManager.emit(EventManager.EventType.PRE_ROLLBACK_NAMESPACE, {namespace: namespace});
}
function deleteNamespace(namespace) {
EventManager.emit(EventManager.EventType.PRE_DELETE_NAMESPACE, {namespace: namespace});
}
setTimeout(function () {
scope.namespace.show = true;
......
......@@ -133,7 +133,10 @@ appService.service('EventManager', [function () {
EDIT_GRAY_RELEASE_RULES: 'edit_gray_release_rules',
UPDATE_GRAY_RELEASE_RULES: 'update_gray_release_rules',
PUBLISH_DENY: 'publish_deny',
EMERGENCY_PUBLISH: 'emergency_publish'
EMERGENCY_PUBLISH: 'emergency_publish',
PRE_DELETE_NAMESPACE: 'pre_delete_namespace',
DELETE_NAMESPACE: 'delete_namespace',
DELETE_NAMESPACE_FAILED: 'delete_namespace_failed'
}
}
......
......@@ -18,6 +18,15 @@ appService.service("NamespaceService", ['$resource', '$q', function ($resource,
getNamespacePublishInfo: {
method: 'GET',
url: '/apps/:appId/namespaces/publish_info'
},
deleteNamespace: {
method: 'DELETE',
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName'
},
getPublicAppNamespaceAllNamespaces: {
method: 'GET',
url: '/envs/:env/appnamespaces/:publicNamespaceName/namespaces',
isArray: true
}
});
......@@ -63,16 +72,53 @@ appService.service("NamespaceService", ['$resource', '$q', function ($resource,
d.resolve(result);
}, function (result) {
d.reject(result);
})
});
return d.promise;
}
function deleteNamespace(appId, env, clusterName, namespaceName) {
var d = $q.defer();
namespace_source.deleteNamespace({
appId: appId,
env: env,
clusterName: clusterName,
namespaceName: namespaceName
},
function (result) {
d.resolve(result);
},
function (result) {
d.reject(result);
});
return d.promise;
}
function getPublicAppNamespaceAllNamespaces(env, publicNamespaceName, page, size) {
var d = $q.defer();
namespace_source.getPublicAppNamespaceAllNamespaces({
env: env,
publicNamespaceName: publicNamespaceName,
page: page,
size: size
}, function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
return d.promise;
}
return {
find_public_namespaces: find_public_namespaces,
createNamespace: createNamespace,
createAppNamespace: createAppNamespace,
getNamespacePublishInfo: getNamespacePublishInfo
getNamespacePublishInfo: getNamespacePublishInfo,
deleteNamespace: deleteNamespace,
getPublicAppNamespaceAllNamespaces: getPublicAppNamespaceAllNamespaces
}
}]);
appService.service('UserService', ['$resource', '$q', function ($resource, $q) {
var user_resource = $resource('', {}, {
load_user:{
load_user: {
method: 'GET',
url:'/user'
url: '/user'
},
find_users: {
method: 'GET',
......@@ -11,26 +11,30 @@ appService.service('UserService', ['$resource', '$q', function ($resource, $q) {
});
return {
load_user: function () {
var finished = false;
var d = $q.defer();
user_resource.load_user({
},
user_resource.load_user({},
function (result) {
finished = true;
d.resolve(result);
}, function (result) {
d.reject(result);
});
},
function (result) {
finished = true;
d.reject(result);
});
return d.promise;
},
find_users: function (keyword) {
var d = $q.defer();
user_resource.find_users({
keyword: keyword
},
function (result) {
d.resolve(result);
}, function (result) {
d.reject(result);
});
keyword: keyword
},
function (result) {
d.resolve(result);
},
function (result) {
d.reject(result);
});
return d.promise;
}
}
......
......@@ -790,3 +790,7 @@ table th {
background: #fff;
}
.dropdown:hover .dropdown-menu {
display: block;
margin-top: 0;
}
......@@ -6,15 +6,14 @@
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">{{title}}</h4>
</div>
<div class="modal-body">
{{detail}}
<div class="modal-body" ng-bind-html="detailAsHtml">
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"
ng-show="showCancelBtn" ng-click="cancel()">取消</button>
<button type="button" class="btn btn-danger" data-dismiss="modal"
ng-click="confirm()">
确定
{{confirmBtnText}}
</button>
</div>
</div>
......
<div id="deleteNamespaceModal" class="modal fade">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header panel-primary">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span
aria-hidden="true">&times;</span></button>
<h4 class="modal-title">
删除Namespace
</h4>
</div>
<div class="modal-body form-horizontal">
删除Namespace将导致实例获取不到此Namespace的配置,确定要删除吗?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal">
取消
</button>
<button type="button" class="btn btn-danger" data-dismiss="modal"
ng-click="doDeleteNamespace()">
确认
</button>
</div>
</div>
</div>
</div>
<section class="branch-panel-body" ng-if="namespace.hasBranch && namespace.currentOperateBranch != 'master'">
<section class="branch-panel-body" ng-if="namespace.hasBranch && namespace.displayControl.currentOperateBranch != 'master'">
<!--main header-->
<header class="panel-heading">
......
......@@ -21,14 +21,14 @@
<div class="col-md-8 pull-left">
<ul class="nav nav-tabs">
<li role="presentation">
<a ng-class="{'node_active': namespace.currentOperateBranch == 'master'}"
<a ng-class="{'node_active': namespace.displayControl.currentOperateBranch == 'master'}"
ng-click="switchBranch('master')">
<img src="img/branch.png">
主版本
</a>
</li>
<li role="presentation">
<a ng-class="{'node_active': namespace.currentOperateBranch != 'master'}"
<a ng-class="{'node_active': namespace.displayControl.currentOperateBranch != 'master'}"
ng-click="switchBranch(namespace.branchName)">
<img src="img/branch.png">
灰度版本
......
......@@ -6,6 +6,7 @@ import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.ConfigConsts;
import com.ctrip.framework.apollo.core.enums.ConfigFileFormat;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.portal.AbstractUnitTest;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.entity.bo.UserInfo;
......@@ -17,10 +18,8 @@ import com.ctrip.framework.apollo.portal.entity.vo.NamespaceIdentifier;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.test.util.ReflectionTestUtils;
import java.util.Arrays;
......@@ -29,8 +28,7 @@ import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.when;
@RunWith(MockitoJUnitRunner.class)
public class ConfigServiceTest {
public class ConfigServiceTest extends AbstractUnitTest {
@Mock
private AdminServiceAPI.NamespaceAPI namespaceAPI;
......@@ -77,7 +75,7 @@ public class ConfigServiceTest {
try {
configService.updateConfigItemByText(model);
}catch (Exception e){
} catch (Exception e) {
Assert.fail();
}
}
......@@ -94,13 +92,15 @@ public class ConfigServiceTest {
}
@Test
public void testCompareTargetNamespaceHasNoItems(){
ItemDTO sourceItem1 = new ItemDTO("a","b","comment",1);
public void testCompareTargetNamespaceHasNoItems() {
ItemDTO sourceItem1 = new ItemDTO("a", "b", "comment", 1);
List<ItemDTO> sourceItems = Arrays.asList(sourceItem1);
String appId = "6666", env = "LOCAL", clusterName = ConfigConsts.CLUSTER_NAME_DEFAULT,
namespaceName = ConfigConsts.NAMESPACE_APPLICATION;
List<NamespaceIdentifier> namespaceIdentifiers = generateNamespaceIdentifer(appId, env, clusterName, namespaceName);
List<NamespaceIdentifier>
namespaceIdentifiers =
generateNamespaceIdentifier(appId, env, clusterName, namespaceName);
NamespaceDTO namespaceDTO = generateNamespaceDTO(appId, clusterName, namespaceName);
when(namespaceAPI.loadNamespace(appId, Env.valueOf(env), clusterName, namespaceName)).thenReturn(namespaceDTO);
......@@ -112,7 +112,7 @@ public class ConfigServiceTest {
List<ItemDiffs> itemDiffses = configService.compare(namespaceIdentifiers, sourceItems);
assertEquals(1,itemDiffses.size());
assertEquals(1, itemDiffses.size());
ItemDiffs itemDiffs = itemDiffses.get(0);
ItemChangeSets changeSets = itemDiffs.getDiffs();
assertEquals(0, changeSets.getUpdateItems().size());
......@@ -127,21 +127,23 @@ public class ConfigServiceTest {
}
@Test
public void testCompare(){
ItemDTO sourceItem1 = new ItemDTO("a","b","comment",1);//not modified
ItemDTO sourceItem2 = new ItemDTO("newKey","c","comment",2);//new item
ItemDTO sourceItem3 = new ItemDTO("c","newValue","comment",3);// update value
ItemDTO sourceItem4 = new ItemDTO("d","b","newComment",4);// update comment
public void testCompare() {
ItemDTO sourceItem1 = new ItemDTO("a", "b", "comment", 1);//not modified
ItemDTO sourceItem2 = new ItemDTO("newKey", "c", "comment", 2);//new item
ItemDTO sourceItem3 = new ItemDTO("c", "newValue", "comment", 3);// update value
ItemDTO sourceItem4 = new ItemDTO("d", "b", "newComment", 4);// update comment
List<ItemDTO> sourceItems = Arrays.asList(sourceItem1, sourceItem2, sourceItem3, sourceItem4);
ItemDTO targetItem1 = new ItemDTO("a","b","comment",1);
ItemDTO targetItem2 = new ItemDTO("c","oldValue","comment",2);
ItemDTO targetItem3 = new ItemDTO("d","b","oldComment",3);
ItemDTO targetItem1 = new ItemDTO("a", "b", "comment", 1);
ItemDTO targetItem2 = new ItemDTO("c", "oldValue", "comment", 2);
ItemDTO targetItem3 = new ItemDTO("d", "b", "oldComment", 3);
List<ItemDTO> targetItems = Arrays.asList(targetItem1, targetItem2, targetItem3);
String appId = "6666", env = "LOCAL", clusterName = ConfigConsts.CLUSTER_NAME_DEFAULT,
namespaceName = ConfigConsts.NAMESPACE_APPLICATION;
List<NamespaceIdentifier> namespaceIdentifiers = generateNamespaceIdentifer(appId, env, clusterName, namespaceName);
List<NamespaceIdentifier>
namespaceIdentifiers =
generateNamespaceIdentifier(appId, env, clusterName, namespaceName);
NamespaceDTO namespaceDTO = generateNamespaceDTO(appId, clusterName, namespaceName);
when(namespaceAPI.loadNamespace(appId, Env.valueOf(env), clusterName, namespaceName)).thenReturn(namespaceDTO);
......@@ -189,7 +191,7 @@ public class ConfigServiceTest {
}
private NamespaceDTO generateNamespaceDTO(String appId, String clusterName, String namespaceName){
private NamespaceDTO generateNamespaceDTO(String appId, String clusterName, String namespaceName) {
NamespaceDTO namespaceDTO = new NamespaceDTO();
namespaceDTO.setAppId(appId);
namespaceDTO.setId(1);
......@@ -198,7 +200,8 @@ public class ConfigServiceTest {
return namespaceDTO;
}
private List<NamespaceIdentifier> generateNamespaceIdentifer(String appId, String env, String clusterName, String namespaceName){
private List<NamespaceIdentifier> generateNamespaceIdentifier(String appId, String env, String clusterName,
String namespaceName) {
NamespaceIdentifier targetNamespace = new NamespaceIdentifier();
targetNamespace.setAppId(appId);
targetNamespace.setEnv(env);
......
......@@ -243,7 +243,8 @@ CREATE TABLE `Namespace` (
`DataChange_LastTime` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
PRIMARY KEY (`Id`),
KEY `AppId_ClusterName_NamespaceName` (`AppId`(191),`ClusterName`(191),`NamespaceName`(191)),
KEY `DataChange_LastTime` (`DataChange_LastTime`)
KEY `DataChange_LastTime` (`DataChange_LastTime`),
KEY `IX_NamespaceName` (`NamespaceName`(191))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='命名空间';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment