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      for (PropertyChange change : changes) {
116        String errorMessage = null;
117        if (!this.parent.isPropertyReconfigurable(change.prop)) {
118          errorMessage = "Property " + change.prop +
119              " is not reconfigurable";
120          LOG.info(errorMessage);
121          results.put(change, Optional.of(errorMessage));
122          continue;
123        }
124        LOG.info("Change property: " + change.prop + " from \""
125            + ((change.oldVal == null) ? "<default>" : change.oldVal)
126            + "\" to \"" + ((change.newVal == null) ? "<default>" : change.newVal)
127            + "\".");
128        try {
129          this.parent.reconfigurePropertyImpl(change.prop, change.newVal);
130        } catch (ReconfigurationException e) {
131          errorMessage = e.getCause().getMessage();
132        }
133        results.put(change, Optional.fromNullable(errorMessage));
134      }
135
136      synchronized (this.parent.reconfigLock) {
137        this.parent.endTime = Time.now();
138        this.parent.status = Collections.unmodifiableMap(results);
139        this.parent.reconfigThread = null;
140      }
141    }
142  }
143
144  /**
145   * Start a reconfiguration task to reload configuration in background.
146   */
147  public void startReconfigurationTask() throws IOException {
148    synchronized (reconfigLock) {
149      if (!shouldRun) {
150        String errorMessage = "The server is stopped.";
151        LOG.warn(errorMessage);
152        throw new IOException(errorMessage);
153      }
154      if (reconfigThread != null) {
155        String errorMessage = "Another reconfiguration task is running.";
156        LOG.warn(errorMessage);
157        throw new IOException(errorMessage);
158      }
159      reconfigThread = new ReconfigurationThread(this);
160      reconfigThread.setDaemon(true);
161      reconfigThread.setName("Reconfiguration Task");
162      reconfigThread.start();
163      startTime = Time.now();
164    }
165  }
166
167  public ReconfigurationTaskStatus getReconfigurationTaskStatus() {
168    synchronized (reconfigLock) {
169      if (reconfigThread != null) {
170        return new ReconfigurationTaskStatus(startTime, 0, null);
171      }
172      return new ReconfigurationTaskStatus(startTime, endTime, status);
173    }
174  }
175
176  public void shutdownReconfigurationTask() {
177    Thread tempThread;
178    synchronized (reconfigLock) {
179      shouldRun = false;
180      if (reconfigThread == null) {
181        return;
182      }
183      tempThread = reconfigThread;
184      reconfigThread = null;
185    }
186
187    try {
188      tempThread.join();
189    } catch (InterruptedException e) {
190    }
191  }
192
193  /**
194   * {@inheritDoc}
195   *
196   * This method makes the change to this objects {@link Configuration}
197   * and calls reconfigurePropertyImpl to update internal data structures.
198   * This method cannot be overridden, subclasses should instead override
199   * reconfigureProperty.
200   */
201  @Override
202  public final String reconfigureProperty(String property, String newVal) 
203    throws ReconfigurationException {
204    if (isPropertyReconfigurable(property)) {
205      LOG.info("changing property " + property + " to " + newVal);
206      String oldVal;
207      synchronized(getConf()) {
208        oldVal = getConf().get(property);
209        reconfigurePropertyImpl(property, newVal);
210        if (newVal != null) {
211          getConf().set(property, newVal);
212        } else {
213          getConf().unset(property);
214        }
215      }
216      return oldVal;
217    } else {
218      throw new ReconfigurationException(property, newVal,
219                                             getConf().get(property));
220    }
221  }
222
223  /**
224   * {@inheritDoc}
225   *
226   * Subclasses must override this.
227   */
228  @Override 
229  public abstract Collection<String> getReconfigurableProperties();
230
231
232  /**
233   * {@inheritDoc}
234   *
235   * Subclasses may wish to override this with a more efficient implementation.
236   */
237  @Override
238  public boolean isPropertyReconfigurable(String property) {
239    return getReconfigurableProperties().contains(property);
240  }
241
242  /**
243   * Change a configuration property.
244   *
245   * Subclasses must override this. This method applies the change to
246   * all internal data structures derived from the configuration property
247   * that is being changed. If this object owns other Reconfigurable objects
248   * reconfigureProperty should be called recursively to make sure that
249   * to make sure that the configuration of these objects is updated.
250   */
251  protected abstract void reconfigurePropertyImpl(String property, String newVal) 
252    throws ReconfigurationException;
253
254}