Skip to content

Commit

Permalink
Allow users to delete AppNamespace (#4499)
Browse files Browse the repository at this point in the history
  • Loading branch information
klboke authored Aug 13, 2022
1 parent 4ca9739 commit 93e557e
Show file tree
Hide file tree
Showing 13 changed files with 348 additions and 203 deletions.
1 change: 1 addition & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Apollo 2.1.0
* [fix(#4474):'openjdk:8-jre-alpine' potentially causing wrong number of cpu cores](https://github.com/apolloconfig/apollo/pull/4475)
* [Switching spring-session serialization mode to json for compatibility with spring-security version updates]()
* [fix(#4483):Fixed overwrite JSON type configuration being empty](https://github.com/apolloconfig/apollo/pull/4486)
* [Allow users to delete AppNamespace](https://github.com/apolloconfig/apollo/pull/4499)
* [fix the deleted at timestamp issue](https://github.com/apolloconfig/apollo/pull/4493)

------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import com.ctrip.framework.apollo.common.utils.InputValidator;
import com.ctrip.framework.apollo.common.utils.RequestPrecondition;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceUsage;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.component.PermissionValidator;
Expand All @@ -38,6 +39,7 @@
import com.ctrip.framework.apollo.portal.service.RoleInitializationService;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -168,16 +170,28 @@ public ResponseEntity<Void> createNamespace(@PathVariable String appId,
}

@PreAuthorize(value = "@permissionValidator.hasDeleteNamespacePermission(#appId)")
@DeleteMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName:.+}")
public ResponseEntity<Void> deleteNamespace(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName) {
@DeleteMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/linked-namespaces/{namespaceName:.+}")
public ResponseEntity<Void> deleteLinkedNamespace(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName) {

namespaceService.deleteNamespace(appId, Env.valueOf(env), clusterName, namespaceName);

return ResponseEntity.ok().build();
}

@PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
@GetMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/linked-namespaces/{namespaceName}/usage")
public List<NamespaceUsage> findLinkedNamespaceUsage(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName) {
NamespaceUsage usage = namespaceService.getNamespaceUsageByEnv(appId, namespaceName, Env.valueOf(env), clusterName);
return Lists.newArrayList(usage);
}

@GetMapping("/apps/{appId}/namespaces/{namespaceName}/usage")
public List<NamespaceUsage> findNamespaceUsage(@PathVariable String appId, @PathVariable String namespaceName) {
return namespaceService.getNamespaceUsageByAppId(appId, namespaceName);
}

@PreAuthorize(value = "@permissionValidator.hasDeleteNamespacePermission(#appId)")
@DeleteMapping("/apps/{appId}/appnamespaces/{namespaceName:.+}")
public ResponseEntity<Void> deleteAppNamespace(@PathVariable String appId, @PathVariable String namespaceName) {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* Copyright 2022 Apollo Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package com.ctrip.framework.apollo.portal.entity.vo;

/**
* @author kl (http://kailing.pub)
* @since 2022/8/9
*/
public class NamespaceUsage {

private String namespaceName;
private String appId;
private String clusterName;
private String envName;
private int instanceCount;
private int branchInstanceCount;
private int linkedNamespaceCount;


public NamespaceUsage() {
}

public NamespaceUsage(String namespaceName, String appId, String clusterName,
String envName) {
this.namespaceName = namespaceName;
this.appId = appId;
this.clusterName = clusterName;
this.envName = envName;
}

public String getNamespaceName() {
return namespaceName;
}

public void setNamespaceName(String namespaceName) {
this.namespaceName = namespaceName;
}

public String getAppId() {
return appId;
}

public void setAppId(String appId) {
this.appId = appId;
}

public String getClusterName() {
return clusterName;
}

public void setClusterName(String clusterName) {
this.clusterName = clusterName;
}

public String getEnvName() {
return envName;
}

public void setEnvName(String envName) {
this.envName = envName;
}

public int getInstanceCount() {
return instanceCount;
}

public void setInstanceCount(int instanceCount) {
this.instanceCount = instanceCount;
}

public int getBranchInstanceCount() {
return branchInstanceCount;
}

public void setBranchInstanceCount(int branchInstanceCount) {
this.branchInstanceCount = branchInstanceCount;
}

public int getLinkedNamespaceCount() {
return linkedNamespaceCount;
}

public void setLinkedNamespaceCount(int linkedNamespaceCount) {
this.linkedNamespaceCount = linkedNamespaceCount;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,14 @@
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;

import java.util.List;
import java.util.Objects;
import java.util.Set;

@Service
public class AppNamespaceService {

Expand Down Expand Up @@ -254,4 +253,5 @@ public AppNamespace deleteAppNamespace(String appId, String namespaceName) {
public void batchDeleteByAppId(String appId, String operator) {
appNamespaceRepository.batchDeleteByAppId(appId, operator);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package com.ctrip.framework.apollo.portal.service;

import com.ctrip.framework.apollo.common.constants.GsonType;
import com.ctrip.framework.apollo.common.dto.ClusterDTO;
import com.ctrip.framework.apollo.common.dto.ItemDTO;
import com.ctrip.framework.apollo.common.dto.NamespaceDTO;
import com.ctrip.framework.apollo.common.dto.PageDTO;
Expand All @@ -28,20 +29,23 @@
import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI.NamespaceAPI;
import com.ctrip.framework.apollo.portal.component.PortalSettings;
import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
import com.ctrip.framework.apollo.portal.constant.RoleType;
import com.ctrip.framework.apollo.portal.constant.TracerEventType;
import com.ctrip.framework.apollo.portal.enricher.adapter.BaseDtoUserInfoEnrichedAdapter;
import com.ctrip.framework.apollo.portal.entity.bo.ItemBO;
import com.ctrip.framework.apollo.portal.entity.bo.NamespaceBO;
import com.ctrip.framework.apollo.portal.entity.vo.NamespaceUsage;
import com.ctrip.framework.apollo.portal.environment.Env;
import com.ctrip.framework.apollo.portal.spi.UserInfoHolder;
import com.ctrip.framework.apollo.portal.util.RoleUtils;
import com.ctrip.framework.apollo.tracer.Tracer;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.gson.Gson;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
Expand Down Expand Up @@ -80,19 +84,21 @@ public class NamespaceService {
private final NamespaceBranchService branchService;
private final RolePermissionService rolePermissionService;
private final AdditionalUserInfoEnrichService additionalUserInfoEnrichService;
private final ClusterService clusterService;

public NamespaceService(
final PortalConfig portalConfig,
final PortalSettings portalSettings,
final UserInfoHolder userInfoHolder,
final AdminServiceAPI.NamespaceAPI namespaceAPI,
final NamespaceAPI namespaceAPI,
final ItemService itemService,
final ReleaseService releaseService,
final AppNamespaceService appNamespaceService,
final InstanceService instanceService,
final @Lazy NamespaceBranchService branchService,
final RolePermissionService rolePermissionService,
final AdditionalUserInfoEnrichService additionalUserInfoEnrichService) {
final AdditionalUserInfoEnrichService additionalUserInfoEnrichService,
ClusterService clusterService) {
this.portalConfig = portalConfig;
this.portalSettings = portalSettings;
this.userInfoHolder = userInfoHolder;
Expand All @@ -104,6 +110,7 @@ public NamespaceService(
this.branchService = branchService;
this.rolePermissionService = rolePermissionService;
this.additionalUserInfoEnrichService = additionalUserInfoEnrichService;
this.clusterService = clusterService;
}


Expand All @@ -124,35 +131,46 @@ public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) {
}


@Transactional
public void deleteNamespace(String appId, Env env, String clusterName, String namespaceName) {

public List<NamespaceUsage> getNamespaceUsageByAppId(String appId, String namespaceName) {
List<Env> envs = portalSettings.getActiveEnvs();
AppNamespace appNamespace = appNamespaceService.findByAppIdAndName(appId, namespaceName);
List<NamespaceUsage> usages = new ArrayList<>();
for (Env env : envs) {
List<ClusterDTO> clusters = clusterService.findClusters(env, appId);
for (ClusterDTO cluster : clusters) {
String clusterName = cluster.getName();
NamespaceUsage usage = this.getNamespaceUsageByEnv(appId, namespaceName, env, clusterName);
if (appNamespace != null && appNamespace.isPublic()) {
int associatedNamespace = this.getPublicAppNamespaceHasAssociatedNamespace(namespaceName, env);
usage.setLinkedNamespaceCount(associatedNamespace);
}

//1. check parent namespace has not instances
if (namespaceHasInstances(appId, env, clusterName, namespaceName)) {
throw new BadRequestException(
"Can not delete namespace because namespace has active instances");
if(usage.getLinkedNamespaceCount() > 0 || usage.getBranchInstanceCount() > 0 || usage.getInstanceCount() > 0) {
usages.add(usage);
}
}
}
return usages;
}

//2. 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");
}
public NamespaceUsage getNamespaceUsageByEnv(String appId, String namespaceName, Env env, String clusterName) {
NamespaceUsage namespaceUsage = new NamespaceUsage(namespaceName, appId, clusterName, env.getName());
int instanceCount = instanceService.getInstanceCountByNamespace(appId, env, clusterName, namespaceName);
namespaceUsage.setInstanceCount(instanceCount);

//3. check public namespace has not associated namespace
if (appNamespace != null && appNamespace.isPublic() && publicAppNamespaceHasAssociatedNamespace(
namespaceName, env)) {
throw new BadRequestException(
"Can not delete public namespace which has associated namespaces");
NamespaceDTO branchNamespace = branchService.findBranchBaseInfo(appId, env, clusterName, namespaceName);
if(branchNamespace != null){
String branchClusterName = branchNamespace.getClusterName();
int branchInstanceCount = instanceService.getInstanceCountByNamespace(appId, env, branchClusterName, namespaceName);
namespaceUsage.setBranchInstanceCount(branchInstanceCount);
}
return namespaceUsage;
}

String operator = userInfoHolder.getUser().getUserId();
@Transactional
public void deleteNamespace(String appId, Env env, String clusterName, String namespaceName) {

String operator = userInfoHolder.getUser().getUserId();
namespaceAPI.deleteNamespace(env, appId, clusterName, namespaceName, operator);
}

Expand Down Expand Up @@ -236,13 +254,12 @@ public NamespaceBO loadNamespaceBO(String appId, Env env, String clusterName,
return transformNamespace2BO(env, namespace);
}

public boolean namespaceHasInstances(String appId, Env env, String clusterName,
String namespaceName) {
return instanceService.getInstanceCountByNamespace(appId, env, clusterName, namespaceName) > 0;
public boolean publicAppNamespaceHasAssociatedNamespace(String publicNamespaceName, Env env) {
return getPublicAppNamespaceHasAssociatedNamespace(publicNamespaceName, env) > 0;
}

public boolean publicAppNamespaceHasAssociatedNamespace(String publicNamespaceName, Env env) {
return namespaceAPI.countPublicAppNamespaceAssociatedNamespaces(env, publicNamespaceName) > 0;
public int getPublicAppNamespaceHasAssociatedNamespace(String publicNamespaceName, Env env) {
return namespaceAPI.countPublicAppNamespaceAssociatedNamespaces(env, publicNamespaceName);
}

public NamespaceBO findPublicNamespaceForAssociatedNamespace(Env env, String appId,
Expand Down
14 changes: 10 additions & 4 deletions apollo-portal/src/main/resources/static/i18n/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
"Common.Department": "Department",
"Common.Cluster": "Cluster",
"Common.Environment": "Environment",
"Common.GrayscaleInstance": "GrayscaleInstance",
"Common.Instance": "Instance",
"Common.Email": "Email",
"Common.AppId": "App Id",
"Common.Namespace": "Namespace",
"Common.LinkedNamespace": "LinkedNamespace",
"Common.AppName": "App Name",
"Common.AppOwner": "App Owner",
"Common.AppOwnerLong": "App Owner",
Expand All @@ -40,9 +43,12 @@
"Common.LoginExpiredTips": "Your login is expired. Please refresh the page and try again.",
"Common.Operation": "Operation",
"Common.Delete": "Delete",
"Common.ForceDelete": "Force Delete",
"Component.DeleteNamespace.Title": "Delete Namespace",
"Component.DeleteNamespace.PublicContent": "Deleting namespace will cause the instances unable to get the configuration of this namespace. Are you sure to delete it?",
"Component.DeleteNamespace.PrivateContent": "Deleting a private Namespace will cause the instances unable to get the configuration of this namespace, and the page will prompt 'missing namespace' (unless the AppNamespace is deleted by admin tool). Are you sure to delete it?",
"Component.DeleteNamespace.PublicContent": "Caution, the public namespace for all environments will be deleted! This will cause the instances unable to get the configuration of this namespace. Are you sure you want to delete it?",
"Component.DeleteNamespace.PrivateContent": "Caution, the private namespace for all environments will be deleted! This will cause the instances unable to get the configuration of this namespace. Are you sure you want to delete it?",
"Component.DeleteNamespace.LinkedContent": "Caution, all the namespaces associated with the current environment will be deleted! This will cause the instances unable to get the configuration of this namespace. Are you sure you want to delete it?",
"Component.DeleteNamespace.ForceDeleteContent": "There are instances in use for the current namespace within 24 hours, are you sure to force delete the namespace?",
"Component.GrayscalePublishRule.Title": "Edit Grayscale Rule",
"Component.GrayscalePublishRule.AppId": "Grayscale AppId",
"Component.GrayscalePublishRule.AcceptRule": "Grayscale Application Rule",
Expand Down Expand Up @@ -428,8 +434,8 @@
"Delete.ClusterNameTips": "(Please query cluster information before deletion)",
"Delete.ClusterInfo": "Cluster information",
"Delete.DeleteNamespace": "Delete AppNamespace",
"Delete.DeleteNamespaceTips": "(Note that Namespace and AppNamespace in all environments will be deleted! If you just want to delete the namespace of some environment, let the user delete it on the project page!",
"Delete.DeleteNamespaceTips2": "Currently users can delete the associated namespace and private namespace by themselves, but they can not delete the AppNamespace. Because deleting AppNamespace has very large impacts, it is only allowed to be deleted by system administrators for the time being. For public Namespace, it is necessary to ensure that no application associates the AppNamespace",
"Delete.DeleteNamespaceTips": "(Note that Namespace and AppNamespace in all environments will be deleted!",
"Delete.DeleteNamespaceTips2": "For public Namespace, it is necessary to ensure that no application associates the AppNamespace",
"Delete.AppNamespaceName": "AppNamespace name",
"Delete.AppNamespaceNameTips": "(For non-properties namespaces, please add the suffix, such as apollo.xml)",
"Delete.AppNamespaceInfo": "AppNamespace Information",
Expand Down
Loading

0 comments on commit 93e557e

Please sign in to comment.