/***************************************************************************
* Copyright (c) 2012-2015 VMware, Inc. All Rights Reserved.
* 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.vmware.bdd.rest;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import com.vmware.bdd.apitypes.*;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.NestedRuntimeException;
import org.springframework.dao.DataAccessException;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ExceptionHandler;
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.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import com.vmware.bdd.aop.annotation.RestCallPointcut;
import com.vmware.bdd.apitypes.TaskRead.Type;
import com.vmware.bdd.entity.AppManagerEntity;
import com.vmware.bdd.exception.BddException;
import com.vmware.bdd.exception.NetworkException;
import com.vmware.bdd.exception.SoftwareManagerCollectorException;
import com.vmware.bdd.manager.*;
import com.vmware.bdd.service.IClusteringService;
import com.vmware.bdd.service.resmgmt.IAppManagerService;
import com.vmware.bdd.service.resmgmt.IDatastoreService;
import com.vmware.bdd.service.resmgmt.INetworkService;
import com.vmware.bdd.service.resmgmt.IResourcePoolService;
import com.vmware.bdd.software.mgmt.plugin.exception.ValidationException;
import com.vmware.bdd.software.mgmt.plugin.intf.SoftwareManager;
import com.vmware.bdd.software.mgmt.plugin.model.ClusterBlueprint;
import com.vmware.bdd.software.mgmt.plugin.model.HadoopStack;
import com.vmware.bdd.utils.CommonUtil;
import com.vmware.bdd.utils.Constants;
import com.vmware.bdd.utils.IpAddressUtil;
import org.apache.commons.configuration.BaseConfiguration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.PropertiesConfiguration;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.log4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@Controller
public class RestResource {
private static final Logger logger = Logger.getLogger(RestResource.class);
@Autowired
private ClusterManager clusterMgr;
@Autowired
private JobManager jobManager;
@Autowired
private IResourcePoolService vcRpSvc;
@Autowired
private INetworkService networkSvc;
@Autowired
private RackInfoManager rackInfoManager;
@Autowired
private IDatastoreService datastoreSvc;
@Autowired
private IAppManagerService appManagerService;
@Autowired
private ScaleManager scaleMgr;
@Autowired
private SoftwareManagerCollector softwareManagerCollector;
@Autowired
private IClusteringService clusteringService;
private static final String ERR_CODE_FILE = "serengeti-errcode.properties";
private static final int DEFAULT_HTTP_ERROR_CODE = 500;
/* HTTP status code read from a config file. */
private static org.apache.commons.configuration.Configuration httpStatusCodes =
init();
private static org.apache.commons.configuration.Configuration init() {
PropertiesConfiguration config = null;
try {
config = new PropertiesConfiguration();
config.setListDelimiter('\0');
config.load(ERR_CODE_FILE);
} catch (ConfigurationException ex) {
// error out if the configuration file is not there
String message = "Cannot load Serengeti error message file.";
Logger.getLogger(RestResource.class).fatal(message, ex);
throw BddException.APP_INIT_ERROR(ex, message);
}
return config;
}
/**
* Get REST api version
* @return REST api version
*/
@RequestMapping(value = "/hello", method = RequestMethod.GET, produces = "application/json")
@ResponseStatus(HttpStatus.OK)
@ResponseBody
public Map<String, String> getHello() {
Map<String, String> serverInfo = new HashMap<String, String>();
serverInfo.put("version", Constants.VERSION);
return serverInfo;
}
// task API
/**
* Get latest tasks of exiting clusters
* @return A list of task information
*/
@RequestMapping(value = "/tasks", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public List<TaskRead> getTasks() {
return jobManager.getLatestTaskForExistedClusters();
}
/**
* Get a specific task by its id
* @param taskId The identity returned as part of uri in the response(Accepted status) header of Location, such as https://hostname:8443/serengeti/api/task/taskId
* @return Task information
*/
@RequestMapping(value = "/task/{taskId}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public TaskRead getTaskById(@PathVariable long taskId) throws Exception {
// TODO add exception handling
TaskRead task = jobManager.getJobExecutionStatus(taskId);
if (task.getStatus() == TaskRead.Status.COMPLETED) {
task.setProgress(1.0);
}
if (task.getType() == null) {
task.setType(Type.INNER); // XXX just keep the interface now
}
return task;
}
// cluster API
/**
* Create a cluster
* @param createSpec create specification
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/clusters", method = RequestMethod.POST, consumes = "application/json")
@ResponseStatus(HttpStatus.ACCEPTED)
public void createCluster(@RequestBody ClusterCreate createSpec,
@RequestParam(value = "skiprefreshvc", required = false) boolean skipRefreshVC,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
verifyInitialized();
String clusterName = createSpec.getName();
logger.info(String.format("The specified node template name for creating cluster %s is %s", clusterName, createSpec.getTemplateName()));
if (!CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
if (CommonUtil.isBlank(createSpec.getAppManager())) {
createSpec.setAppManager(Constants.IRONFAN);
} else {
AppManagerEntity appManager =
appManagerService.findAppManagerByName(createSpec
.getAppManager());
if (appManager == null) {
throw BddException.NOT_FOUND("application manager",
createSpec.getAppManager());
}
}
BaseConfiguration params = new BaseConfiguration();
params.addProperty(Constants.SKIP_REFRESH_VC, skipRefreshVC);
long jobExecutionId = clusterMgr.createCluster(createSpec, params);
redirectRequest(jobExecutionId, request, response);
}
/**
* Configure a hadoop or hbase cluster's properties
* @param clusterName
* @param createSpec The existing create specification plus the configuration map supported in cluster and node group levels(please refer to a sample specification file)
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}/config", method = RequestMethod.PUT, consumes = "application/json")
@ResponseStatus(HttpStatus.ACCEPTED)
public void configCluster(@PathVariable("clusterName") String clusterName,
@RequestBody ClusterCreate createSpec, HttpServletRequest request,
HttpServletResponse response) throws Exception {
verifyInitialized();
if (!CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
Long taskId = clusterMgr.configCluster(clusterName, createSpec);
redirectRequest(taskId, request, response);
}
private void redirectRequest(long taskId, HttpServletRequest request,
HttpServletResponse response) {
StringBuffer url = request.getRequestURL();
String pathInfo = request.getPathInfo();
if (!CommonUtil.validataPathInfo(pathInfo)) {
throw BddException.INVALID_PARAMETER("requested path info", pathInfo);
}
int subLength = url.length() - pathInfo.length();
url.setLength(subLength);
url.append("/task/").append(Long.toString(taskId));
response.setHeader(Constants.RESPONSE_HEADER_LOCATION, url.toString());
}
/**
* Delete a cluster
* @param clusterName
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.ACCEPTED)
public void deleteCluster(@PathVariable("clusterName") String clusterName,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
verifyInitialized();
clusterName = CommonUtil.decode(clusterName);
// make sure cluster name is valid
if (!CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
Long taskId = clusterMgr.deleteClusterByName(clusterName);
redirectRequest(taskId, request, response);
}
@RequestMapping(value = "/cluster/{clusterName}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json")
public void updateCluster(@PathVariable("clusterName") String clusterName,
@RequestBody(required = false) ClusterCreate clusterUpdate,
@RequestParam(value = "state", required = false) String state,
@RequestParam(value = "force", required = false, defaultValue = "false") Boolean force,
@RequestParam(value = "ignorewarning", required = false, defaultValue = "false") boolean ignoreWarning,
@RequestParam(value = "append", required = false, defaultValue = "false") boolean append,
HttpServletRequest request, HttpServletResponse response) throws Exception {
if (state != null) {
// forward request to startStop`() for backward compatibility
request.getRequestDispatcher(clusterName + "/action").forward(request, response);
response.setStatus(HttpStatus.ACCEPTED.value());
return;
}
verifyInitialized();
clusterName = CommonUtil.decode(clusterName);
if (CommonUtil.isBlank(clusterName) || !CommonUtil.validateResourceName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
clusterMgr.updateCluster(clusterUpdate, ignoreWarning, append);
response.setStatus(HttpStatus.OK.value());
}
/**
* Start or stop a normal cluster, or resume a failed cluster after adjusting the resources allocated to this cluster
* @param clusterName
* @param state Can be start, stop, or resume
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}/action", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.ACCEPTED)
public void startStopResumeCluster(
@PathVariable("clusterName") String clusterName,
@RequestParam(value = "state", required = true) String state,
@RequestParam(value = "force", required = false, defaultValue = "false") Boolean force,
@RequestParam(value = "skiprefreshvc", required = false) boolean skipRefreshVC,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
verifyInitialized();
clusterName = CommonUtil.decode(clusterName);
if (CommonUtil.isBlank(clusterName)
|| !CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
Long taskId;
if (state.equals("stop")) {
taskId = clusterMgr.stopCluster(clusterName);
redirectRequest(taskId, request, response);
} else if (state.equals("start")) {
taskId = clusterMgr.startCluster(clusterName, force);
redirectRequest(taskId, request, response);
} else if (state.equals("resume")) {
BaseConfiguration params = new BaseConfiguration();
params.addProperty(Constants.SKIP_REFRESH_VC, skipRefreshVC);
taskId = clusterMgr.resumeClusterCreation(clusterName, params);
redirectRequest(taskId, request, response);
} else {
throw BddException.INVALID_PARAMETER("cluster state", state);
}
}
/**
* Expand the number of nodes in a node group
* @param clusterName
* @param groupName
* @param instanceNum The target instance number after resize. It can be larger/smaller than existing instance number in this node group
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}/nodegroup/{groupName}/instancenum", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.ACCEPTED)
public void resizeCluster(@PathVariable("clusterName") String clusterName,
@PathVariable("groupName") String groupName,
@RequestBody Integer instanceNum,
@RequestParam(value = "force", required = false, defaultValue = "false") Boolean force,
@RequestParam(value = "skiprefreshvc", required = false) boolean skipRefreshVC,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
verifyInitialized();
if (CommonUtil.isBlank(clusterName)
|| !CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
if (CommonUtil.isBlank(groupName)
|| !CommonUtil.validateNodeGroupName(groupName)) {
throw BddException.INVALID_PARAMETER("node group name", groupName);
}
if (instanceNum <= 0) {
throw BddException.INVALID_PARAMETER("node group instance number",
String.valueOf(instanceNum));
}
BaseConfiguration params = new BaseConfiguration();
params.addProperty(Constants.SKIP_REFRESH_VC, skipRefreshVC);
Long taskId =
clusterMgr.resizeCluster(clusterName, groupName, instanceNum, force, params);
redirectRequest(taskId, request, response);
}
/**
* Upgrade a cluster
* @param clusterName
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
* @throws Exception
*/
@RequestMapping(value = "/cluster/{clusterName}/upgrade", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.ACCEPTED)
public void upgradeCluster(@PathVariable("clusterName") String clusterName,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
// make sure cluster name is valid
if (!CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
Long taskId = clusterMgr.upgradeClusterByName(clusterName);
redirectRequest(taskId, request, response);
}
/**
* Scale up or down the cpu and memory of each node in a node group
* @param clusterName
* @param groupName
* @param scale The new cpu and memory allocated to each node in this node group
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}/nodegroup/{groupName}/scale", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.ACCEPTED)
public void scale(@PathVariable("clusterName") String clusterName,
@PathVariable("groupName") String groupName,
@RequestBody ResourceScale scale, HttpServletRequest request,
HttpServletResponse response) throws Exception {
verifyInitialized();
if (CommonUtil.isBlank(clusterName)
|| !CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
if (CommonUtil.isBlank(groupName)
|| !CommonUtil.validateNodeGroupName(groupName)) {
throw BddException.INVALID_PARAMETER("node group name", groupName);
}
if (scale.getCpuNumber() <= 0
&& scale.getMemory() < Constants.MIN_MEM_SIZE) {
throw BddException.INVALID_PARAMETER_WITHOUT_EQUALS_SIGN(
"node group scale parameter. The number of CPUs must be greater than zero, and the memory size in MB must be greater than or equal to "
+ Constants.MIN_MEM_SIZE + ":", scale);
}
logger.info("scale cluster: " + scale.toString());
Long taskId =
scaleMgr.scaleNodeGroupResource(scale);
redirectRequest(taskId, request, response);
}
/**
* Turn on or off some compute nodes
* @param clusterName
* @param requestBody
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}/param_wait_for_result", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.ACCEPTED)
public void asyncSetParam(@PathVariable("clusterName") String clusterName, @RequestBody ElasticityRequestBody requestBody, HttpServletRequest request,
HttpServletResponse response) throws Exception {
verifyInitialized();
validateInput(clusterName, requestBody);
ClusterRead cluster = clusterMgr.getClusterByName(clusterName, false);
if (!cluster.needAsyncUpdateParam(requestBody)) {
throw BddException.BAD_REST_CALL(null, "invalid input to cluster.");
}
Long taskId =
clusterMgr.asyncSetParam(clusterName, null, null, null, null, requestBody.getIoPriority());
redirectRequest(taskId, request, response);
}
/**
* Change elasticity mode, IO priority, and maximum or minimum number of powered on compute nodes under auto mode
* @param clusterName
* @param requestBody
* @param request
*/
@RequestMapping(value = "/cluster/{clusterName}/param", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void syncSetParam(@PathVariable("clusterName") String clusterName, @RequestBody ElasticityRequestBody requestBody, HttpServletRequest request,
HttpServletResponse response) throws Exception {
verifyInitialized();
validateInput(clusterName, requestBody);
clusterMgr.syncSetParam(clusterName, null, null, null, null, requestBody.getIoPriority());
}
private void validateInput(String clusterName, ElasticityRequestBody requestBody) {
if (CommonUtil.isBlank(clusterName) || !CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
}
/**
* Replace some failed disks with new disks
* @param clusterName
* @param fixDiskSpec
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}/fix/disk", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.ACCEPTED)
public void fixCluster(@PathVariable("clusterName") String clusterName,
@RequestBody FixDiskRequestBody fixDiskSpec,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
verifyInitialized();
Long taskId =
clusterMgr.fixDiskFailures(clusterName,
fixDiskSpec.getNodeGroupName());
redirectRequest(taskId, request, response);
}
/**
* Retrieve a cluster information by its name
* @param clusterName
* @param details not used by this version
* @return The cluster information
*/
@RequestMapping(value = "/cluster/{clusterName}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
public ClusterRead getCluster(
@PathVariable("clusterName") String clusterName,
@RequestParam(value = "details", required = false) Boolean details) {
clusterName = CommonUtil.decode(clusterName);
if (CommonUtil.isBlank(clusterName)
|| !CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
return clusterMgr.getClusterByName(clusterName, (details == null) ? false
: details);
}
/**
* Retrieve a cluster's specification by its name
* @param clusterName
* @return The cluster specification
*/
@RequestMapping(value = "/cluster/{clusterName}/spec", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public ClusterCreate getClusterSpec(
@PathVariable("clusterName") String clusterName) {
clusterName = CommonUtil.decode(clusterName);
if (CommonUtil.isBlank(clusterName)
|| !CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
return clusterMgr.getClusterSpec(clusterName);
}
@RequestMapping(value = "/cluster/{clusterName}/rack", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public Map<String, String> getClusterRackTopology(
@PathVariable("clusterName") String clusterName,
@RequestParam(value = "topology", required = false) String topology) {
clusterName = CommonUtil.decode(clusterName);
if (CommonUtil.isBlank(clusterName)
|| !CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
return clusterMgr.getRackTopology(clusterName, topology);
}
/**
* Get all clusters' information
* @param details not used by this version
* @return A list of cluster information
*/
@RequestMapping(value = "/clusters", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<ClusterRead> getClusters(
@RequestParam(value = "details", required = false) Boolean details) {
return clusterMgr.getClusters((details == null) ? false : details);
}
/**
* Add nodeGroup to a cluster
* @param nodeGroupAddSpec create specification
* @param request
* @return Return a response with Accepted status and put task uri in the Location of header that can be used to monitor the progress
*/
@RequestMapping(value = "/cluster/{clusterName}/nodegroups", method = RequestMethod.POST, consumes = "application/json")
@ResponseStatus(HttpStatus.ACCEPTED)
public void expandCluster(@RequestBody NodeGroupAdd nodeGroupAddSpec,
@PathVariable("clusterName") String clusterName,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
verifyInitialized();
List<String> failedMsgList = new ArrayList<String>();
List<String> warningMsgList = new ArrayList<String>();
nodeGroupAddSpec.validateNodeGroupAdd(failedMsgList, warningMsgList);
NodeGroupCreate[] nodeGroupsAdd = nodeGroupAddSpec.getNodeGroups();
if (!CommonUtil.validateClusterName(clusterName)) {
throw BddException.INVALID_PARAMETER("cluster name", clusterName);
}
logger.info("call rest for expand node groups into a cluster");
Long taskId =
clusterMgr.expandCluster(clusterName, nodeGroupsAdd);
redirectRequest(taskId, request, response);
}
// cloud provider API
/**
* Add a VC resourcepool into BDE
* @param rpSpec
*/
@RequestMapping(value = "/resourcepools", method = RequestMethod.POST, consumes = "application/json")
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void addResourcePool(@RequestBody ResourcePoolAdd rpSpec) {
verifyInitialized();
if (rpSpec == null) {
throw BddException.INVALID_PARAMETER("rpSpec", null);
}
if (CommonUtil.isBlank(rpSpec.getName())
|| !CommonUtil.validateResourceName(rpSpec.getName())) {
throw BddException.INVALID_PARAMETER("resource pool name",
rpSpec.getName());
}
if (CommonUtil.isBlank(rpSpec.getVcClusterName())
|| !CommonUtil.validateVcResourceName(rpSpec.getVcClusterName())) {
throw BddException.INVALID_PARAMETER("vCenter Server cluster name",
rpSpec.getVcClusterName());
}
rpSpec.setResourcePoolName(CommonUtil.notNull(
rpSpec.getResourcePoolName(), ""));
if (!CommonUtil.isBlank(rpSpec.getResourcePoolName())
&& !CommonUtil.validateVcResourceName(rpSpec.getResourcePoolName())) {
throw BddException.INVALID_PARAMETER(
"vCenter Server resource pool name",
rpSpec.getResourcePoolName());
}
vcRpSvc.addResourcePool(rpSpec.getName(), rpSpec.getVcClusterName(),
rpSpec.getResourcePoolName());
}
/**
* Get all BDE resource pools' information
* @return a list of BDE resource pool information
*/
@RequestMapping(value = "/resourcepools", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<ResourcePoolRead> getResourcePools() {
return vcRpSvc.getAllResourcePoolForRest();
}
/**
* Get a BDE resource pool's information by its name
* @param rpName
* @return The resource pool information
*/
@RequestMapping(value = "/resourcepool/{rpName}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public ResourcePoolRead getResourcePool(@PathVariable("rpName") String rpName) {
rpName = CommonUtil.decode(rpName);
if (CommonUtil.isBlank(rpName)
|| !CommonUtil.validateResourceName(rpName)) {
throw BddException.INVALID_PARAMETER("resource pool name", rpName);
}
ResourcePoolRead read = vcRpSvc.getResourcePoolForRest(rpName);
if (read == null) {
throw BddException.NOT_FOUND("Resource pool", rpName);
}
return read;
}
/**
* Delete a BDE resource pool, and the corresponding VC resource pool will still keep there
* @param rpName
*/
@RequestMapping(value = "/resourcepool/{rpName}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void deleteResourcePool(@PathVariable("rpName") String rpName) {
verifyInitialized();
rpName = CommonUtil.decode(rpName);
if (CommonUtil.isBlank(rpName)
|| !CommonUtil.validateResourceName(rpName)) {
throw BddException.INVALID_PARAMETER("resource pool name", rpName);
}
vcRpSvc.deleteResourcePool(rpName);
}
/**
* Add a VC datastore, or multiple VC datastores When regex is true into BDE
* @param dsSpec
*/
@RequestMapping(value = "/datastores", method = RequestMethod.POST, consumes = "application/json")
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void addDatastore(@RequestBody DatastoreAdd dsSpec) {
verifyInitialized();
if (dsSpec == null) {
throw BddException.INVALID_PARAMETER("dsSpec", null);
}
if (CommonUtil.isBlank(dsSpec.getName())
|| !CommonUtil.validateResourceName(dsSpec.getName())) {
throw BddException.INVALID_PARAMETER("datestore name",
dsSpec.getName());
}
if (!dsSpec.getRegex() && !CommonUtil.validateVcDataStoreNames(dsSpec.getSpec())) {
throw BddException.INVALID_PARAMETER("vCenter Server datastore name",
dsSpec.getSpec().toString());
}
datastoreSvc.addDatastores(dsSpec);
}
/**
* Get a BDE datastore information
* @param dsName
* @return The BDE datastore information
*/
@RequestMapping(value = "/datastore/{dsName}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public DatastoreRead getDatastore(@PathVariable("dsName") String dsName) {
dsName = CommonUtil.decode(dsName);
if (CommonUtil.isBlank(dsName)
|| !CommonUtil.validateResourceName(dsName)) {
throw BddException.INVALID_PARAMETER("datestore name", dsName);
}
DatastoreRead read = datastoreSvc.getDatastoreRead(dsName);
if (read == null) {
throw BddException.NOT_FOUND("Data store", dsName);
}
return read;
}
/**
* Get all BDE datastores' information
* @return A list of BDE datastore information
*/
@RequestMapping(value = "/datastores", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<DatastoreRead> getDatastores() {
return datastoreSvc.getAllDatastoreReads();
}
/**
* Delete a BDE datastore, and the corresponding VC datastore will still keep there
* @param dsName
*/
@RequestMapping(value = "/datastore/{dsName}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void deleteDatastore(@PathVariable("dsName") String dsName) {
verifyInitialized();
dsName = CommonUtil.decode(dsName);
if (CommonUtil.isBlank(dsName)
|| !CommonUtil.validateResourceName(dsName)) {
throw BddException.INVALID_PARAMETER("date store name", dsName);
}
datastoreSvc.deleteDatastore(dsName);
}
/**
* Delete a BDE network, and the corresponding VC network will still keep there
* @param networkName
*/
@RequestMapping(value = "/network/{networkName}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void deleteNetworkByName(
@PathVariable("networkName") String networkName) {
verifyInitialized();
networkName = CommonUtil.decode(networkName);
if (CommonUtil.isBlank(networkName)
|| !CommonUtil.validateResourceName(networkName)) {
throw BddException.INVALID_PARAMETER("network name", networkName);
}
networkSvc.removeNetwork(networkName);
}
/**
* Get the BDE network information by its name
* @param networkName
* @param details true will return information about allocated ips to cluster nodes
* @return The BDE network information
*/
@RequestMapping(value = "/network/{networkName}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public NetworkRead getNetworkByName(
@PathVariable("networkName") String networkName,
@RequestParam(value = "details", required = false, defaultValue = "false") final Boolean details) {
networkName = CommonUtil.decode(networkName);
if (CommonUtil.isBlank(networkName)
|| !CommonUtil.validateResourceName(networkName)) {
throw BddException.INVALID_PARAMETER("network name", networkName);
}
NetworkRead network =
networkSvc.getNetworkByName(networkName, details != null ? details
: false);
if (network == null) {
throw NetworkException.NOT_FOUND("Network", networkName);
}
return network;
}
/**
* Get all BDE networks' information
* @param details true will return information about allocated ips to cluster nodes
* @return A list of BDE network information
*/
@RequestMapping(value = "/networks", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<NetworkRead> getNetworks(
@RequestParam(value = "details", required = false, defaultValue = "false") final Boolean details) {
return networkSvc.getAllNetworks(details != null ? details : false);
}
/**
* Add a VC network into BDE
* @param na
*/
@RequestMapping(value = "/networks", method = RequestMethod.POST, consumes = "application/json")
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void addNetworks(@RequestBody final NetworkAdd na) {
verifyInitialized();
List<String> missingParameters = new ArrayList<String>();
if (CommonUtil.isBlank(na.getName())) {
missingParameters.add("name");
}
if (CommonUtil.isBlank(na.getPortGroup())) {
missingParameters.add("portGroup");
}
if (na.getDnsType() == null) {
missingParameters.add("dnsType");
}
if (!missingParameters.isEmpty()) {
throw BddException.MISSING_PARAMETER(missingParameters);
}
if (!CommonUtil.validateResourceName(na.getName())) {
throw BddException.INVALID_PARAMETER("name", na.getName());
}
if (!CommonUtil.validateDnsType(na.getDnsType())) {
throw BddException.INVALID_DNS_TYPE(na.getDnsType());
}
if (na.getIsDhcp()) {
networkSvc.addDhcpNetwork(na.getName(), na.getPortGroup(), na.getDnsType());
} else {
if (!IpAddressUtil.isValidNetmask(na.getNetmask())) {
throw BddException.INVALID_PARAMETER("netmask", na.getNetmask());
}
long netmask = IpAddressUtil.getAddressAsLong(na.getNetmask());
if (na.getGateway() != null && !IpAddressUtil.isValidIp(netmask,
IpAddressUtil.getAddressAsLong(na.getGateway()))) {
throw BddException.INVALID_PARAMETER("gateway", na.getGateway());
}
if (na.getDns1() != null && !IpAddressUtil.isValidIp(na.getDns1())) {
throw BddException.INVALID_PARAMETER("primary DNS", na.getDns1());
}
if (na.getDns2() != null && !IpAddressUtil.isValidIp(na.getDns2())) {
throw BddException.INVALID_PARAMETER("secondary DNS", na.getDns2());
}
if (na.getDnsType().equals(NetworkDnsType.DYNAMIC.toString())) {
throw BddException.INVALID_PARAMETER("dns TYPE", na.getDnsType());
}
IpAddressUtil.verifyIPBlocks(na.getIpBlocks(), netmask);
networkSvc.addIpPoolNetwork(na.getName(), na.getPortGroup(),
na.getNetmask(), na.getGateway(), na.getDns1(), na.getDns2(),
na.getIpBlocks(), na.getDnsType());
}
}
/**
* Add ips into an existing BDE network
* @param networkName
* @param request
*/
@RequestMapping(value = "/network/{networkName}", method = RequestMethod.PUT, consumes = "application/json")
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void updateNetwork(@PathVariable("networkName") String networkName,
@RequestBody NetworkAdd networkAdd, HttpServletRequest request,
HttpServletResponse response) {
verifyInitialized();
networkName = CommonUtil.decode(networkName);
if (CommonUtil.isBlank(networkName)
|| !CommonUtil.validateResourceName(networkName)) {
throw BddException.INVALID_PARAMETER("network name", networkName);
}
if (networkAdd.getIpBlocks() == null && networkAdd.getDnsType() == null) {
throw BddException.INVALID_OPTIONS_WHEN_UPDATE_NETWORK(new String[]{"addIP", "dnsType"});
}
if (networkAdd.getDnsType() != null && !CommonUtil.validateDnsType(networkAdd.getDnsType())) {
throw BddException.INVALID_DNS_TYPE(networkAdd.getDnsType());
}
networkSvc.updateNetwork(networkName, networkAdd);
}
/**
* Get all appmanager types supported by BDE
* @return The list of Application Manager types in BDE
*/
@RequestMapping(value = "/appmanagers/types", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<String> getAppManagerTypes() {
return softwareManagerCollector.getAllAppManagerTypes();
}
/**
* Add an appmanager to BDE
* @param appManagerAdd
*/
@RequestMapping(value = "/appmanagers", method = RequestMethod.POST, consumes = "application/json")
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void addAppManager(@RequestBody final AppManagerAdd appManagerAdd) {
if (appManagerAdd == null) {
throw BddException.INVALID_PARAMETER("appManagerAdd", null);
}
if (CommonUtil.isBlank(appManagerAdd.getName())
|| !CommonUtil.validateResourceName(appManagerAdd.getName())) {
throw BddException.INVALID_PARAMETER("appmanager name",
appManagerAdd.getName());
}
softwareManagerCollector.createSoftwareManager(appManagerAdd);
//pluginService.addPlugin(pluginAdd);
}
/**
* Modify an app manager
* @param appManagerAdd
* @param request
* @param response
*/
@RequestMapping(value = "/appmanagers", method = RequestMethod.PUT, consumes = "application/json")
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void modifyAppManager(@RequestBody AppManagerAdd appManagerAdd,
HttpServletRequest request,
HttpServletResponse response) {
if (appManagerAdd == null) {
throw BddException.INVALID_PARAMETER("appManagerAdd", null);
}
try {
softwareManagerCollector.modifySoftwareManager(appManagerAdd);
} catch (SWMgrCollectorInternalException ex) {
throw BddException.wrapIfNeeded(ex, "App Manager Management Error");
}
}
/**
* Delete an app manager
* @param appManagerName
*/
@RequestMapping(value = "/appmanager/{appManagerName}", method = RequestMethod.DELETE)
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void deleteAppManager(@PathVariable("appManagerName") String appManagerName) {
appManagerName = CommonUtil.decode(appManagerName);
if (CommonUtil.isBlank(appManagerName)
|| !CommonUtil.validateResourceName(appManagerName)) {
throw BddException.INVALID_PARAMETER("appmanager name", appManagerName);
}
try {
softwareManagerCollector.deleteSoftwareManager(appManagerName);
} catch (SWMgrCollectorInternalException ex) {
throw BddException.wrapIfNeeded(ex, "App Manager Management Error");
}
}
/**
* Get a BDE appmanager information
* @param appManagerName
* @return The BDE appmanager information
*/
@RequestMapping(value = "/appmanager/{appManagerName}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public AppManagerRead getAppManager(@PathVariable("appManagerName") String appManagerName) {
appManagerName = CommonUtil.decode(appManagerName);
if (CommonUtil.isBlank(appManagerName)
|| !CommonUtil.validateResourceName(appManagerName)) {
throw BddException.INVALID_PARAMETER("appmanager name", appManagerName);
}
AppManagerRead read = softwareManagerCollector.getAppManagerRead(appManagerName);
return read;
}
/**
* Get supported distro information of a BDE appmanager
* @param appManagerName
* @return The list of supported distros
*/
@RequestMapping(value = "/appmanager/{appManagerName}/distros", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<DistroRead> getAppManagerDistros(@PathVariable("appManagerName") String appManagerName) {
appManagerName = CommonUtil.decode(appManagerName);
if (CommonUtil.isBlank(appManagerName)
|| !CommonUtil.validateResourceName(appManagerName)) {
throw BddException.INVALID_PARAMETER("appmanager name", appManagerName);
}
SoftwareManager softMgr =
softwareManagerCollector.getSoftwareManager(appManagerName);
List<HadoopStack> stacks = null;
try {
stacks = softMgr.getSupportedStacks();
} catch (Exception e) {
// call SoftwareManagerCollector.loadSoftwareManager() to detect connection issue
logger.error("Failed to get supported stacks from appmaanger "
+ appManagerName, e);
SoftwareManager softwareManager =
softwareManagerCollector.getSoftwareManager(appManagerName);
// if succeed, call getSupportedStacks() again
try {
logger.info("Call getSupportedStacks() again.");
stacks = softwareManager.getSupportedStacks();
} catch (Exception ex) {
logger.error("Failed to get supported stacks from appmanager "
+ appManagerName + " again.", ex);
throw SoftwareManagerCollectorException.CONNECT_FAILURE(
appManagerName, ExceptionUtils.getRootCauseMessage(ex));
}
}
List<DistroRead> distros = new ArrayList<DistroRead>(stacks.size());
for (HadoopStack stack : stacks) {
distros.add(new DistroRead(stack));
}
return distros;
}
/**
* Get supported role information of a distro of a BDE appmanager
* @param appManagerName
* @param distroName
* @return The list of supported roles
*/
@RequestMapping(value = "/appmanager/{appManagerName}/distro/{distroName}/roles", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<String> getAppManagerDistroRoles(
@PathVariable("appManagerName") String appManagerName,
@PathVariable("distroName") String distroName) {
appManagerName = CommonUtil.decode(appManagerName);
if (CommonUtil.isBlank(appManagerName)
|| !CommonUtil.validateResourceName(appManagerName)) {
throw BddException.INVALID_PARAMETER("appmanager name", appManagerName);
}
SoftwareManager softMgr =
softwareManagerCollector.getSoftwareManager(appManagerName);
List<HadoopStack> stacks = softMgr.getSupportedStacks();
HadoopStack hadoopStack = null;
for (HadoopStack stack : stacks) {
if (distroName.equals(stack.getDistro())) {
hadoopStack = stack;
break;
}
}
if (hadoopStack == null) {
throw BddException.NOT_FOUND("Distro", distroName);
} else {
return hadoopStack.getRoles();
}
}
/**
* Get supported configuration information of a distro of a BDE appmanager
* @param appManagerName
* @param distroName
* @return The list of supported configurations
*/
@RequestMapping(value = "/appmanager/{appManagerName}/distro/{distroName}/configurations", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public String getAppManagerStackConfigurations(
@PathVariable("appManagerName") String appManagerName,
@PathVariable("distroName") String distroName) {
appManagerName = CommonUtil.decode(appManagerName);
if (CommonUtil.isBlank(appManagerName)
|| !CommonUtil.validateResourceName(appManagerName)) {
throw BddException.INVALID_PARAMETER("appmanager name", appManagerName);
}
SoftwareManager softMgr =
softwareManagerCollector.getSoftwareManager(appManagerName);
List<HadoopStack> stacks = softMgr.getSupportedStacks();
HadoopStack hadoopStack = null;
for (HadoopStack stack : stacks) {
if (distroName.equals(stack.getDistro())) {
hadoopStack = stack;
break;
}
}
if (hadoopStack == null) {
throw BddException.NOT_FOUND("distro", distroName);
} else {
return softMgr.getSupportedConfigs(hadoopStack);
}
}
@RequestMapping(value = "/appmanager/{appManagerName}/defaultdistro", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public DistroRead getDefaultStack(@PathVariable("appManagerName") String appManagerName) {
appManagerName = CommonUtil.decode(appManagerName);
if (CommonUtil.isBlank(appManagerName)
|| !CommonUtil.validateResourceName(appManagerName)) {
throw BddException.INVALID_PARAMETER("appmanager name", appManagerName);
}
SoftwareManager softMgr = softwareManagerCollector.getSoftwareManager(appManagerName);
HadoopStack stack = softMgr.getDefaultStack();
if (stack == null) {
return null;
} else {
return new DistroRead(stack);
}
}
@RequestMapping(value = "/appmanager/{appManagerName}/distro/{distroName:.+}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public DistroRead getDistroByName(
@PathVariable("appManagerName") String appManagerName,
@PathVariable("distroName") String distroName) {
appManagerName = CommonUtil.decode(appManagerName);
distroName = CommonUtil.decode(distroName);
if (CommonUtil.isBlank(appManagerName)
|| !CommonUtil.validateResourceName(appManagerName)) {
throw BddException.INVALID_PARAMETER("appmanager name", appManagerName);
}
SoftwareManager softMgr = softwareManagerCollector.getSoftwareManager(appManagerName);
HadoopStack stack = clusterMgr.filterDistroFromAppManager(softMgr, distroName);
if (stack == null) {
return null;
} else {
return new DistroRead(stack);
}
}
/**
* Get all BDE appmanagers' information
* @return The list of Application Managers in BDE
*/
@RequestMapping(value = "/appmanagers", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<AppManagerRead> getAppManagers() {
return softwareManagerCollector.getAllAppManagerReads();
}
/**
* Store rack list information into BDE for rack related support, such as hadoop rack awareness and node placement policies
* @param racksInfo A list of rack information
*/
@RequestMapping(value = "/racks", method = RequestMethod.PUT)
@ResponseStatus(HttpStatus.OK)
@RestCallPointcut
public void importRacks(@RequestBody final RackInfoList racksInfo)
throws Exception {
verifyInitialized();
if (racksInfo == null || racksInfo.size() == 0) {
throw BddException.INVALID_PARAMETER("rack list", "empty");
}
rackInfoManager.importRackInfo(racksInfo);
}
/**
* Get the rack list
* @return A list of rack information
*/
@RequestMapping(value = "/racks", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<RackInfo> exportRacks() throws Exception {
return rackInfoManager.exportRackInfo();
}
/**
* Get available distributions information
* @return A list of distribution information
*/
@RequestMapping(value = "/distros", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public List<DistroRead> getDistros() {
//TODO handle appmanager case
SoftwareManager softMgr =
softwareManagerCollector.getSoftwareManager(Constants.IRONFAN);
List<HadoopStack> stacks = softMgr.getSupportedStacks();
List<DistroRead> distros = new ArrayList<DistroRead>(stacks.size());
for (HadoopStack stack : stacks) {
distros.add(new DistroRead(stack));
}
return distros;
}
/**
* Get the distribution information by its name such as apache, bigtop, cdh, intel, gphd, hdp, mapr, phd,etc.
* @param distroName
* @return The distribution information
*/
@RequestMapping(value = "/distro/{distroName}", method = RequestMethod.GET, produces = "application/json")
@ResponseBody
@RestCallPointcut
public DistroRead getDistroByName(
@PathVariable("distroName") String distroName) {
if (CommonUtil.isBlank(distroName)
|| !CommonUtil.validateDistroName(distroName)) {
throw BddException.INVALID_PARAMETER("distro name", distroName);
}
//TODO handle appmanager case
SoftwareManager softMgr =
softwareManagerCollector.getSoftwareManager(Constants.IRONFAN);
List<HadoopStack> stacks = softMgr.getSupportedStacks();
for (HadoopStack stack : stacks) {
if (distroName.equalsIgnoreCase(stack.getDistro())) {
return new DistroRead(stack);
}
}
throw BddException.NOT_FOUND("Distro", distroName);
}
@RequestMapping(value = "/cluster/{clusterName}/validate", method = RequestMethod.POST, produces = "application/json")
@ResponseBody
public ValidateResult validateBlueprint(@RequestBody ClusterCreate createSpec) {
SoftwareManager softwareManager =
clusterMgr.getClusterConfigMgr().getSoftwareManager(
createSpec.getAppManager());
ClusterBlueprint blueprint = createSpec.toBlueprint();
ValidateResult result = new ValidateResult();
boolean validated = false;
try {
validated = softwareManager.validateBlueprint(blueprint);
} catch (ValidationException ve) {
result.setFailedMsgList(ve.getFailedMsgList());
result.setWarningMsgList(ve.getWarningMsgList());
}
result.setValidated(validated);
return result;
}
private void verifyInitialized() {
if (!clusteringService.isInited()) {
Throwable initErr = clusteringService.getInitError();
throw BddException.APP_INIT_ERROR(initErr, initErr.getMessage());
}
}
/**
* Recover clusters for disaster recovery from a data center to another
* @param vcResMap: vc resource name mapping from one data center to another
* @param request
*/
@RequestMapping(value = "/recover", method = RequestMethod.PUT, consumes = "application/json")
@ResponseStatus(HttpStatus.OK)
public void recoverClusters(@RequestBody VcResourceMap vcResMap,
HttpServletRequest request, HttpServletResponse response)
throws Exception {
List<VcClusterMap> clstMaps = null;
List<DatacenterMap> dcmaps = vcResMap.getDatacenters();
if ( null != dcmaps ) {
// currently for bde, only one data center is considered
DatacenterMap dcmap = dcmaps.get(0);
clstMaps = dcmap.getClusters();
// update all the resource pools on the vc cluster info from name mapping
List<ResourcePoolRead> allRps = vcRpSvc.getAllResourcePoolForRest();
for ( ResourcePoolRead rp : allRps ) {
String rpName = rp.getRpName();
String srcVcCluster = rp.getVcCluster();
for ( VcClusterMap clstmap : clstMaps ) {
String clst = clstmap.getSrc();
if ( srcVcCluster.equals(clst) ) {
vcRpSvc.updateResourcePool(rpName, clstmap.getTgt(), null);
}
}
}
}
// update all the nodes on vm moid and host_name
// if clstMaps is null, it means doing recover on the same data center
clusterMgr.recoverClusters(clstMaps);
}
}