Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ISSUE#7210] Add service rest api for v2. #7211

Merged
merged 2 commits into from
Nov 11, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,18 @@
package com.alibaba.nacos.console.exception;

import com.alibaba.nacos.auth.exception.AccessException;
import com.alibaba.nacos.common.model.RestResultUtils;
import com.alibaba.nacos.common.utils.ExceptionUtil;
import com.alibaba.nacos.core.utils.Commons;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

import javax.servlet.http.HttpServletRequest;

/**
* Exception handler for console module.
*
Expand All @@ -47,8 +51,13 @@ private ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentExc
}

@ExceptionHandler(Exception.class)
private ResponseEntity<String> handleException(Exception e) {
LOGGER.error("CONSOLE", e);
private ResponseEntity<Object> handleException(HttpServletRequest request, Exception e) {
String uri = request.getRequestURI();
LOGGER.error("CONSOLE {}", uri, e);
if (uri.contains(Commons.NACOS_SERVER_VERSION_V2)) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(RestResultUtils.failed(ExceptionUtil.getAllExceptionMsg(e)));
}
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(ExceptionUtil.getAllExceptionMsg(e));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
/*
* Copyright 1999-2018 Alibaba Group Holding Ltd.
*
* 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.alibaba.nacos.naming.controllers;

import com.alibaba.nacos.api.common.Constants;
import com.alibaba.nacos.api.exception.NacosException;
import com.alibaba.nacos.api.naming.CommonParams;
import com.alibaba.nacos.api.selector.Selector;
import com.alibaba.nacos.auth.annotation.Secured;
import com.alibaba.nacos.auth.common.ActionTypes;
import com.alibaba.nacos.common.Beta;
import com.alibaba.nacos.common.model.RestResult;
import com.alibaba.nacos.common.model.RestResultUtils;
import com.alibaba.nacos.common.utils.JacksonUtils;
import com.alibaba.nacos.common.utils.NumberUtils;
import com.alibaba.nacos.common.utils.StringUtils;
import com.alibaba.nacos.core.utils.WebUtils;
import com.alibaba.nacos.naming.core.ServiceOperatorV2Impl;
import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata;
import com.alibaba.nacos.naming.core.v2.pojo.Service;
import com.alibaba.nacos.naming.misc.UtilsAndCommons;
import com.alibaba.nacos.naming.pojo.ServiceDetailInfo;
import com.alibaba.nacos.naming.pojo.ServiceNameView;
import com.alibaba.nacos.naming.selector.NoneSelector;
import com.alibaba.nacos.naming.selector.SelectorManager;
import com.alibaba.nacos.naming.utils.ServiceUtil;
import com.alibaba.nacos.naming.web.NamingResourceParser;
import com.fasterxml.jackson.databind.JsonNode;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import java.net.URLDecoder;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;

/**
* Service operation controller.
*
* @author nkorange
*/
@Beta
@RestController
@RequestMapping(UtilsAndCommons.DEFAULT_NACOS_NAMING_CONTEXT_V2 + UtilsAndCommons.NACOS_NAMING_SERVICE_CONTEXT)
public class ServiceControllerV2 {

private final ServiceOperatorV2Impl serviceOperatorV2;

private final SelectorManager selectorManager;

public ServiceControllerV2(ServiceOperatorV2Impl serviceOperatorV2, SelectorManager selectorManager) {
this.serviceOperatorV2 = serviceOperatorV2;
this.selectorManager = selectorManager;
}

/**
* Create a new service. This API will create a persistence service.
*
* @param namespaceId namespace id
* @param serviceName service name
* @param protectThreshold protect threshold
* @param metadata service metadata
* @param selector selector
* @return 'ok' if success
* @throws Exception exception
*/
@PostMapping(value = "/{serviceName}")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public RestResult<String> create(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@PathVariable String serviceName, @RequestParam(defaultValue = Constants.DEFAULT_GROUP) String groupName,
@RequestParam(required = false, defaultValue = "false") boolean ephemeral,
@RequestParam(required = false, defaultValue = "0.0F") float protectThreshold,
@RequestParam(defaultValue = StringUtils.EMPTY) String metadata,
@RequestParam(defaultValue = StringUtils.EMPTY) String selector) throws Exception {
ServiceMetadata serviceMetadata = new ServiceMetadata();
serviceMetadata.setProtectThreshold(protectThreshold);
serviceMetadata.setSelector(parseSelector(selector));
serviceMetadata.setExtendData(UtilsAndCommons.parseMetadata(metadata));
serviceMetadata.setEphemeral(ephemeral);
serviceOperatorV2.create(Service.newService(namespaceId, groupName, serviceName, ephemeral), serviceMetadata);
return RestResultUtils.success("ok");
}

/**
* Remove service.
*
* @param namespaceId namespace
* @param serviceName service name
* @return 'ok' if success
* @throws Exception exception
*/
@DeleteMapping(value = "/{serviceName}")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public RestResult<String> remove(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@PathVariable String serviceName, @RequestParam(defaultValue = Constants.DEFAULT_GROUP) String groupName)
throws Exception {
serviceOperatorV2.delete(Service.newService(namespaceId, groupName, serviceName));
return RestResultUtils.success("ok");
}

/**
* Get detail of service.
*
* @param namespaceId namespace
* @param serviceName service name
* @return detail information of service
* @throws NacosException nacos exception
*/
@GetMapping(value = "/{serviceName}")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public RestResult<ServiceDetailInfo> detail(
@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@PathVariable String serviceName, @RequestParam(defaultValue = Constants.DEFAULT_GROUP) String groupName)
throws Exception {
ServiceDetailInfo result = serviceOperatorV2
.queryService(Service.newService(namespaceId, groupName, serviceName));
return RestResultUtils.success(result);
}

/**
* List all service names.
*
* @param request http request
* @return all service names
* @throws Exception exception
*/
@GetMapping("/list")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.READ)
public RestResult<ServiceNameView> list(HttpServletRequest request) throws Exception {
final int pageNo = NumberUtils.toInt(WebUtils.required(request, "pageNo"));
final int pageSize = NumberUtils.toInt(WebUtils.required(request, "pageSize"));
String namespaceId = WebUtils.optional(request, CommonParams.NAMESPACE_ID, Constants.DEFAULT_NAMESPACE_ID);
String groupName = WebUtils.optional(request, CommonParams.GROUP_NAME, Constants.DEFAULT_GROUP);
String selectorString = WebUtils.optional(request, "selector", StringUtils.EMPTY);
ServiceNameView result = new ServiceNameView();
Collection<String> serviceNameList = serviceOperatorV2.listService(namespaceId, groupName, selectorString);
result.setCount(serviceNameList.size());
result.setServices(ServiceUtil.pageServiceName(pageNo, pageSize, serviceNameList));
return RestResultUtils.success(result);

}

