/* * Licensed to the Apache Software Foundation (ASF) under one or more contributor license * agreements. See the NOTICE file distributed with this work for additional information regarding * copyright ownership. The ASF licenses this file to You 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.apache.geode.management.internal; import java.net.UnknownHostException; import org.apache.commons.lang.StringUtils; import org.apache.logging.log4j.Logger; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.ServerConnector; import org.apache.geode.cache.AttributesFactory; import org.apache.geode.cache.CacheFactory; import org.apache.geode.cache.DataPolicy; import org.apache.geode.cache.RegionAttributes; import org.apache.geode.cache.Scope; import org.apache.geode.distributed.internal.DistributionConfig; import org.apache.geode.internal.GemFireVersion; import org.apache.geode.internal.net.SSLConfigurationFactory; import org.apache.geode.internal.net.SocketCreator; import org.apache.geode.internal.cache.GemFireCacheImpl; import org.apache.geode.internal.cache.InternalRegionArguments; import org.apache.geode.internal.logging.LogService; import org.apache.geode.internal.security.SecurableCommunicationChannel; import org.apache.geode.management.ManagementService; /** * Agent implementation that controls the HTTP server end points used for REST clients to connect * gemfire data node. * <p> * The RestAgent is used to start http service in embedded mode on any non manager data node with * developer REST APIs service enabled. * * @since GemFire 8.0 */ public class RestAgent { private static final Logger logger = LogService.getLogger(); private boolean running = false; private final DistributionConfig config; public RestAgent(DistributionConfig config) { this.config = config; } public synchronized boolean isRunning() { return this.running; } private boolean isManagementRestServiceRunning(GemFireCacheImpl cache) { final SystemManagementService managementService = (SystemManagementService) ManagementService.getManagementService(cache); return (managementService.getManagementAgent() != null && managementService.getManagementAgent().isHttpServiceRunning()); } public synchronized void start(GemFireCacheImpl cache) { if (!this.running && this.config.getHttpServicePort() != 0 && !isManagementRestServiceRunning(cache)) { try { startHttpService(); this.running = true; cache.setRESTServiceRunning(true); // create region to hold query information (queryId, queryString). Added // for the developer REST APIs RestAgent.createParameterizedQueryRegion(); } catch (RuntimeException e) { logger.debug(e.getMessage(), e); } } } public synchronized void stop() { if (this.running) { stopHttpService(); if (logger.isDebugEnabled()) { logger.debug("Gemfire Rest Http service stopped"); } this.running = false; } else { if (logger.isDebugEnabled()) { logger.debug("Attempt to stop Gemfire Rest Http service which is not running"); } } } private Server httpServer; private final String GEMFIRE_VERSION = GemFireVersion.getGemFireVersion(); private AgentUtil agentUtil = new AgentUtil(GEMFIRE_VERSION); private boolean isRunningInTomcat() { return (System.getProperty("catalina.base") != null || System.getProperty("catalina.home") != null); } // Start HTTP service in embedded mode public void startHttpService() { // TODO: add a check that will make sure that we start HTTP service on // non-manager data node String httpServiceBindAddress = getBindAddressForHttpService(this.config); logger.info("Attempting to start HTTP service on port ({}) at bind-address ({})...", this.config.getHttpServicePort(), httpServiceBindAddress); // Find the developer REST WAR file final String gemfireAPIWar = agentUtil.findWarLocation("geode-web-api"); if (gemfireAPIWar == null) { logger.info( "Unable to find GemFire Developer REST API WAR file; the Developer REST Interface for GemFire will not be accessible."); } try { // Check if we're already running inside Tomcat if (isRunningInTomcat()) { logger.warn( "Detected presence of catalina system properties. HTTP service will not be started. To enable the GemFire Developer REST API, please deploy the /geode-web-api WAR file in your application server."); } else if (agentUtil.isWebApplicationAvailable(gemfireAPIWar)) { final int port = this.config.getHttpServicePort(); this.httpServer = JettyHelper.initJetty(httpServiceBindAddress, port, SSLConfigurationFactory.getSSLConfigForComponent(SecurableCommunicationChannel.WEB)); this.httpServer = JettyHelper.addWebApplication(httpServer, "/gemfire-api", gemfireAPIWar); this.httpServer = JettyHelper.addWebApplication(httpServer, "/geode", gemfireAPIWar); if (logger.isDebugEnabled()) { logger.info("Starting HTTP embedded server on port ({}) at bind-address ({})...", ((ServerConnector) this.httpServer.getConnectors()[0]).getPort(), httpServiceBindAddress); } this.httpServer = JettyHelper.startJetty(this.httpServer); logger.info("HTTP service started successfully...!!"); } } catch (Exception e) { stopHttpService();// Jetty needs to be stopped even if it has failed to // start. Some of the threads are left behind even if // server.start() fails due to an exception throw new RuntimeException("HTTP service failed to start due to " + e.getMessage()); } } public static String getBindAddressForHttpService(DistributionConfig config) { String bindAddress = config.getHttpServiceBindAddress(); if (!StringUtils.isBlank(bindAddress)) return bindAddress; bindAddress = config.getServerBindAddress(); if (!StringUtils.isBlank(bindAddress)) return bindAddress; bindAddress = config.getBindAddress(); if (!StringUtils.isBlank(bindAddress)) return bindAddress; try { bindAddress = SocketCreator.getLocalHost().getHostAddress(); logger.info("RestAgent.getBindAddressForHttpService.localhost: " + SocketCreator.getLocalHost().getHostAddress()); } catch (UnknownHostException e) { logger.error("LocalHost could not be found.", e); } return bindAddress; } private void stopHttpService() { if (this.httpServer != null) { logger.info("Stopping the HTTP service..."); try { this.httpServer.stop(); } catch (Exception e) { logger.warn("Failed to stop the HTTP service because: {}", e.getMessage(), e); } finally { try { this.httpServer.destroy(); } catch (Exception ignore) { logger.error("Failed to properly release resources held by the HTTP service: {}", ignore.getMessage(), ignore); } finally { this.httpServer = null; System.clearProperty("catalina.base"); System.clearProperty("catalina.home"); } } } } /** * This method will create a REPLICATED region named _ParameterizedQueries__. In developer REST * APIs, this region will be used to store the queryId and queryString as a key and value * respectively. */ public static void createParameterizedQueryRegion() { try { if (logger.isDebugEnabled()) { logger.debug("Starting creation of __ParameterizedQueries__ region"); } GemFireCacheImpl cache = (GemFireCacheImpl) CacheFactory.getAnyInstance(); if (cache != null) { // cache.getCacheConfig().setPdxReadSerialized(true); final InternalRegionArguments regionArguments = new InternalRegionArguments(); regionArguments.setIsUsedForMetaRegion(true); final AttributesFactory<String, String> attributesFactory = new AttributesFactory<String, String>(); attributesFactory.setConcurrencyChecksEnabled(false); attributesFactory.setDataPolicy(DataPolicy.REPLICATE); attributesFactory.setKeyConstraint(String.class); attributesFactory.setScope(Scope.DISTRIBUTED_ACK); attributesFactory.setStatisticsEnabled(false); attributesFactory.setValueConstraint(String.class); final RegionAttributes<String, String> regionAttributes = attributesFactory.create(); cache.createVMRegion("__ParameterizedQueries__", regionAttributes, regionArguments); if (logger.isDebugEnabled()) { logger.debug("Successfully created __ParameterizedQueries__ region"); } } else { logger.error("Cannot create ParameterizedQueries Region as no cache found!"); } } catch (Exception e) { if (logger.isDebugEnabled()) { logger.debug("Error creating __ParameterizedQueries__ Region with cause {}", e.getMessage(), e); } } } }