Commit c5e0ca63 by Jason Song

Merge pull request #227 from lepdou/optimization

发布时可以查看变更集 & 自定义集群选择标签可以设置默认选择 & 获取adminservice地址增加重试和缓存机制
parents 91e56acf 4562308d
......@@ -25,6 +25,6 @@ public class API {
}
public String getAdminServiceHost(Env env) {
return serviceLocator.getAdminService(env).getHomepageUrl();
return serviceLocator.getServiceAddress(env).getHomepageUrl();
}
}
......@@ -30,7 +30,7 @@ public class PortalConfigController {
@Autowired
private PortalConfigService configService;
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.PUT, consumes = {
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items", method = RequestMethod.PUT, consumes = {
"application/json"})
public void modifyItems(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
......@@ -51,7 +51,7 @@ public class PortalConfigController {
configService.updateConfigItemByText(model);
}
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item", method = RequestMethod.POST)
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item", method = RequestMethod.POST)
public ItemDTO createItem(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@RequestBody ItemDTO item){
......@@ -64,7 +64,7 @@ public class PortalConfigController {
return configService.createOrUpdateItem(appId, Env.valueOf(env), clusterName, namespaceName, item);
}
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item", method = RequestMethod.PUT)
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/item", method = RequestMethod.PUT)
public ItemDTO updateItem(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName,
@RequestBody ItemDTO item){
......@@ -85,7 +85,7 @@ public class PortalConfigController {
configService.deleteItem(Env.valueOf(env), itemId);
}
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/release", method = RequestMethod.POST, consumes = {
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/release", method = RequestMethod.POST, consumes = {
"application/json"})
public ReleaseDTO createRelease(@PathVariable String appId,
@PathVariable String env, @PathVariable String clusterName,
......@@ -106,7 +106,7 @@ public class PortalConfigController {
}
@RequestMapping(value = "/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items")
@RequestMapping(value = "/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces/{namespaceName}/items")
public List<ItemDTO> findItems(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName, @PathVariable String namespaceName){
......
......@@ -45,7 +45,7 @@ public class PortalNamespaceController {
namespaceService.createAppNamespace(appNamespace);
}
@RequestMapping("/apps/{appId}/env/{env}/clusters/{clusterName}/namespaces")
@RequestMapping("/apps/{appId}/envs/{env}/clusters/{clusterName}/namespaces")
public List<NamespaceVO> findNamespaces(@PathVariable String appId, @PathVariable String env,
@PathVariable String clusterName) {
if (StringUtils.isContainEmpty(appId, env, clusterName)) {
......
......@@ -16,7 +16,7 @@ public class NamespaceTextModel implements Verifiable {
@Override
public boolean isInvalid(){
return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName, configText) || namespaceId <= 0;
return StringUtils.isContainEmpty(appId, env, clusterName, namespaceName) || namespaceId <= 0;
}
public String getAppId() {
return appId;
......
......@@ -8,6 +8,7 @@ import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.dto.NamespaceDTO;
import com.ctrip.framework.apollo.core.dto.ReleaseDTO;
import com.ctrip.framework.apollo.core.enums.Env;
import com.ctrip.framework.apollo.core.exception.NotFoundException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.portal.PortalSettings;
import com.ctrip.framework.apollo.portal.api.AdminServiceAPI;
......@@ -47,11 +48,11 @@ public class PortalNamespaceService {
private Gson gson = new Gson();
public List<AppNamespaceDTO> findPublicAppNamespaces(){
public List<AppNamespaceDTO> findPublicAppNamespaces() {
return namespaceAPI.findPublicAppNamespaces(portalSettings.getFirstAliveEnv());
}
public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace){
public NamespaceDTO createNamespace(Env env, NamespaceDTO namespace) {
return namespaceAPI.createNamespace(env, namespace);
}
......
package com.ctrip.framework.apollo.portal.service;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
......@@ -33,57 +31,21 @@ public class ServiceLocator {
private static final int DEFAULT_TIMEOUT_MS = 1000;
private static final int RETRY_TIMES = 3;
private static final int CALL_META_SERVER_THRESHOLD = 10;
private static final String ADMIN_SERVICE_URL_PATH = "/services/admin";
private RestTemplate restTemplate;
@Autowired
private HttpMessageConverters httpMessageConverters;
private Map<Env, List<ServiceDTO>> serviceCaches = new ConcurrentHashMap<Env, List<ServiceDTO>>();
private Map<Env, ServiceDTO[]> serviceAddressCache = new ConcurrentHashMap<>();
private final AtomicInteger adminCallCounts = new AtomicInteger(0);
private final AtomicInteger configCallCounts = new AtomicInteger(0);
public ServiceDTO getAdminService(Env env) throws ServiceException {
List<ServiceDTO> services = getServices(env, "admin");
if (services == null || services.size() == 0) {
throw new ServiceException("No available admin service");
}
return services.get(Math.abs(adminCallCounts.getAndIncrement()) % services.size());
}
public ServiceDTO getConfigService(Env env) throws ServiceException {
List<ServiceDTO> services = getServices(env, "config");
if (services == null || services.size() == 0) {
throw new ServiceException("No available config service");
}
return services.get(Math.abs(configCallCounts.getAndIncrement()) % services.size());
}
private List<ServiceDTO> getServices(Env env, String serviceUrl) {
String domainName = MetaDomainConsts.getDomain(env);
String url = domainName + "/services/" + serviceUrl;
List<ServiceDTO> serviceDtos = null;
try {
ServiceDTO[] services = restTemplate.getForObject(new URI(url), ServiceDTO[].class);
if (services != null && services.length > 0) {
if (!serviceCaches.containsKey(env)) {
serviceDtos = new ArrayList<ServiceDTO>();
serviceCaches.put(env, serviceDtos);
} else {
serviceDtos = serviceCaches.get(env);
serviceDtos.clear();
}
for (ServiceDTO service : services) {
serviceDtos.add(service);
}
}
} catch (Exception ex) {
logger.warn(ex.getMessage());
}
return serviceDtos;
}
@PostConstruct
private void postConstruct() {
restTemplate = new RestTemplate(httpMessageConverters.getConverters());
......@@ -99,4 +61,55 @@ public class ServiceLocator {
rf.setConnectTimeout(DEFAULT_TIMEOUT_MS);
}
}
public ServiceDTO getServiceAddress(Env env) throws ServiceException {
if (adminCallCounts.get() % CALL_META_SERVER_THRESHOLD == 0) {
return getServiceAddressFromMetaServer(env);
} else {
//if cached then return from cache
ServiceDTO[] serviceDTOs = serviceAddressCache.get(env);
if (serviceDTOs != null && serviceDTOs.length > 0){
return randomServiceAddress(serviceDTOs);
}else {//return from meta server
return getServiceAddressFromMetaServer(env);
}
}
}
public ServiceDTO getServiceAddressFromMetaServer(Env env) {
//retry
for (int i = 0; i < RETRY_TIMES; i++) {
ServiceDTO[] services = getServices(env);
if (services != null && services.length > 0) {
serviceAddressCache.put(env, services);
return randomServiceAddress(services);
} else {
logger.warn(String.format("can not get %s admin service address at %d time", env, i));
}
}
logger.error(String.format("can not get %s admin service address", env));
throw new ServiceException("No available admin service");
}
private ServiceDTO[] getServices(Env env) {
String domainName = MetaDomainConsts.getDomain(env);
String url = domainName + ADMIN_SERVICE_URL_PATH;
try {
return restTemplate.getForObject(new URI(url), ServiceDTO[].class);
} catch (Exception ex) {
logger.warn(ex.getMessage());
return null;
}
}
private ServiceDTO randomServiceAddress(ServiceDTO[] services){
return services[Math.abs(adminCallCounts.getAndIncrement()) % services.length];
}
}
......@@ -3,7 +3,6 @@ package com.ctrip.framework.apollo.portal.service.txtresolver;
import com.ctrip.framework.apollo.core.dto.ItemChangeSets;
import com.ctrip.framework.apollo.core.dto.ItemDTO;
import com.ctrip.framework.apollo.core.exception.BadRequestException;
import com.ctrip.framework.apollo.core.utils.StringUtils;
import com.ctrip.framework.apollo.common.utils.BeanUtils;
import org.springframework.stereotype.Component;
......
......@@ -73,7 +73,7 @@
<script src="vendor/angular/loading-bar.min.js"></script>
<!-- jquery.js -->
<script src="vendor/jquery.js" type="text/javascript"></script>
<script src="vendor/jquery.min.js" type="text/javascript"></script>
<!-- bootstrap.js -->
<script src="vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
......
......@@ -46,7 +46,7 @@
<div class="form-group">
<label class="col-sm-2 control-label">同步到那个集群</label>
<div class="col-sm-6">
<apolloclusterselector apollo-app-id="pageContext.appId" apollo-default-checked="true"
<apolloclusterselector apollo-app-id="pageContext.appId" apollo-default-all-checked="true"
apollo-select="collectSelectedClusters"></apolloclusterselector>
</div>
</div>
......@@ -177,7 +177,7 @@
<script src="../vendor/angular/loading-bar.min.js"></script>
<!-- jquery.js -->
<script src="../vendor/jquery.js" type="text/javascript"></script>
<script src="vendor/jquery.min.js" type="text/javascript"></script>
<!-- bootstrap.js -->
<script src="../vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
......
......@@ -71,7 +71,7 @@
<script src="vendor/angular/loading-bar.min.js"></script>
<!-- jquery.js -->
<script src="vendor/jquery.js" type="text/javascript"></script>
<script src="vendor/jquery.min.js" type="text/javascript"></script>
<!-- bootstrap.js -->
<script src="vendor/bootstrap/js/bootstrap.min.js" type="text/javascript"></script>
......
......@@ -41,7 +41,7 @@
<div class="form-group">
<label class="col-sm-3 control-label"><font style="color: red">*</font>选择集群</label>
<div class="col-sm-6">
<apolloclusterselector apollo-app-id="appId" apollo-default-checked="true"
<apolloclusterselector apollo-app-id="appId" apollo-default-all-checked="true"
apollo-select="collectSelectedClusters"></apolloclusterselector>
</div>
</div>
......@@ -94,7 +94,7 @@
<script src="vendor/angular/loading-bar.min.js"></script>
<!-- jquery.js -->
<script src="vendor/jquery.js" type="text/javascript"></script>
<script src="vendor/jquery.min.js" type="text/javascript"></script>
<script src="vendor/select2/select2.min.js" type="text/javascript"></script>
......
......@@ -26,12 +26,12 @@ appUtil.service('AppUtil', ['toastr', function (toastr) {
collectData: function (response) {
var data = [];
response.entities.forEach(function (entity) {
if (entity.code == 200){
if (entity.code == 200) {
data.push(entity.body);
}else {
} else {
toastr.warning(entity.message);
}
});
});
return data;
}
}
......
......@@ -14,3 +14,28 @@ $(document).ready(function () {
$(function () {
$('[data-toggle="tooltip"]').tooltip()
});
// (new Date()).Format("yyyy-MM-dd hh:mm:ss.S") ==> 2006-07-02 08:09:04.423
// (new Date()).Format("yyyy-M-d h:m:s.S") ==> 2006-7-2 8:9:4.18
Date.prototype.Format = function (fmt) {
var o = {
"M+": this.getMonth() + 1, //月份
"d+": this.getDate(), //日
"h+": this.getHours(), //小时
"m+": this.getMinutes(), //分
"s+": this.getSeconds(), //秒
"q+": Math.floor((this.getMonth() + 3) / 3), //季度
"S": this.getMilliseconds() //毫秒
};
if (/(y+)/.test(fmt)) {
fmt = fmt.replace(RegExp.$1, (this.getFullYear() + "").substr(4 - RegExp.$1.length));
}
for (var k in o) {
if (new RegExp("(" + k + ")").test(fmt)) {
fmt =
fmt.replace(RegExp.$1,
(RegExp.$1.length == 1) ? (o[k]) : (("00" + o[k]).substr(("" + o[k]).length)));
}
}
return fmt;
};
......@@ -2,8 +2,6 @@ application_module.controller("ConfigNamespaceController",
['$rootScope', '$scope', '$location', 'toastr', 'AppUtil', 'ConfigService',
function ($rootScope, $scope, $location, toastr, AppUtil, ConfigService) {
////// namespace //////
var namespace_view_type = {
TEXT: 'text',
TABLE: 'table',
......@@ -53,8 +51,6 @@ application_module.controller("ConfigNamespaceController",
});
};
////// global view oper //////
$scope.switchView = function (namespace, viewType) {
if (namespace_view_type.TEXT == viewType) {
namespace.text = parseModel2Text(namespace);
......@@ -89,8 +85,6 @@ application_module.controller("ConfigNamespaceController",
return result;
}
////// text view oper //////
$scope.toCommitNamespace = {};
$scope.setCommitNamespace = function (namespace) {
......@@ -133,19 +127,20 @@ application_module.controller("ConfigNamespaceController",
}
};
/////// release ///////
var releaseModal = $('#releaseModal');
var releaseNamespace = {};
$scope.toReleaseNamespace = {};
$scope.prepareReleaseNamespace = function (namespace) {
releaseNamespace = namespace;
$scope.releaseTitle = new Date().Format("yyyy-MM-dd hh:mm:ss");
$scope.toReleaseNamespace = namespace;
};
$scope.releaseComment = '';
$scope.releaseTitle = '';
$scope.release = function () {
ConfigService.release($rootScope.pageContext.appId, $rootScope.pageContext.env,
$rootScope.pageContext.clusterName,
releaseNamespace.namespace.namespaceName,
$scope.toReleaseNamespace.namespace.namespaceName,
$scope.releaseTitle,
$scope.releaseComment).then(
function (result) {
......@@ -182,12 +177,13 @@ application_module.controller("ConfigNamespaceController",
};
$scope.deleteItem = function () {
ConfigService.delete_item($rootScope.pageContext.env, toDeleteItemId).then(function (result) {
toastr.success("删除成功!");
$rootScope.refreshNamespaces();
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "删除失败");
});
ConfigService.delete_item($rootScope.pageContext.env, toDeleteItemId).then(
function (result) {
toastr.success("删除成功!");
$rootScope.refreshNamespaces();
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "删除失败");
});
};
var toOperationNamespaceName = '';
......
......@@ -116,7 +116,6 @@ directive_module.directive('apollonav', function ($compile, $window, toastr, App
});
/** env cluster selector*/
directive_module.directive('apolloclusterselector', function ($compile, $window, AppService, AppUtil, toastr) {
return {
restrict: 'E',
......@@ -125,28 +124,42 @@ directive_module.directive('apolloclusterselector', function ($compile, $window,
replace: true,
scope: {
appId: '=apolloAppId',
defaultChecked: '=apolloDefaultChecked',
select: '=apolloSelect'
defaultAllChecked: '=apolloDefaultAllChecked',
select: '=apolloSelect',
defaultCheckedEnv: '=apolloDefaultCheckedEnv',
defaultCheckedCluster: '=apolloDefaultCheckedCluster'
},
link: function (scope, element, attrs) {
////// load env //////
AppService.load_nav_tree(scope.appId).then(function (result) {
scope.clusters = [];
var envClusterInfo = AppUtil.collectData(result);
envClusterInfo.forEach(function (node) {
var env = node.env;
node.clusters.forEach(function (cluster) {
cluster.env = env;
cluster.checked = scope.defaultChecked;
scope.clusters.push(cluster);
})
});
scope.select(collectSelectedClusters());
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载环境信息出错");
scope.$watch("defaultCheckedEnv", function (newValue, oldValue) {
refreshClusterList();
});
scope.envAllSelected = scope.defaultChecked;
refreshClusterList();
function refreshClusterList() {
AppService.load_nav_tree(scope.appId).then(function (result) {
scope.clusters = [];
var envClusterInfo = AppUtil.collectData(result);
envClusterInfo.forEach(function (node) {
var env = node.env;
node.clusters.forEach(function (cluster) {
cluster.env = env;
cluster.checked = scope.defaultAllChecked ||
(cluster.env == scope.defaultCheckedEnv && cluster.name
== scope.defaultCheckedCluster);
scope.clusters.push(cluster);
})
});
scope.select(collectSelectedClusters());
}, function (result) {
toastr.error(AppUtil.errorMsg(result), "加载环境信息出错");
});
}
scope.envAllSelected = scope.defaultAllChecked;
scope.toggleEnvsCheckedStatus = function () {
scope.envAllSelected = !scope.envAllSelected;
......
......@@ -3,20 +3,20 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
load_all_namespaces: {
method: 'GET',
isArray: true,
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces'
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces'
},
find_items: {
method: 'GET',
isArray: true,
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/items'
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/items'
},
modify_items: {
method: 'PUT',
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/items'
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/items'
},
release: {
method: 'POST',
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/release'
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/release'
},
diff: {
method: 'POST',
......@@ -30,11 +30,11 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
},
create_item: {
method: 'POST',
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/item'
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/item'
},
update_item: {
method: 'PUT',
url: '/apps/:appId/env/:env/clusters/:clusterName/namespaces/:namespaceName/item'
url: '/apps/:appId/envs/:env/clusters/:clusterName/namespaces/:namespaceName/item'
},
delete_item: {
method: 'DELETE',
......@@ -56,6 +56,7 @@ appService.service("ConfigService", ['$resource', '$q', function ($resource, $q)
});
return d.promise;
},
find_items: function (appId, env, clusterName, namespaceName) {
var d = $q.defer();
config_source.find_items({
......
This source diff could not be displayed because it is too large. You can view the blob instead.
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