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