001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019package org.apache.hadoop.conf;
020
021import org.apache.commons.logging.*;
022
023import org.apache.commons.lang.StringEscapeUtils;
024
025import java.util.Collection;
026import java.util.Enumeration;
027import java.io.IOException;
028import java.io.PrintWriter;
029
030import javax.servlet.ServletException;
031import javax.servlet.http.HttpServlet;
032import javax.servlet.http.HttpServletRequest;
033import javax.servlet.http.HttpServletResponse;
034
035import org.apache.hadoop.util.StringUtils;
036
037/**
038 * A servlet for changing a node's configuration.
039 *
040 * Reloads the configuration file, verifies whether changes are
041 * possible and asks the admin to approve the change.
042 *
043 */
044public class ReconfigurationServlet extends HttpServlet {
045  
046  private static final long serialVersionUID = 1L;
047
048  private static final Log LOG =
049    LogFactory.getLog(ReconfigurationServlet.class);
050
051  // the prefix used to fing the attribute holding the reconfigurable 
052  // for a given request
053  //
054  // we get the attribute prefix + servlet path
055  public static final String CONF_SERVLET_RECONFIGURABLE_PREFIX =
056    "conf.servlet.reconfigurable.";
057  
058  @Override
059  public void init() throws ServletException {
060    super.init();
061  }
062
063  private Reconfigurable getReconfigurable(HttpServletRequest req) {
064    LOG.info("servlet path: " + req.getServletPath());
065    LOG.info("getting attribute: " + CONF_SERVLET_RECONFIGURABLE_PREFIX +
066             req.getServletPath());
067    return (Reconfigurable)
068      this.getServletContext().getAttribute(CONF_SERVLET_RECONFIGURABLE_PREFIX +
069                                            req.getServletPath());
070  }
071
072  private void printHeader(PrintWriter out, String nodeName) {
073    out.print("<html><head>");
074    out.printf("<title>%s Reconfiguration Utility</title>%n",
075               StringEscapeUtils.escapeHtml(nodeName));
076    out.print("</head><body>\n");
077    out.printf("<h1>%s Reconfiguration Utility</h1>%n",
078               StringEscapeUtils.escapeHtml(nodeName));
079  }
080
081  private void printFooter(PrintWriter out) {
082    out.print("</body></html>\n");
083  }
084  
085  /**
086   * Print configuration options that can be changed.
087   */
088  private void printConf(PrintWriter out, Reconfigurable reconf) {
089    Configuration oldConf = reconf.getConf();
090    Configuration newConf = new Configuration();
091
092    Collection<ReconfigurationUtil.PropertyChange> changes = 
093      ReconfigurationUtil.getChangedProperties(newConf, 
094                                               oldConf);
095
096    boolean changeOK = true;
097    
098    out.println("<form action=\"\" method=\"post\">");
099    out.println("<table border=\"1\">");
100    out.println("<tr><th>Property</th><th>Old value</th>");
101    out.println("<th>New value </th><th></th></tr>");
102    for (ReconfigurationUtil.PropertyChange c: changes) {
103      out.print("<tr><td>");
104      if (!reconf.isPropertyReconfigurable(c.prop)) {
105        out.print("<font color=\"red\">" + 
106                  StringEscapeUtils.escapeHtml(c.prop) + "</font>");
107        changeOK = false;
108      } else {
109        out.print(StringEscapeUtils.escapeHtml(c.prop));
110        out.print("<input type=\"hidden\" name=\"" +
111                  StringEscapeUtils.escapeHtml(c.prop) + "\" value=\"" +
112                  StringEscapeUtils.escapeHtml(c.newVal) + "\"/>");
113      }
114      out.print("</td><td>" +
115                (c.oldVal == null ? "<it>default</it>" : 
116                 StringEscapeUtils.escapeHtml(c.oldVal)) +
117                "</td><td>" +
118                (c.newVal == null ? "<it>default</it>" : 
119                 StringEscapeUtils.escapeHtml(c.newVal)) +
120                "</td>");
121      out.print("</tr>\n");
122    }
123    out.println("</table>");
124    if (!changeOK) {
125      out.println("<p><font color=\"red\">WARNING: properties marked red" +
126                  " will not be changed until the next restart.</font></p>");
127    }
128    out.println("<input type=\"submit\" value=\"Apply\" />");
129    out.println("</form>");
130  }
131
132  @SuppressWarnings("unchecked")
133  private Enumeration<String> getParams(HttpServletRequest req) {
134    return req.getParameterNames();
135  }
136
137  /**
138   * Apply configuratio changes after admin has approved them.
139   */
140  private void applyChanges(PrintWriter out, Reconfigurable reconf,
141      HttpServletRequest req) throws ReconfigurationException {
142    Configuration oldConf = reconf.getConf();
143    Configuration newConf = new Configuration();
144
145    Enumeration<String> params = getParams(req);
146
147    synchronized(oldConf) {
148      while (params.hasMoreElements()) {
149        String rawParam = params.nextElement();
150        String param = StringEscapeUtils.unescapeHtml(rawParam);
151        String value =
152          StringEscapeUtils.unescapeHtml(req.getParameter(rawParam));
153        if (value != null) {
154          if (value.equals(newConf.getRaw(param)) || value.equals("default") ||
155              value.equals("null") || value.isEmpty()) {
156            if ((value.equals("default") || value.equals("null") || 
157                 value.isEmpty()) && 
158                oldConf.getRaw(param) != null) {
159              out.println("<p>Changed \"" + 
160                          StringEscapeUtils.escapeHtml(param) + "\" from \"" +
161                          StringEscapeUtils.escapeHtml(oldConf.getRaw(param)) +
162                          "\" to default</p>");
163              reconf.reconfigureProperty(param, null);
164            } else if (!value.equals("default") && !value.equals("null") &&
165                       !value.isEmpty() && 
166                       (oldConf.getRaw(param) == null || 
167                        !oldConf.getRaw(param).equals(value))) {
168              // change from default or value to different value
169              if (oldConf.getRaw(param) == null) {
170                out.println("<p>Changed \"" + 
171                            StringEscapeUtils.escapeHtml(param) + 
172                            "\" from default to \"" +
173                            StringEscapeUtils.escapeHtml(value) + "\"</p>");
174              } else {
175                out.println("<p>Changed \"" + 
176                            StringEscapeUtils.escapeHtml(param) + "\" from \"" +
177                            StringEscapeUtils.escapeHtml(oldConf.
178                                                         getRaw(param)) +
179                            "\" to \"" +
180                            StringEscapeUtils.escapeHtml(value) + "\"</p>");
181              }
182              reconf.reconfigureProperty(param, value);
183            } else {
184              LOG.info("property " + param + " unchanged");
185            }
186          } else {
187            // parameter value != newConf value
188            out.println("<p>\"" + StringEscapeUtils.escapeHtml(param) + 
189                        "\" not changed because value has changed from \"" +
190                        StringEscapeUtils.escapeHtml(value) + "\" to \"" +
191                        StringEscapeUtils.escapeHtml(newConf.getRaw(param)) +
192                        "\" since approval</p>");
193          }
194        }
195      }
196    }
197  }
198
199  @Override
200  protected void doGet(HttpServletRequest req, HttpServletResponse resp)
201    throws ServletException, IOException {
202    LOG.info("GET");
203    resp.setContentType("text/html");
204    PrintWriter out = resp.getWriter();
205    
206    Reconfigurable reconf = getReconfigurable(req);
207    String nodeName = reconf.getClass().getCanonicalName();
208
209    printHeader(out, nodeName);
210    printConf(out, reconf);
211    printFooter(out);
212  }
213
214  @Override
215  protected void doPost(HttpServletRequest req, HttpServletResponse resp)
216    throws ServletException, IOException {
217    LOG.info("POST");
218    resp.setContentType("text/html");
219    PrintWriter out = resp.getWriter();
220
221    Reconfigurable reconf = getReconfigurable(req);
222    String nodeName = reconf.getClass().getCanonicalName();
223
224    printHeader(out, nodeName);
225
226    try { 
227      applyChanges(out, reconf, req);
228    } catch (ReconfigurationException e) {
229      resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, 
230                     StringUtils.stringifyException(e));
231      return;
232    }
233
234    out.println("<p><a href=\"" + req.getServletPath() + "\">back</a></p>");
235    printFooter(out);
236  }
237
238}