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}