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 com.google.common.annotations.VisibleForTesting;
022    import com.google.common.base.Optional;
023    import com.google.common.base.Preconditions;
024    import com.google.common.collect.Maps;
025    import org.apache.commons.logging.*;
026    import org.apache.hadoop.util.Time;
027    import org.apache.hadoop.conf.ReconfigurationUtil.PropertyChange;
028    
029    import java.io.IOException;
030    import java.util.Collection;
031    import java.util.Collections;
032    import 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     */
041    public 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.toString();
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    }