/**
* Update service.
*
* @param namespaceId namespace id
* @param serviceName service name
* @param protectThreshold protect threshold
* @param metadata service metadata
* @param selector selector
* @return 'ok' if success
* @throws Exception exception
*/
@PutMapping(value = "/{serviceName}")
@Secured(parser = NamingResourceParser.class, action = ActionTypes.WRITE)
public RestResult<String> update(@RequestParam(defaultValue = Constants.DEFAULT_NAMESPACE_ID) String namespaceId,
@PathVariable String serviceName, @RequestParam(defaultValue = Constants.DEFAULT_GROUP) String groupName,
@RequestParam(required = false, defaultValue = "0.0F") float protectThreshold,
@RequestParam(defaultValue = StringUtils.EMPTY) String metadata,
@RequestParam(defaultValue = StringUtils.EMPTY) String selector) throws Exception {
ServiceMetadata serviceMetadata = new ServiceMetadata();
serviceMetadata.setProtectThreshold(protectThreshold);
serviceMetadata.setExtendData(UtilsAndCommons.parseMetadata(metadata));
serviceMetadata.setSelector(parseSelector(selector));
Service service = Service.newService(namespaceId, groupName, serviceName);
serviceOperatorV2.update(service, serviceMetadata);
return RestResultUtils.success("ok");
}

