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 }