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 */ 018package org.apache.hadoop.hbase.util; 019 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Set; 025import org.apache.hadoop.conf.Configuration; 026import org.apache.hadoop.hbase.HConstants; 027import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; 028import org.apache.hadoop.hbase.coprocessor.CoprocessorReloadTask; 029import org.apache.hadoop.hbase.security.access.BulkLoadReadOnlyController; 030import org.apache.hadoop.hbase.security.access.EndpointReadOnlyController; 031import org.apache.hadoop.hbase.security.access.MasterReadOnlyController; 032import org.apache.hadoop.hbase.security.access.RegionReadOnlyController; 033import org.apache.hadoop.hbase.security.access.RegionServerReadOnlyController; 034import org.apache.yetus.audience.InterfaceAudience; 035import org.slf4j.Logger; 036import org.slf4j.LoggerFactory; 037 038import org.apache.hbase.thirdparty.com.google.common.base.Preconditions; 039import org.apache.hbase.thirdparty.com.google.common.base.Strings; 040import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableList; 041import org.apache.hbase.thirdparty.com.google.common.collect.ImmutableMap; 042 043/** 044 * Helper class for coprocessor host when configuration changes. 045 */ 046@InterfaceAudience.Private 047public final class CoprocessorConfigurationUtil { 048 private static final Logger LOG = LoggerFactory.getLogger(CoprocessorConfigurationUtil.class); 049 050 private static final ImmutableMap<String, List<String>> READONLY_COPROCESSORS = 051 ImmutableMap.of(CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY, 052 ImmutableList.of(MasterReadOnlyController.class.getName()), 053 CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, 054 ImmutableList.of(RegionServerReadOnlyController.class.getName()), 055 CoprocessorHost.REGION_COPROCESSOR_CONF_KEY, 056 ImmutableList.of(RegionReadOnlyController.class.getName(), 057 BulkLoadReadOnlyController.class.getName(), EndpointReadOnlyController.class.getName())); 058 059 private CoprocessorConfigurationUtil() { 060 } 061 062 /** 063 * Check configuration change by comparing current loaded coprocessors with configuration values. 064 * This method is useful when the configuration object has been updated, but we need to determine 065 * if the coprocessor configuration has actually changed compared to what's currently loaded. 066 * <p> 067 * <b>Note:</b> This method only detects changes in the set of coprocessor class names. It does 068 * <b>not</b> detect changes to priority or path for coprocessors that are already loaded with the 069 * same class name. If you need to update the priority or path of an existing coprocessor, you 070 * must restart the region/regionserver/master. 071 * @param coprocessorHost the coprocessor host to check current loaded coprocessors (can be null) 072 * @param conf the configuration to check 073 * @param configurationKey the configuration keys to check 074 * @return true if configuration has changed, false otherwise 075 */ 076 public static boolean checkConfigurationChange(CoprocessorHost<?, ?> coprocessorHost, 077 Configuration conf, String... configurationKey) { 078 Preconditions.checkArgument(configurationKey != null, "Configuration Key(s) must be provided"); 079 Preconditions.checkArgument(conf != null, "Configuration must be provided"); 080 081 if ( 082 !conf.getBoolean(CoprocessorHost.COPROCESSORS_ENABLED_CONF_KEY, 083 CoprocessorHost.DEFAULT_COPROCESSORS_ENABLED) 084 ) { 085 return false; 086 } 087 088 if (coprocessorHost == null) { 089 // If no coprocessor host exists, check if any coprocessors are now configured 090 return hasCoprocessorsConfigured(conf, configurationKey); 091 } 092 093 // Get currently loaded coprocessor class names 094 Set<String> currentlyLoaded = coprocessorHost.getCoprocessorClassNames(); 095 096 // Get coprocessor class names from configuration 097 // Only class names are compared; priority and path changes are not detected 098 Set<String> configuredClasses = new HashSet<>(); 099 for (String key : configurationKey) { 100 String[] classes = conf.getStrings(key); 101 if (classes != null) { 102 for (String className : classes) { 103 // Handle the className|priority|path format 104 String[] classNameToken = className.split("\\|"); 105 String actualClassName = classNameToken[0].trim(); 106 if (!Strings.isNullOrEmpty(actualClassName)) { 107 configuredClasses.add(actualClassName); 108 } 109 } 110 } 111 } 112 113 // Compare the two sets 114 return !currentlyLoaded.equals(configuredClasses); 115 } 116 117 /** 118 * Helper method to check if there are any coprocessors configured. 119 */ 120 private static boolean hasCoprocessorsConfigured(Configuration conf, String... configurationKey) { 121 for (String key : configurationKey) { 122 String[] coprocessors = conf.getStrings(key); 123 if (coprocessors != null && coprocessors.length > 0) { 124 return true; 125 } 126 } 127 return false; 128 } 129 130 private static List<String> getCoprocessorsFromConfig(Configuration conf, 131 String configurationKey) { 132 String[] existing = conf.getStrings(configurationKey); 133 return existing != null ? new ArrayList<>(Arrays.asList(existing)) : new ArrayList<>(); 134 } 135 136 public static void addCoprocessors(Configuration conf, String configurationKey, 137 List<String> coprocessorsToAdd) { 138 List<String> existing = getCoprocessorsFromConfig(conf, configurationKey); 139 140 boolean isModified = false; 141 142 for (String coprocessor : coprocessorsToAdd) { 143 if (!existing.contains(coprocessor)) { 144 existing.add(coprocessor); 145 isModified = true; 146 } 147 } 148 149 if (isModified) { 150 conf.setStrings(configurationKey, existing.toArray(new String[0])); 151 } 152 } 153 154 public static void removeCoprocessors(Configuration conf, String configurationKey, 155 List<String> coprocessorsToRemove) { 156 List<String> existing = getCoprocessorsFromConfig(conf, configurationKey); 157 158 if (existing.isEmpty()) { 159 return; 160 } 161 162 boolean isModified = false; 163 164 for (String coprocessor : coprocessorsToRemove) { 165 if (existing.contains(coprocessor)) { 166 existing.remove(coprocessor); 167 isModified = true; 168 } 169 } 170 171 if (isModified) { 172 conf.setStrings(configurationKey, existing.toArray(new String[0])); 173 } 174 } 175 176 private static List<String> getReadOnlyCoprocessors(String configurationKey) { 177 return READONLY_COPROCESSORS.get(configurationKey); 178 } 179 180 /** 181 * This method adds or removes relevant ReadOnlyController coprocessors to the provided 182 * configuration based on whether read-only mode is enabled in the provided Configuration. 183 * @param conf The up-to-date configuration used to determine how to handle 184 * coprocessors 185 * @param coprocessorConfKey The configuration key name 186 */ 187 public static void syncReadOnlyConfigurations(Configuration conf, String coprocessorConfKey) { 188 boolean isReadOnlyModeEnabled = ConfigurationUtil.isReadOnlyModeEnabledInConf(conf); 189 190 List<String> cpList = getReadOnlyCoprocessors(coprocessorConfKey); 191 if (isReadOnlyModeEnabled) { 192 CoprocessorConfigurationUtil.addCoprocessors(conf, coprocessorConfKey, cpList); 193 } else { 194 CoprocessorConfigurationUtil.removeCoprocessors(conf, coprocessorConfKey, cpList); 195 } 196 } 197 198 /** 199 * Check whether ReadOnlyController coprocessors have been loaded in the provided configuration. 200 * @param conf the configuration we are checking 201 * @param coprocessorConfKey configuration key used for setting master, region server, or region 202 * coprocessors 203 * @return true if the ReadOnlyCoprocessors are loaded in the configuration; false otherwise 204 */ 205 public static boolean areReadOnlyCoprocessorsLoaded(Configuration conf, 206 String coprocessorConfKey) { 207 // Using a HashSet will improve performance when searching for read-only coprocessors 208 HashSet<String> allCoprocessors = 209 new HashSet<>(getCoprocessorsFromConfig(conf, coprocessorConfKey)); 210 List<String> readOnlyCoprocessors = getReadOnlyCoprocessors(coprocessorConfKey); 211 return allCoprocessors.containsAll(readOnlyCoprocessors); 212 } 213 214 /** 215 * Takes an updated configuration and updates the coprocessors for that configuration key in the 216 * current configuration. 217 * @param currentConf the configuration currently used by the master, region server, or 218 * region 219 * @param updatedConf the updated version of the configuration whose coprocessors we want 220 * to copy 221 * @param coprocessorConfKey configuration key used for setting master, region server, or region 222 * coprocessors 223 */ 224 public static void updateCoprocessorListInConf(Configuration currentConf, 225 Configuration updatedConf, String coprocessorConfKey) { 226 String[] updatedCoprocessorList = updatedConf.getStrings(coprocessorConfKey); 227 if (updatedCoprocessorList != null) { 228 currentConf.setStrings(coprocessorConfKey, updatedCoprocessorList); 229 } else { 230 currentConf.unset(coprocessorConfKey); 231 } 232 } 233 234 /** 235 * Gets the name of a component based on the provided coprocessor configuration key. 236 * @param coprocessorConfKey configuration key used for setting master, region server, or region 237 * coprocessors 238 * @return the component type - Master, Region Server, or Region 239 */ 240 public static String getComponentName(String coprocessorConfKey) { 241 return switch (coprocessorConfKey) { 242 case CoprocessorHost.MASTER_COPROCESSOR_CONF_KEY -> "Master"; 243 case CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY -> "Region Server"; 244 case CoprocessorHost.REGION_COPROCESSOR_CONF_KEY -> "Region"; 245 default -> throw new IllegalArgumentException( 246 "Unsupported coprocessor configuration key: " + coprocessorConfKey); 247 }; 248 } 249 250 /** 251 * This method updates the coprocessors on the master, region server, or region if a change has 252 * been detected. Detected changes include changes in coprocessors or changes in read-only mode 253 * configuration. If a change is detected, then new coprocessors are loaded using the provided 254 * reload method. The new value for the read-only config variable is updated as well. 255 * @param newConf an updated configuration 256 * @param originalIsReadOnlyEnabled the original value for 257 * {@value HConstants#HBASE_GLOBAL_READONLY_ENABLED_KEY} 258 * @param coprocessorHost the coprocessor host for HMaster, HRegionServer, or HRegion 259 * @param coprocessorConfKey configuration key used for setting master, region server, or 260 * region coprocessors 261 * @param isMaintenanceMode whether maintenance mode is active (mainly for HMaster) 262 * @param instance string value of the instance calling this method (mainly helps 263 * with tracking region logging) 264 * @param reloadTask lambda function that reloads coprocessors on the master, 265 * region server, or region 266 */ 267 public static void maybeUpdateCoprocessors(Configuration newConf, 268 boolean originalIsReadOnlyEnabled, CoprocessorHost<?, ?> coprocessorHost, 269 String coprocessorConfKey, boolean isMaintenanceMode, String instance, 270 CoprocessorReloadTask reloadTask) { 271 272 boolean maybeUpdatedReadOnlyMode = ConfigurationUtil.isReadOnlyModeEnabledInConf(newConf); 273 boolean hasReadOnlyModeChanged = originalIsReadOnlyEnabled != maybeUpdatedReadOnlyMode; 274 boolean hasCoprocessorConfigChanged = CoprocessorConfigurationUtil 275 .checkConfigurationChange(coprocessorHost, newConf, coprocessorConfKey); 276 277 // update region server coprocessor if the configuration has changed. 278 if ((hasCoprocessorConfigChanged || hasReadOnlyModeChanged) && !isMaintenanceMode) { 279 LOG.info("Updating coprocessors for {} {} because the configuration has changed", 280 getComponentName(coprocessorConfKey), instance); 281 CoprocessorConfigurationUtil.syncReadOnlyConfigurations(newConf, coprocessorConfKey); 282 reloadTask.reload(newConf); 283 } 284 285 if (hasReadOnlyModeChanged) { 286 LOG.info("Config {} has been dynamically changed to {} for {} {}", 287 HConstants.HBASE_GLOBAL_READONLY_ENABLED_KEY, maybeUpdatedReadOnlyMode, 288 getComponentName(coprocessorConfKey), instance); 289 } 290 } 291}