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 com.google.common.annotations.VisibleForTesting;
022import com.google.common.base.Optional;
023import com.google.common.base.Preconditions;
024import com.google.common.collect.Maps;
025import org.apache.commons.logging.*;
026import org.apache.hadoop.util.Time;
027import org.apache.hadoop.conf.ReconfigurationUtil.PropertyChange;
028
029import java.io.IOException;
030import java.util.Collection;
031import java.util.Collections;
032import java.util.Map;
033
034/**
035 * Utility base class for implementing the Reconfigurable interface.
036 *
037 * Subclasses should override reconfigurePropertyImpl to change individual
038 * properties and getReconfigurableProperties to get all properties that
039 * can be changed at run time.
040 */
041public abstract class ReconfigurableBase 
042  extends Configured implements Reconfigurable {
043  
044  private static final Log LOG =
045    LogFactory.getLog(ReconfigurableBase.class);
046  // Use for testing purpose.
047  private ReconfigurationUtil reconfigurationUtil = new ReconfigurationUtil();
048
049  /** Background thread to reload configuration. */
050  private Thread reconfigThread = null;
051  private volatile boolean shouldRun = true;
052  private Object reconfigLock = new Object();
053
054  /**
055   * The timestamp when the <code>reconfigThread</code> starts.
056   */
057  private long startTime = 0;
058
059  /**
060   * The timestamp when the <code>reconfigThread</code> finishes.
061   */
062  private long endTime = 0;
063
064  /**
065   * A map of <changed property, error message>. If error message is present,
066   * it contains the messages about the error occurred when applies the particular
067   * change. Otherwise, it indicates that the change has been successfully applied.
068   */
069  private Map<PropertyChange, Optional<String>> status = null;
070
071  /**
072   * Construct a ReconfigurableBase.
073   */
074  public ReconfigurableBase() {
075    super(new Configuration());
076  }
077
078  /**
079   * Construct a ReconfigurableBase with the {@link Configuration}
080   * conf.
081   */
082  public ReconfigurableBase(Configuration conf) {
083    super((conf == null) ? new Configuration() : conf);
084  }
085
086  @VisibleForTesting
087  public void setReconfigurationUtil(ReconfigurationUtil ru) {
088    reconfigurationUtil = Preconditions.checkNotNull(ru);
089  }
090
091  @VisibleForTesting
092  public Collection<PropertyChange> getChangedProperties(
093      Configuration newConf, Configuration oldConf) {
094    return reconfigurationUtil.parseChangedProperties(newConf, oldConf);
095  }
096
097  /**
098   * A background thread to apply configuration changes.
099   */
100  private static class ReconfigurationThread extends Thread {
101    private ReconfigurableBase parent;
102
103    ReconfigurationThread(ReconfigurableBase base) {
104      this.parent = base;
105    }
106
107    // See {@link ReconfigurationServlet#applyChanges}
108    public void run() {
109      LOG.info("Starting reconfiguration task.");
110      Configuration oldConf = this.parent.getConf();
111      Configuration newConf = new Configuration();
112      Collection<PropertyChange> changes =
113          this.parent.getChangedProperties(newConf, oldConf);
114      Map<PropertyChange, Optional<String>> results = Maps.newHashMap();
115      ConfigRedactor oldRedactor = new ConfigRedactor(oldConf);
116      ConfigRedactor newRedactor = new ConfigRedactor(newConf);
117      for (PropertyChange change : changes) {
118        String errorMessage = null;
119        String oldValRedacted = oldRedactor.redact(change.prop, change.oldVal);
120        String newValRedacted = newRedactor.redact(change.prop, change.newVal);
121        if (!this.parent.isPropertyReconfigurable(change.prop)) {
122          errorMessage = "Property " + change.prop +
123              " is not reconfigurable";
124          LOG.info(errorMessage);
125          results.put(change, Optional.of(errorMessage));
126          continue;
127        }
128        LOG.info("Change property: " + change.prop + " from \""
129            + ((change.oldVal == null) ? "<default>" : oldValRedacted)
130            + "\" to \""
131            + ((change.newVal == null) ? "<default>" : newValRedacted)
132            + "\".");
133        try {
134          this.parent.reconfigurePropertyImpl(change.prop, change.newVal);
135        } catch (ReconfigurationException e) {
136          errorMessage = e.getCause().getMessage();
137        }
138        results.put(change, Optional.fromNullable(errorMessage));
139      }
140
141      synchronized (this.parent.reconfigLock) {
142        this.parent.endTime = Time.now();
143        this.parent.status = Collections.unmodifiableMap(results);
144        this.parent.reconfigThread = null;
145      }
146    }
147  }
148
149  /**
150   * Start a reconfiguration task to reload configuration in background.
151   */
152  public void startReconfigurationTask() throws IOException {
153    synchronized (reconfigLock) {
154      if (!shouldRun) {
155        String errorMessage = "The server is stopped.";
156        LOG.warn(errorMessage);
157        throw new IOException(errorMessage);
158      }
159      if (reconfigThread != null) {
160        String errorMessage = "Another reconfiguration task is running.";
161        LOG.warn(errorMessage);
162        throw new IOException(errorMessage);
163      }
164      reconfigThread = new ReconfigurationThread(this);
165      reconfigThread.setDaemon(true);
166      reconfigThread.setName("Reconfiguration Task");
167      reconfigThread.start();
168      startTime = Time.now();
169    }
170  }
171
172  public ReconfigurationTaskStatus getReconfigurationTaskStatus() {
173    synchronized (reconfigLock) {
174      if (reconfigThread != null) {
175        return new ReconfigurationTaskStatus(startTime, 0, null);
176      }
177      return new ReconfigurationTaskStatus(startTime, endTime, status);
178    }
179  }
180
181  public void shutdownReconfigurationTask() {
182    Thread tempThread;
183    synchronized (reconfigLock) {
184      shouldRun = false;
185      if (reconfigThread == null) {
186        return;
187      }
188      tempThread = reconfigThread;
189      reconfigThread = null;
190    }
191
192    try {
193      tempThread.join();
194    } catch (InterruptedException e) {
195    }
196  }
197
198  /**
199   * {@inheritDoc}
200   *
201   * This method makes the change to this objects {@link Configuration}
202   * and calls reconfigurePropertyImpl to update internal data structures.
203   * This method cannot be overridden, subclasses should instead override
204   * reconfigureProperty.
205   */
206  @Override
207  public final String reconfigureProperty(String property, String newVal) 
208    throws ReconfigurationException {
209    if (isPropertyReconfigurable(property)) {
210      LOG.info("changing property " + property + " to " + newVal);
211      String oldVal;
212      synchronized(getConf()) {
213        oldVal = getConf().get(property);
214        reconfigurePropertyImpl(property, newVal);
215        if (newVal != null) {
216          getConf().set(property, newVal);
217        } else {
218          getConf().unset(property);
219        }
220      }
221      return oldVal;
222    } else {
223      throw new ReconfigurationException(property, newVal,
224                                             getConf().get(property));
225    }
226  }
227
228  /**
229   * {@inheritDoc}
230   *
231   * Subclasses must override this.
232   */
233  @Override 
234  public abstract Collection<String> getReconfigurableProperties();
235
236
237  /**
238   * {@inheritDoc}
239   *
240   * Subclasses may wish to override this with a more efficient implementation.
241   */
242  @Override
243  public boolean isPropertyReconfigurable(String property) {
244    return getReconfigurableProperties().contains(property);
245  }
246
247  /**
248   * Change a configuration property.
249   *
250   * Subclasses must override this. This method applies the change to
251   * all internal data structures derived from the configuration property
252   * that is being changed. If this object owns other Reconfigurable objects
253   * reconfigureProperty should be called recursively to make sure that
254   * to make sure that the configuration of these objects is updated.
255   */
256  protected abstract void reconfigurePropertyImpl(String property, String newVal) 
257    throws ReconfigurationException;
258
259}