/* * Copyright 2013-2015 the original author or 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 org.springframework.xd.dirt.rest; import java.util.HashMap; import java.util.Map; import java.util.StringTokenizer; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.ExposesResourceFor; import org.springframework.hateoas.PagedResources; import org.springframework.hateoas.mvc.ResourceAssemblerSupport; import org.springframework.http.HttpStatus; import org.springframework.http.client.SimpleClientHttpRequestFactory; import org.springframework.stereotype.Controller; import org.springframework.util.StringUtils; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.client.RestClientException; import org.springframework.web.client.RestTemplate; import org.springframework.xd.dirt.cluster.Container; import org.springframework.xd.dirt.cluster.ContainerShutdownException; import org.springframework.xd.dirt.cluster.ModuleMessageRateNotFoundException; import org.springframework.xd.dirt.cluster.NoSuchContainerException; import org.springframework.xd.dirt.container.store.ContainerRepository; import org.springframework.xd.dirt.container.store.DetailedContainer; import org.springframework.xd.rest.domain.DetailedContainerResource; /** * Handles interaction with the runtime containers/and its modules. * * @author Ilayaperumal Gopinathan * @author Mark Fisher * @author Gunnar Hillert */ @Controller @RequestMapping("/runtime/containers") @ExposesResourceFor(DetailedContainerResource.class) public class ContainersController { /** * Logger. */ private final Logger logger = LoggerFactory.getLogger(this.getClass()); @Autowired private ContainerRepository containerRepository; private ResourceAssemblerSupport<DetailedContainer, DetailedContainerResource> resourceAssembler = new RuntimeContainerResourceAssembler(); private RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory()); @Value("${management.contextPath:/management}") private String managementContextPath; @Value("${xd.messageRateMonitoring.enabled:false}") private String enableMessageRates; private final static String CONTAINER_HOST_URI_PROTOCOL = "http://"; private final static String SHUTDOWN_ENDPOINT = "/shutdown"; private final static String JOLOKIA_XD_MODULE_MBEAN_URL = "/management/jolokia/read/xd.*:module=*,component=*,name=*"; private final static String INPUT_CHANNEL_NAME = "input"; private final static String OUTPUT_CHANNEL_NAME = "output"; @Autowired public ContainersController(ContainerRepository containerRepository) { this.containerRepository = containerRepository; } /** * List all the available containers along with the message rates for * each deployed modules. * * @throws ModuleMessageRateNotFoundException * @throws JSONException */ @RequestMapping(value = "", method = RequestMethod.GET) @ResponseStatus(HttpStatus.OK) @ResponseBody public PagedResources<DetailedContainerResource> list(Pageable pageable, PagedResourcesAssembler<DetailedContainer> assembler) throws ModuleMessageRateNotFoundException { Page<DetailedContainer> containers = containerRepository.findAllRuntimeContainers(pageable, true); for (DetailedContainer container : containers) { if (!container.getDeployedModules().isEmpty()) { setMessageRates(container); } } return assembler.toResource(containers, resourceAssembler); } /** * Set the message rates of all the deployed modules in the given container. * * @param container the container to set the message rates */ private void setMessageRates(DetailedContainer container) { String containerHost = container.getAttributes().getIp(); String containerManagementPort = container.getAttributes().getManagementPort(); if (StringUtils.hasText(containerManagementPort) && enableMessageRates.equalsIgnoreCase("true")) { Map<String, HashMap<String, Double>> messageRates = new HashMap<String, HashMap<String, Double>>(); String request = String.format("%s%s:%s%s", CONTAINER_HOST_URI_PROTOCOL, containerHost, containerManagementPort, JOLOKIA_XD_MODULE_MBEAN_URL); try { String response = restTemplate.getForObject(request, String.class).toString(); JSONObject jObject = new JSONObject(response); JSONObject value = jObject.getJSONObject("value"); JSONArray jsonArray = value.names(); // iterate over each module MBean for (int i = 0; i < jsonArray.length(); i++) { String mbeanKey = (String) jsonArray.get(i); StringTokenizer tokenizer = new StringTokenizer(mbeanKey, ","); if (mbeanKey.contains("component=MessageChannel") && (mbeanKey.contains("name=" + INPUT_CHANNEL_NAME) || mbeanKey.contains("name=" + OUTPUT_CHANNEL_NAME))) { while (tokenizer.hasMoreElements()) { String element = (String) tokenizer.nextElement(); if (element.startsWith("module=")) { String key = String.format("%s", element.substring(element.indexOf("=") + 1)); HashMap<String, Double> rate = (messageRates.get(key) != null) ? messageRates.get(key) : new HashMap<String, Double>(); Double rateValue = (Double) value.getJSONObject((String) jsonArray.get(i)).get( "MeanSendRate"); if (mbeanKey.contains("name=" + INPUT_CHANNEL_NAME)) { rate.put(INPUT_CHANNEL_NAME, rateValue); } else if (mbeanKey.contains("name=" + OUTPUT_CHANNEL_NAME)) { rate.put(OUTPUT_CHANNEL_NAME, rateValue); } messageRates.put(key, rate); } } } } container.setMessageRates(messageRates); } catch (RestClientException e) { logger.warn(String.format("Error getting message rate metrics for %s", container.getName()), e); } catch (JSONException jse) { logger.warn(String.format("Error getting message rate metrics for %s", container.getName()), jse); } } } /** * Shutdown container by the given containerId. * * @throws NoSuchContainerException */ @RequestMapping(value = "", method = RequestMethod.DELETE, params = "containerId") @ResponseStatus(HttpStatus.OK) public void shutdownContainer(String containerId) throws NoSuchContainerException, ContainerShutdownException { Container container = this.containerRepository.findOne(containerId); if (container != null) { String containerHost = container.getAttributes().getIp(); String containerManagementPort = container.getAttributes().getManagementPort(); RestTemplate restTemplate = new RestTemplate(new SimpleClientHttpRequestFactory()); try { restTemplate.postForObject(CONTAINER_HOST_URI_PROTOCOL + containerHost + ":" + containerManagementPort + managementContextPath + SHUTDOWN_ENDPOINT, Object.class, Object.class); } catch (RestClientException e) { throw new ContainerShutdownException(e.getMessage()); } } else { throw new NoSuchContainerException("Container could not be found with id " + containerId); } } }