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}