Coverage Summary for Class: ConfigLoader (co.rsk.config)

Class Method, % Line, %
ConfigLoader 18.2% (2/11) 3.7% (4/109)
ConfigLoader$1 0% (0/1) 0% (0/1)
Total 16.7% (2/12) 3.6% (4/110)


1 /* 2  * This file is part of RskJ 3  * Copyright (C) 2018 RSK Labs Ltd. 4  * 5  * This program is free software: you can redistribute it and/or modify 6  * it under the terms of the GNU Lesser General Public License as published by 7  * the Free Software Foundation, either version 3 of the License, or 8  * (at your option) any later version. 9  * 10  * This program is distributed in the hope that it will be useful, 11  * but WITHOUT ANY WARRANTY; without even the implied warranty of 12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 13  * GNU Lesser General Public License for more details. 14  * 15  * You should have received a copy of the GNU Lesser General Public License 16  * along with this program. If not, see <http://www.gnu.org/licenses/>. 17  */ 18 package co.rsk.config; 19  20 import co.rsk.cli.CliArgs; 21 import com.typesafe.config.*; 22 import org.ethereum.config.SystemProperties; 23 import org.slf4j.Logger; 24 import org.slf4j.LoggerFactory; 25  26 import javax.annotation.Nonnull; 27 import java.io.File; 28 import java.util.Map; 29 import java.util.Objects; 30  31 /** 32  * Class that encapsulates config loading strategy. 33  */ 34 public class ConfigLoader { 35  36  private static final Logger logger = LoggerFactory.getLogger("config"); 37  38  private static final String MAINNET_RESOURCE_PATH = "config/main"; 39  private static final String TESTNET_RESOURCE_PATH = "config/testnet"; 40  private static final String REGTEST_RESOURCE_PATH = "config/regtest"; 41  private static final String DEVNET_RESOURCE_PATH = "config/devnet"; 42  private static final String EXPECTED_RESOURCE_PATH = "expected"; 43  private static final String YES = "yes"; 44  private static final String NO = "no"; 45  46  private final CliArgs<NodeCliOptions, NodeCliFlags> cliArgs; 47  48  public ConfigLoader(CliArgs<NodeCliOptions, NodeCliFlags> cliArgs) { 49  this.cliArgs = Objects.requireNonNull(cliArgs); 50  } 51  52  /** 53  * Loads configurations from different sources with the following precedence: 54  * 1. Command line arguments 55  * 2. Environment variables 56  * 3. System properties 57  * 4. User configuration file 58  * 5. Installer configuration file 59  * 6. Default settings per network in resources/[network].conf 60  * 7. Default settings for all networks in resources/reference.conf 61  * 62  * <p> 63  * During the verification process the unified configuration is being tested against the setting names 64  * defined in the expected.conf config file. The process is silent by default which means that in a case of any problems 65  * with the config settings only error logs will be generated and the node will continue its running. 66  * 67  * <p> 68  * If the <b><blockchain.config.verify/b> setting is {@code true} (either set in a .conf file or via <b>--verify-config</b> command line flag), 69  * then in a case of any problems an exception will be thrown. 70  * 71  * Note: 72  * 1. The <b><blockchain.config.verify/b> setting is {@code false} by default. 73  * 2. Config verification process of matching actual and expected config settings is a recursive process and takes into 74  * account appropriate setting names. Scalar values are not tested for matching, e.g. if we have settingKey="some value" 75  * in the expected.conf file and settingKey=100 in a user config file, then it will pass the verification process. 76  * 77  * @throws RskConfigurationException on configuration errors 78  */ 79  public Config getConfig() { 80  Config cliConfig = getConfigFromCliArgs(); 81  Config systemPropsConfig = ConfigFactory.systemProperties(); 82  Config systemEnvConfig = ConfigFactory.systemEnvironment(); 83  Config userCustomConfig = getUserCustomConfig(); 84  Config installerConfig = getInstallerConfig(); 85  86  Config userConfig = ConfigFactory.empty() 87  .withFallback(cliConfig) 88  .withFallback(systemPropsConfig) 89  .withFallback(systemEnvConfig) 90  .withFallback(userCustomConfig) 91  .withFallback(installerConfig); 92  Config networkBaseConfig = getNetworkDefaultConfig(userConfig); 93  Config unifiedConfig = userConfig.withFallback(networkBaseConfig); 94  95  Config expectedConfig = ConfigFactory.parseResourcesAnySyntax(EXPECTED_RESOURCE_PATH) 96  .withFallback(systemPropsConfig) 97  .withFallback(systemEnvConfig); 98  boolean valid = isActualObjectValid("", expectedConfig.root(), unifiedConfig.root()); 99  100  if (unifiedConfig.getBoolean(SystemProperties.PROPERTY_BC_VERIFY) && !valid) { 101  throw new RskConfigurationException("Verification of node config settings has failed. See the previous error logs for details."); 102  } 103  104  return unifiedConfig; 105  } 106  107  private Config getConfigFromCliArgs() { 108  Config config = ConfigFactory.empty(); 109  110  for (NodeCliFlags flag : cliArgs.getFlags()) { 111  config = flag.withConfig(config); 112  } 113  114  for (Map.Entry<NodeCliOptions, String> entry : cliArgs.getOptions().entrySet()) { 115  config = entry.getKey().withConfig(config, entry.getValue()); 116  } 117  118  return config; 119  } 120  121  private Config getUserCustomConfig() { 122  String file = System.getProperty("rsk.conf.file"); 123  Config cmdLineConfigFile = file != null ? ConfigFactory.parseFile(new File(file)) : ConfigFactory.empty(); 124  logger.info( 125  "Config ( {} ): user properties from -Drsk.conf.file file '{}'", 126  cmdLineConfigFile.entrySet().isEmpty() ? NO : YES, 127  file 128  ); 129  return cmdLineConfigFile; 130  } 131  132  private Config getInstallerConfig() { 133  File installerFile = new File("/etc/rsk/node.conf"); 134  Config installerConfig = installerFile.exists() ? ConfigFactory.parseFile(installerFile) : ConfigFactory.empty(); 135  logger.info( 136  "Config ( {} ): default properties from installer '/etc/rsk/node.conf'", 137  installerConfig.entrySet().isEmpty() ? NO : YES 138  ); 139  return installerConfig; 140  } 141  142  /** 143  * @return the network-specific configuration based on the user config, or mainnet if no configuration is specified. 144  */ 145  private Config getNetworkDefaultConfig(Config userConfig) { 146  if (userConfig.hasPath(SystemProperties.PROPERTY_BC_CONFIG_NAME)) { 147  String network = userConfig.getString(SystemProperties.PROPERTY_BC_CONFIG_NAME); 148  if (NodeCliFlags.NETWORK_TESTNET.getName().equals(network)) { 149  return ConfigFactory.load(TESTNET_RESOURCE_PATH); 150  } else if (NodeCliFlags.NETWORK_REGTEST.getName().equals(network)) { 151  return ConfigFactory.load(REGTEST_RESOURCE_PATH); 152  } else if (NodeCliFlags.NETWORK_DEVNET.getName().equals(network)) { 153  return ConfigFactory.load(DEVNET_RESOURCE_PATH); 154  } else if (NodeCliFlags.NETWORK_MAINNET.getName().equals(network)) { 155  return ConfigFactory.load(MAINNET_RESOURCE_PATH); 156  } else { 157  String exceptionMessage = String.format( 158  "%s is not a valid network name (%s property)", 159  network, 160  SystemProperties.PROPERTY_BC_CONFIG_NAME 161  ); 162  logger.warn(exceptionMessage); 163  throw new IllegalArgumentException(exceptionMessage); 164  } 165  } 166  167  logger.info("Network not set, using mainnet by default"); 168  return ConfigFactory.load(MAINNET_RESOURCE_PATH); 169  } 170  171  private static boolean isActualObjectValid(@Nonnull String keyPath, @Nonnull ConfigObject expectedObject, @Nonnull ConfigObject actualObject) { 172  boolean valid = true; 173  String prefix = keyPath.isEmpty() ? "" : keyPath + "."; 174  for (Map.Entry<String, ConfigValue> actualEntry : actualObject.entrySet()) { 175  String actualEntryKey = actualEntry.getKey(); 176  ConfigValue actualEntryValue = actualEntry.getValue(); 177  if (expectedObject.isEmpty()) { 178  // if expected object is empty, then the actual object should contain only scalar items 179  if (isCollectionType(actualEntryValue.valueType())) { 180  String entryKeyPath = prefix + actualEntryKey; 181  logger.error("Expected scalar config value for key path `{}`. Actual value is {}. See expected.conf for the expected settings", 182  entryKeyPath, actualEntryValue); 183  valid = false; 184  } 185  } else { 186  ConfigValue expectedEntryValue = expectedObject.get(actualEntryKey); 187  String entryKeyPath = prefix + actualEntryKey; 188  if (expectedEntryValue == null) { 189  logger.error("Unexpected config value {} for key path `{}`. See expected.conf for the expected settings", actualEntryValue, entryKeyPath); 190  valid = false; 191  } else { 192  valid &= isActualValueValid(entryKeyPath, expectedEntryValue, actualEntryValue); 193  } 194  } 195  } 196  return valid; 197  } 198  199  private static boolean isActualListValid(@Nonnull String keyPath, @Nonnull ConfigList expectedList, @Nonnull ConfigList actualList) { 200  if (expectedList.size() > 1) { 201  throw new RuntimeException("An array in expected.conf should either be empty or contain one template item."); 202  } 203  204  boolean valid = true; 205  int index = 0; 206  for (ConfigValue actualItem : actualList) { 207  if (expectedList.isEmpty()) { 208  // if expected list is empty, then the actual list should contain only scalar items 209  if (isCollectionType(actualItem.valueType())) { 210  String itemKeyPath = keyPath + "[" + index + "]"; 211  logger.error("Expected scalar config value for key path `{}`. Actual value is {}. See expected.conf for the expected settings", 212  itemKeyPath, actualItem); 213  valid = false; 214  } 215  } else { 216  // Assuming that all items in the list should have the same configuration structure. 217  String itemKeyPath = keyPath + "[" + index + "]"; 218  ConfigValue expectedItem = expectedList.get(0); 219  valid &= isActualValueValid(itemKeyPath, expectedItem, actualItem); 220  } 221  index++; 222  } 223  return valid; 224  } 225  226  private static boolean isActualValueValid(@Nonnull String keyPath, @Nonnull ConfigValue expectedValue, @Nonnull ConfigValue actualValue) { 227  ConfigValueType actualValueType = Objects.requireNonNull(actualValue.valueType()); 228  ConfigValueType expectedValueType = Objects.requireNonNull(expectedValue.valueType()); 229  230  if (!isCollectionType(expectedValueType) && !isCollectionType(actualValueType)) { 231  return true; // We don't verify non-collection types 232  } 233  234  if (expectedValueType != actualValueType) { 235  logger.error("Config value type mismatch. `{}` has type {}, but should have {}. See expected.conf for the expected settings", 236  keyPath, actualValueType, expectedValueType); 237  return false; 238  } 239  240  switch (actualValueType) { 241  case OBJECT: 242  ConfigObject actualObject = (ConfigObject) actualValue; 243  ConfigObject expectedObject = (ConfigObject) expectedValue; 244  return isActualObjectValid(keyPath, expectedObject, actualObject); 245  case LIST: 246  ConfigList actualList = (ConfigList) actualValue; 247  ConfigList expectedList = (ConfigList) expectedValue; 248  return isActualListValid(keyPath, expectedList, actualList); 249  default: 250  return true; 251  } 252  } 253  254  /** 255  * Checks whether the value type is a collection of other values. 256  * 257  * @return {@code true} if the value type is either {@link ConfigValueType#OBJECT} or {@link ConfigValueType#LIST}. 258  */ 259  public static boolean isCollectionType(ConfigValueType valueType) { 260  return valueType == ConfigValueType.OBJECT || valueType == ConfigValueType.LIST; 261  } 262  263 }