private Selector parseSelector(String selectorJsonString) throws Exception {
if (StringUtils.isBlank(selectorJsonString)) {
return new NoneSelector();
}

JsonNode selectorJson = JacksonUtils.toObj(URLDecoder.decode(selectorJsonString, "UTF-8"));
String type = Optional.ofNullable(selectorJson.get("type"))
.orElseThrow(() -> new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!"))
.asText();
String expression = Optional.ofNullable(selectorJson.get("expression")).map(JsonNode::asText).orElse(null);
Selector selector = selectorManager.parseSelector(type, expression);
if (Objects.isNull(selector)) {
throw new NacosException(NacosException.INVALID_PARAM, "not match any type of selector!");
}
return selector;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,17 @@
import com.alibaba.nacos.naming.core.v2.metadata.NamingMetadataOperateService;
import com.alibaba.nacos.naming.core.v2.metadata.ServiceMetadata;
import com.alibaba.nacos.naming.core.v2.pojo.Service;
import com.alibaba.nacos.naming.pojo.ClusterInfo;
import com.alibaba.nacos.naming.pojo.ServiceDetailInfo;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;

/**
Expand All @@ -61,6 +65,17 @@ public ServiceOperatorV2Impl(NamingMetadataOperateService metadataOperateService
@Override
public void create(String namespaceId, String serviceName, ServiceMetadata metadata) throws NacosException {
Service service = getServiceFromGroupedServiceName(namespaceId, serviceName, metadata.isEphemeral());
create(service, metadata);
}

/**
* Create new service.
*
* @param service v2 service
* @param metadata new metadata of service
* @throws NacosException nacos exception during creating
*/
public void create(Service service, ServiceMetadata metadata) throws NacosException {
if (ServiceManager.getInstance().containSingleton(service)) {
throw new NacosException(NacosException.INVALID_PARAM,
String.format("specified service %s already exists!", service.getGroupedServiceName()));
Expand All @@ -80,14 +95,24 @@ public void update(Service service, ServiceMetadata metadata) throws NacosExcept
@Override
public void delete(String namespaceId, String serviceName) throws NacosException {
Service service = getServiceFromGroupedServiceName(namespaceId, serviceName, true);
delete(service);
}

/**
* Delete service.
*
* @param service service v2
* @throws NacosException nacos exception during delete
*/
public void delete(Service service) throws NacosException {
if (!ServiceManager.getInstance().containSingleton(service)) {
throw new NacosException(NacosException.INVALID_PARAM,
String.format("service %s not found!", service.getGroupedServiceName()));
}

if (!serviceStorage.getPushData(service).getHosts().isEmpty()) {
throw new NacosException(NacosException.INVALID_PARAM,
"Service " + serviceName + " is not empty, can't be delete. Please unregister instance first");
throw new NacosException(NacosException.INVALID_PARAM, "Service " + service.getGroupedServiceName()
+ " is not empty, can't be delete. Please unregister instance first");
}
metadataOperateService.deleteServiceMetadata(service);
}
Expand All @@ -113,6 +138,33 @@ public ObjectNode queryService(String namespaceId, String serviceName) throws Na
return result;
}

/**
* Query service detail.
*
* @param service service
* @return service detail with cluster info
* @throws NacosException nacos exception during query
*/
public ServiceDetailInfo queryService(Service service) throws NacosException {
if (!ServiceManager.getInstance().containSingleton(service)) {
throw new NacosException(NacosException.INVALID_PARAM,
"service not found, namespace: " + service.getNamespace() + ", serviceName: " + service
.getGroupedServiceName());
}
ServiceDetailInfo result = new ServiceDetailInfo();
ServiceMetadata serviceMetadata = metadataManager.getServiceMetadata(service).orElse(new ServiceMetadata());
setServiceMetadata(result, serviceMetadata, service);
Map<String, ClusterInfo> clusters = new HashMap<>(2);
for (String each : serviceStorage.getClusters(service)) {
ClusterMetadata clusterMetadata =
serviceMetadata.getClusters().containsKey(each) ? serviceMetadata.getClusters().get(each)
: new ClusterMetadata();
clusters.put(each, newClusterNodeV2(each, clusterMetadata));
}
result.setClusterMap(clusters);
return result;
}

private void setServiceMetadata(ObjectNode serviceDetail, ServiceMetadata serviceMetadata, Service service) {
serviceDetail.put(FieldsConstants.NAME_SPACE_ID, service.getNamespace());
serviceDetail.put(FieldsConstants.GROUP_NAME, service.getGroup());
Expand All @@ -123,6 +175,15 @@ private void setServiceMetadata(ObjectNode serviceDetail, ServiceMetadata servic
serviceDetail.replace(FieldsConstants.SELECTOR, JacksonUtils.transferToJsonNode(serviceMetadata.getSelector()));
}

private void setServiceMetadata(ServiceDetailInfo serviceDetail, ServiceMetadata serviceMetadata, Service service) {
serviceDetail.setNamespace(service.getNamespace());
serviceDetail.setGroupName(service.getGroup());
serviceDetail.setServiceName(service.getName());
serviceDetail.setProtectThreshold(serviceMetadata.getProtectThreshold());
serviceDetail.setMetadata(serviceMetadata.getExtendData());
serviceDetail.setSelector(serviceMetadata.getSelector());
}

private ObjectNode newClusterNode(String clusterName, ClusterMetadata clusterMetadata) {
ObjectNode result = JacksonUtils.createEmptyJsonNode();
result.put(FieldsConstants.NAME, clusterName);
Expand All @@ -132,10 +193,17 @@ private ObjectNode newClusterNode(String clusterName, ClusterMetadata clusterMet
return result;
}

private ClusterInfo newClusterNodeV2(String clusterName, ClusterMetadata clusterMetadata) {
ClusterInfo result = new ClusterInfo();
result.setClusterName(clusterName);
result.setHealthChecker(clusterMetadata.getHealthChecker());
result.setMetadata(clusterMetadata.getExtendData());
return result;
}

@Override
@SuppressWarnings("unchecked")
public Collection<String> listService(String namespaceId, String groupName, String selector)
throws NacosException {
public Collection<String> listService(String namespaceId, String groupName, String selector) throws NacosException {
Collection<Service> services = ServiceManager.getInstance().getSingletons(namespaceId);
if (services.isEmpty()) {
return Collections.EMPTY_LIST;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,12 @@ public class UtilsAndCommons {

public static final String NACOS_SERVER_VERSION = "/v1";

public static final String NACOS_SERVER_VERSION_2 = "/v2";

public static final String DEFAULT_NACOS_NAMING_CONTEXT = NACOS_SERVER_VERSION + "/ns";

public static final String DEFAULT_NACOS_NAMING_CONTEXT_V2 = NACOS_SERVER_VERSION_2 + "/ns";

public static final String NACOS_NAMING_CONTEXT = DEFAULT_NACOS_NAMING_CONTEXT;

public static final String NACOS_NAMING_CATALOG_CONTEXT = "/catalog";
Expand Down
Loading