001/* 002 * Stallion Core: A Modern Web Framework 003 * 004 * Copyright (C) 2015 - 2016 Stallion Software LLC. 005 * 006 * This program is free software: you can redistribute it and/or modify it under the terms of the 007 * GNU General Public License as published by the Free Software Foundation, either version 2 of 008 * the License, or (at your option) any later version. This program is distributed in the hope that 009 * it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of 010 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public 011 * License for more details. You should have received a copy of the GNU General Public License 012 * along with this program. If not, see <http://www.gnu.org/licenses/gpl-2.0.html>. 013 * 014 * 015 * 016 */ 017 018package io.stallion.monitoring; 019 020import com.mashape.unirest.http.HttpResponse; 021import com.mashape.unirest.http.Unirest; 022import com.mashape.unirest.http.exceptions.UnirestException; 023import io.stallion.asyncTasks.AsyncTaskController; 024import io.stallion.exceptions.ClientException; 025import io.stallion.exceptions.WebException; 026import io.stallion.jobs.JobCoordinator; 027import io.stallion.plugins.StallionJavaPlugin; 028import io.stallion.plugins.PluginRegistry; 029import io.stallion.restfulEndpoints.EndpointResource; 030import io.stallion.restfulEndpoints.MinRole; 031import io.stallion.restfulEndpoints.XSRF; 032import io.stallion.services.Log; 033import io.stallion.settings.Settings; 034import io.stallion.templating.TemplateRenderer; 035import io.stallion.users.Role; 036 037import javax.ws.rs.*; 038 039import java.io.IOException; 040import java.net.URL; 041import java.util.*; 042import java.util.jar.Manifest; 043 044import static io.stallion.utils.Literals.*; 045import static io.stallion.Context.*; 046 047@Path("/st-internal") 048public class InternalEndpoints implements EndpointResource { 049 050 051 @GET 052 @Path("/health") 053 public HealthInfo checkHealth(@QueryParam("secret") String secret, @QueryParam("failOnWarnings") Boolean failOnWarnings, @QueryParam("sections") String sections) { 054 failOnWarnings = or(failOnWarnings, false); 055 sections = or(sections, "all"); 056 checkSecret(secret); 057 HealthInfo info = buildHealthInfo(sections); 058 if (info.getErrors().size() > 0) { 059 response().setStatus(515); 060 } 061 if (failOnWarnings && info.getWarnings().size() > 0) { 062 response().setStatus(515); 063 } 064 info.setHttpStatusCode(response().getStatus()); 065 return info; 066 } 067 068 @POST 069 @Path("/run-job") 070 @XSRF(false) 071 public Object runJob(@QueryParam("secret") String secret, @QueryParam("job") String job) { 072 checkSecret(secret); 073 JobCoordinator.instance().forceRunJob(job, true); 074 return true; 075 } 076 077 078 private HealthInfo buildHealthInfo(String sectionsString) { 079 List<String> sections = Arrays.asList(sectionsString.split(",")); 080 if ("all".equals(sectionsString)) { 081 sections = list("http", "jobs", "tasks", "endpoints", "system"); 082 } 083 HealthInfo info = new HealthInfo(); 084 085 if (sections.contains("http")) { 086 info.setHttp(HealthTracker.instance().getHttpHealthInfo()); 087 } 088 if (sections.contains("jobs")) { 089 info.setJobs(JobCoordinator.instance().buildJobHealthInfos()); 090 } 091 if (sections.contains("tasks")) { 092 info.setTasks(AsyncTaskController.instance().buildHealthInfo()); 093 } 094 if (sections.contains("endpoints")) { 095 info.setEndpoints(checkEndpointHealth()); 096 } 097 if (sections.contains("system")) { 098 info.setSystem(new SystemHealth().hydrateSystemHealth()); 099 } 100 101 info.hydrateErrors(); 102 103 return info; 104 } 105 106 private void checkSecret(String secret) { 107 if (empty(settings().getHealthCheckSecret())) { 108 throw new ClientException("You must define a setting value for 'healthCheckSecret' in your stallion.toml. This should be a random string that only you know. Then you should pass this in via the query string parameter 'secret' or the header 'X-Healthcheck-Secret", 500); 109 } 110 if (empty(secret)) { 111 secret = request().getHeader("X-Healthcheck-Secret"); 112 } 113 if (empty(secret)) { 114 throw new ClientException("You must pass in a either a query param 'secret' or a header 'X-Healthcheck-Secret'. The passed in secret should match the value defined in stallion.toml for the setting name 'healthCheckSecret'. ", 403); 115 } 116 if (!secret.equals(Settings.instance().getHealthCheckSecret())) { 117 throw new ClientException("Invalid healthcheck secret"); 118 } 119 } 120 121 private List<EndpointHealthInfo> checkEndpointHealth() { 122 List<EndpointHealthInfo> infos = new ArrayList<>(); 123 for (String[] endPointArray: settings().getHealthCheckEndpoints()) { 124 EndpointHealthInfo endPointHealth = new EndpointHealthInfo(); 125 infos.add(endPointHealth); 126 endPointHealth.setUrl(endPointArray[0]); 127 HttpResponse<String> httpResponse = null; 128 try { 129 httpResponse = Unirest.get("http://localhost:" + settings().getPort() + "" + endPointArray[0]).asString(); 130 } catch (UnirestException e) { 131 endPointHealth.setStatusCode(999); 132 continue; 133 } 134 endPointHealth.setStatusCode(httpResponse.getStatus()); 135 if (endPointArray.length > 1) { 136 endPointHealth.setFoundString(httpResponse.getBody().contains(endPointArray[1])); 137 } else { 138 endPointHealth.setFoundString(true); 139 } 140 } 141 return infos; 142 } 143 144 @GET 145 @Path("/info") 146 public SystemInformation getInfo(@QueryParam("secret") String secret) { 147 checkSecret(secret); 148 SystemInformation info = new SystemInformation(); 149 info.setTargetPath(settings().getTargetFolder()); 150 info.setxRealIp(request().getHeader("X-Real-Ip")); 151 info.setxForwardedFor(request().getHeader("x-Fowarded-For")); 152 info.setGuessedHost(request().getHost()); 153 info.setGuessedScheme(request().getScheme()); 154 info.setxForwardedProto(request().getHeader("X-Forwarded-Proto")); 155 info.setRemoteAddr(request().getRemoteAddr()); 156 info.setDeployDate(System.getenv("STALLION_DEPLOY_TIME")); 157 info.setGuessedIp(request().getActualIp()); 158 info.setInstanceHostName(System.getenv("STALLION_HOST")); 159 info.setInstanceDomain(System.getenv("STALLION_DOMAIN")); 160 info.setxForwardedHost(request().getHeader("x-forwarded-host")); 161 info.setxUpstreamForwardedProto(request().getHeader("x-upstream-forwarded-proto")); 162 info.setEnv(Settings.instance().getEnv()); 163 info.setPort(settings().getPort()); 164 List<ClassLoader> loaders = new ArrayList<>(); 165 loaders.add(getClass().getClassLoader()); 166 for (StallionJavaPlugin booter: PluginRegistry.instance().getJavaPluginByName().values()) { 167 loaders.add(booter.getClass().getClassLoader()); 168 } 169 for (ClassLoader loader: loaders) { 170 try { 171 Enumeration<URL> resources = loader.getResources("META-INF/MANIFEST.MF"); 172 for (Integer i : safeLoop(1000)) { 173 if (!resources.hasMoreElements()) { 174 break; 175 } 176 URL url = resources.nextElement(); 177 Manifest manifest = new Manifest(url.openStream()); 178 String buildTime = manifest.getMainAttributes().getValue("Build-Time"); 179 String key = url.toString(); 180 if (!empty(buildTime)) { 181 info.getJarBuildDates().put(key, buildTime); 182 } 183 } 184 } catch (IOException ex) { 185 Log.exception(ex, "Error loading MANIFEST.MF"); 186 } 187 } 188 return info; 189 } 190 191 @GET 192 @Path("/exceptions") 193 @MinRole(value = Role.ADMIN, redirect = true) 194 @Produces("text/html") 195 public String viewExceptions() { 196 Map<String, Object> context = map(); 197 ArrayList<ExceptionInfo> exceptions = new ArrayList<>(); 198 for(ExceptionInfo info: HealthTracker.instance().getExceptionQueue()) { 199 exceptions.add(info); 200 } 201 Collections.reverse(exceptions); 202 context.put("exceptions", exceptions); 203 return TemplateRenderer.instance().renderTemplate(getClass().getResource("/templates/exceptions.jinja"), context); 204 } 205 206 @POST 207 @Produces("text/html") 208 @Path("/force-exception") 209 public String forceException(@QueryParam("secret") String secret) { 210 checkSecret(secret); 211 throw new WebException("A forced exception!", 500); 212 } 213 214 @GET 215 @Produces("text/html") 216 @Path("/force-exception-get") 217 public String forceExceptionGet(@QueryParam("secret") String secret) { 218 checkSecret(secret); 219 throw new WebException("A forced exception!", 500); 220 } 221}