/** * 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.hadoop.conf; import org.apache.commons.logging.*; import org.apache.commons.lang.StringEscapeUtils; import java.util.Collection; import java.util.Enumeration; import java.io.IOException; import java.io.PrintWriter; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.hadoop.util.StringUtils; /** * A servlet for changing a node's configuration. * * Reloads the configuration file, verifies whether changes are * possible and asks the admin to approve the change. * */ public class ReconfigurationServlet extends HttpServlet { private static final long serialVersionUID = 1L; private static final Log LOG = LogFactory.getLog(ReconfigurationServlet.class); // the prefix used to fing the attribute holding the reconfigurable // for a given request // // we get the attribute prefix + servlet path public static final String CONF_SERVLET_RECONFIGURABLE_PREFIX = "conf.servlet.reconfigurable."; @Override public void init() throws ServletException { super.init(); } private Reconfigurable getReconfigurable(HttpServletRequest req) { LOG.info("servlet path: " + req.getServletPath()); LOG.info("getting attribute: " + CONF_SERVLET_RECONFIGURABLE_PREFIX + req.getServletPath()); return (Reconfigurable) this.getServletContext().getAttribute(CONF_SERVLET_RECONFIGURABLE_PREFIX + req.getServletPath()); } private void printHeader(PrintWriter out, String nodeName) { out.print("<html><head>"); out.printf("<title>%s Reconfiguration Utility</title>\n", StringEscapeUtils.escapeHtml(nodeName)); out.print("</head><body>\n"); out.printf("<h1>%s Reconfiguration Utility</h1>\n", StringEscapeUtils.escapeHtml(nodeName)); } private void printFooter(PrintWriter out) { out.print("</body></html>\n"); } /** * Print configuration options that can be changed. */ private void printConf(PrintWriter out, Reconfigurable reconf) { Configuration oldConf = reconf.getConf(); Configuration newConf = new Configuration(); Collection<ReconfigurationUtil.PropertyChange> changes = ReconfigurationUtil.getChangedProperties(newConf, oldConf); boolean changeOK = true; out.println("<form action=\"\" method=\"post\">"); out.println("<table border=\"1\">"); out.println("<tr><th>Property</th><th>Old value</th>"); out.println("<th>New value </th><th></th></tr>"); for (ReconfigurationUtil.PropertyChange c: changes) { out.print("<tr><td>"); if (!reconf.isPropertyReconfigurable(c.prop)) { out.print("<font color=\"red\">" + StringEscapeUtils.escapeHtml(c.prop) + "</font>"); changeOK = false; } else { out.print(StringEscapeUtils.escapeHtml(c.prop)); out.print("<input type=\"hidden\" name=\"" + StringEscapeUtils.escapeHtml(c.prop) + "\" value=\"" + StringEscapeUtils.escapeHtml(c.newVal) + "\"/>"); } out.print("</td><td>" + (c.oldVal == null ? "<it>default</it>" : StringEscapeUtils.escapeHtml(c.oldVal)) + "</td><td>" + (c.newVal == null ? "<it>default</it>" : StringEscapeUtils.escapeHtml(c.newVal)) + "</td>"); out.print("</tr>\n"); } out.println("</table>"); if (!changeOK) { out.println("<p><font color=\"red\">WARNING: properties marked red" + " will not be changed until the next restart.</font></p>"); } out.println("<input type=\"submit\" value=\"Apply\" />"); out.println("</form>"); } @SuppressWarnings("unchecked") private Enumeration<String> getParams(HttpServletRequest req) { return (Enumeration<String>) req.getParameterNames(); } /** * Apply configuratio changes after admin has approved them. */ private void applyChanges(PrintWriter out, Reconfigurable reconf, HttpServletRequest req) throws IOException, ReconfigurationException { Configuration oldConf = reconf.getConf(); Configuration newConf = new Configuration(); Enumeration<String> params = getParams(req); synchronized(oldConf) { while (params.hasMoreElements()) { String rawParam = params.nextElement(); String param = StringEscapeUtils.unescapeHtml(rawParam); String value = StringEscapeUtils.unescapeHtml(req.getParameter(rawParam)); if (value != null) { if (value.equals(newConf.getRaw(param)) || value.equals("default") || value.equals("null") || value.isEmpty()) { if ((value.equals("default") || value.equals("null") || value.isEmpty()) && oldConf.getRaw(param) != null) { out.println("<p>Changed \"" + StringEscapeUtils.escapeHtml(param) + "\" from \"" + StringEscapeUtils.escapeHtml(oldConf.getRaw(param)) + "\" to default</p>"); reconf.reconfigureProperty(param, null); } else if (!value.equals("default") && !value.equals("null") && !value.isEmpty() && (oldConf.getRaw(param) == null || !oldConf.getRaw(param).equals(value))) { // change from default or value to different value if (oldConf.getRaw(param) == null) { out.println("<p>Changed \"" + StringEscapeUtils.escapeHtml(param) + "\" from default to \"" + StringEscapeUtils.escapeHtml(value) + "\"</p>"); } else { out.println("<p>Changed \"" + StringEscapeUtils.escapeHtml(param) + "\" from \"" + StringEscapeUtils.escapeHtml(oldConf. getRaw(param)) + "\" to \"" + StringEscapeUtils.escapeHtml(value) + "\"</p>"); } reconf.reconfigureProperty(param, value); } else { LOG.info("property " + param + " unchanged"); } } else { // parameter value != newConf value out.println("<p>\"" + StringEscapeUtils.escapeHtml(param) + "\" not changed because value has changed from \"" + StringEscapeUtils.escapeHtml(value) + "\" to \"" + StringEscapeUtils.escapeHtml(newConf.getRaw(param)) + "\" since approval</p>"); } } } } } @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { LOG.info("GET"); PrintWriter out = resp.getWriter(); Reconfigurable reconf = getReconfigurable(req); String nodeName = reconf.getClass().getCanonicalName(); printHeader(out, nodeName); printConf(out, reconf); printFooter(out); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { LOG.info("POST"); PrintWriter out = resp.getWriter(); Reconfigurable reconf = getReconfigurable(req); String nodeName = reconf.getClass().getCanonicalName(); printHeader(out, nodeName); try { applyChanges(out, reconf, req); } catch (ReconfigurationException e) { resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, StringUtils.stringifyException(e)); return; } out.println("<p><a href=\"" + req.getServletPath() + "\">back</a></p>"); printFooter(out); } }