文前说明
作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。
本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。
分析整理的版本为 Ovirt 3.4.5 版本。
命令使用方式:engine-config <action> [<args>]
动作 | 说明 |
---|---|
-l,--list | 显示可用的系统参数关键字列表 |
-a,--all | 显示可用的系统参数信息列表 |
-g,--get= | 根据关键字或者系统参数信息 |
-s KEY=VALUE,--set KEY=VALUE | 设置系统参数的关键字和值 |
-m KEY=VALUE,--merge KEY=VALUE | 合并系统参数的关键字和值 |
-h,--help | 显示帮助信息 |
选项 | 说明 |
---|---|
--cver | 系统参数的配置版本 |
-p,--properties= | 使用的系统参数资源文件名称 |
-c,--config= | 使用的系统参数配置文件名称 |
--log-file= | 日志输出文件 |
--log-level= | 输出日志级别(默认为 DEBUG),有 INFO、WARN 和 ERROR |
--log4j-config | 设置 Log4j XML 配置文件 |
- 设置用户密码交互式方式 ,例如
engine-config -s PasswordEntry=interactive
- 设置用户密码,密码文件方式 ,例如
engine-config -s PasswordEntry --admin-pass-file=/tmp/mypass
- 设置用户密码,用户和密码文件配置方式,例如
engine-config -s PasswordEntry=/tmp/mypass
命令采用了 shell 调用 java 的方式进行实现。
engine-config.sh 利用 jboss-modules.jar 查询指定的模块 org.ovirt.engine.core.tools 执行启动类 org.ovirt.engine.core.config.EngineConfigExecutor
exec "${JAVA_HOME}/bin/java" \
-Djboss.modules.write-indexes=false \
-jar "${JBOSS_HOME}/jboss-modules.jar" \
-dependencies org.ovirt.engine.core.tools \
-class org.ovirt.engine.core.config.EngineConfigExecutor \
"$@"
- EngineConfigExecutor 执行 main 方法,通过 EngineConfigCLIParser 处理验证参数。
parser = new EngineConfigCLIParser();
parser.parse(args);
- 通过 EngineConfig 处理整个 config 命令的业务流程。
EngineConfig.getInstance().setUpAndExecute(parser);
public void setUpAndExecute(EngineConfigCLIParser parser) throws Exception {
log.debug("Arguments have been parsed: " + parser.engineConfigMapToString());
ConfigActionType actionType = parser.getConfigAction();
actionType.validate(parser.getEngineConfigMap());
setEngineConfigLogic(new EngineConfigLogic(parser));
engineConfigLogic.execute();
}
- 创建 EngineConfigLogic 对象,执行相关业务。
public void execute() throws Exception {
ConfigActionType actionType = parser.getConfigAction();
log.debug("execute: beginning execution of action " + actionType + ".");
switch (actionType) {
case ACTION_ALL:
printAllValues();
break;
case ACTION_LIST:
listKeys();
break;
case ACTION_GET:
printKey();
break;
case ACTION_SET:
persistValue();
break;
case ACTION_MERGE:
mergeValue();
break;
case ACTION_HELP:
printHelpForKey();
break;
case ACTION_RELOAD:
reloadConfigurations();
break;
default: // Should have already been discovered before execute
log.debug("execute: unable to recognize action: " + actionType + ".");
throw new UnsupportedOperationException("Please tell me what to do: list? get? set? get-all? reload?");
}
}
engine-config 执行的方法,通过 ConfigActionType 枚举进行定义。
ACTION_ALL(Arrays.asList(new String[] { "-a", "--all" }), null),
ACTION_LIST(Arrays.asList(new String[] { "-l", "--list" }), null),
ACTION_GET(Arrays.asList(new String[] { "-g", "--get" }), new ValidatorType[] { ValidatorType.get }),
ACTION_SET(Arrays.asList(new String[] { "-s", "--set" }), new ValidatorType[] { ValidatorType.set }),
ACTION_HELP(Arrays.asList(new String[] { "-h", "--help" }), new ValidatorType[] { ValidatorType.help }),
ACTION_RELOAD(Arrays.asList(new String[] { "-r", "--reload" }), null),
ACTION_MERGE(Arrays.asList(new String[] { "-m", "--merge" }), new ValidatorType[] { ValidatorType.set });
执行时参数通过 OptionKey 枚举进行定义。
OPTION_CONFIG(Arrays.asList(new String[] { "-c", "--config" })),
OPTION_PROPERTIES(Arrays.asList(new String[] { "-p", "--properties" })),
OPTION_VERSION(Arrays.asList(new String[] { "--cver" })),
OPTION_USER(Arrays.asList(new String[] { "-u", "--user" })),
OPTION_ADMINPASSFILE(Arrays.asList(new String[] { "--admin-pass-file" })),
OPTION_ONLY_RELOADABLE(Arrays.asList(new String[] { "-o", "--only-reloadable" })),
OPTION_LOG_FILE(Arrays.asList(new String[] { "--log-file" })),
OPTION_LOG_LEVEL(Arrays.asList(new String[] { "--log-level" })),
OPTION_LOG4J_CONFIG(Arrays.asList(new String[] { "--log4j-config" }));
- EngineConfigLogic 对象实例化的同时,创建了数据库连接。
this.configDAO = new ConfigDaoImpl(appConfig);
ds = new StandaloneDataSource();
public StandaloneDataSource() throws SQLException {
// Load the service configuration file:
EngineLocalConfig config = EngineLocalConfig.getInstance();
// Get the database connection details:
driver = config.getProperty(ENGINE_DB_DRIVER);
url = config.getProperty(ENGINE_DB_URL);
user = config.getProperty(ENGINE_DB_USER);
password = config.getProperty(ENGINE_DB_PASSWORD);
// Load the driver:
try {
Class.forName(driver);
}
catch (ClassNotFoundException exception) {
throw new SQLException("Failed to load database driver \"" + driver + "\".", exception);
}
// Open the connection:
openConnection();
// Register a shutdown hook to close the connection when finishing:
Runtime.getRuntime().addShutdownHook(
new Thread() {
@Override
public void run() {
closeConnection();
}
}
);
}
- 实例化所用到的创建数据库连接的参数信息,通过 LocalConfig 类的创建而加载,内容来自 10-setup-database.conf 文件。
ENGINE_DB_HOST="localhost"
ENGINE_DB_PORT="5432"
ENGINE_DB_USER="engine"
ENGINE_DB_PASSWORD="engine"
ENGINE_DB_DATABASE="engine"
ENGINE_DB_SECURED="False"
ENGINE_DB_SECURED_VALIDATION="False"
ENGINE_DB_DRIVER="org.postgresql.Driver"
ENGINE_DB_URL="jdbc:postgresql://${ENGINE_DB_HOST}:${ENGINE_DB_PORT}/${ENGINE_DB_DATABASE}?sslfactory=org.postgresql.ssl.NonValidatingFactory"
- 读取 engine-config.conf 内容,获取读取的表名、字段名。
# engine-config configuration
# Data structure
configTable=vdc_options
configColumnName=option_name
configColumnValue=option_value
configColumnVersion=version
- 拼装 sql 语句创建操作 engine 内置参数的各语句。
selectSql =
MessageFormat.format("select {0} from {1} where {2}=? and {3} =?",
valueColumn,
configTable,
nameColumn,
versionColumn);
updateSql =
MessageFormat.format("update {0} set {1}=? where {2}=? and {3}=?",
configTable,
valueColumn,
nameColumn,
versionColumn);
selectKeysForNameSql =
MessageFormat.format("select * from {0} where {1}=? ",
configTable,
nameColumn);
- 读取 engine-config.properties 内容,获取 engine 内置参数条目。
#
# engine-config tool properties file.
# Please do not edit without proper guidance.
#
AbortMigrationOnError.type=Boolean
AbortMigrationOnError.description="Optionally abort an ongoing migration on any error"
AsyncTaskPollingRate.description="Async Task Polling Rate (in seconds)"
AsyncTaskPollingRate.type=Integer
AsyncTaskZombieTaskLifeInMinutes.description="Zombie tasks life-time in minutes"
AsyncTaskZombieTaskLifeInMinutes.type=Integer
AuditLogAgingThreshold.description="Audit Log Aging Threshold (in days)"
AuditLogAgingThreshold.type=Integer
AuditLogCleanupTime.description="Audit Log Cleanup Time"
AuthenticationMethod.description="Authentication Method"
AuthenticationMethod.validValues=LDAP,Windows
BlockMigrationOnSwapUsagePercentage.description="Host swap percentage threshold (for scheduling)"
BlockMigrationOnSwapUsagePercentage.type=Integer
BootstrapMinimalVdsmVersion.description="Minimum VDSM version"
BootstrapMinimalVdsmVersion.type=String
CABaseDirectory.description="CA Base Directory"
CpuOverCommitDurationMinutes.description="The duration in minutes of CPU consumption to activate selection algorithm"
CpuOverCommitDurationMinutes.type=Integer
DisableFenceAtStartupInSec.description="Disable Fence Operations At oVirt Startup In Seconds"
DisableFenceAtStartupInSec.type=Integer
......
--all 的实现
调用 printAllValues 方法。查询内置参数信息。
List<ConfigurationNode> configNodes = keysConfig.getRootNode().getChildren();
管理员密码的修改
修改方式 | 命令 |
---|---|
交互方式修改 | engine-config -s AdminPassword=interactive |
文件方式修改 | engine-config -s AdminPassword=/tmp/mypass |
命令行明文方式修改 | engine-config -s AdminPassword=123 -p prop.txt,prop.txt中只需要写入“AdminPassword=” |
- 密码加密后存放在 vdc_options 表中,查管理员密码的 sql 语句:
select option_value from vdc_options where option_name=’AdminPassword’
- 密码采用 RSA 加密算法(公钥加密、私钥解密)。
参数 | 值 |
---|---|
truststore | engine.p12 |
keyalias | 1 |
keypass | mypass |
-
实现同样执行了 EngineConfigExecutor。
-
调用 EngineConfigLogic 的 execute 方法。
-
执行 persistValue 方法。
boolean sucessUpdate = persist(key, value, version);
ConfigKey configKey = configKeyFactory.generateByPropertiesKey(key);
String type = configurationAt.getString("type");
......
ConfigKey configKey = new ConfigKey(type, description, alternateKey, key, "", validValues,
"", getHelperByType(type), reloadable);
- 根据关键字 AdminPassword 从 engine-config.properties 配置文件中获取 AdminPassword.type=Password。
- type 为 Password 因此根据 getHelperByType 方法,构建策略 org.ovirt.engine.core.config.entity.helper.PasswordValueHelper。
private ValueHelper getHelperByType(String type) {
ValueHelper valueHelper;
try {
if (type == null) {
type = "String";
}
Class<?> cls = Class.forName("org.ovirt.engine.core.config.entity.helper." + type + "ValueHelper");
valueHelper = (ValueHelper) cls.newInstance();
} catch (Exception e) {
// failed finding a helper for this type. Setting default string type
Logger.getLogger(EngineConfig.class).debug("Unable to find " + type + " type. Using default string type.");
valueHelper = new StringValueHelper();
}
return valueHelper;
}
configKey.safeSetValue(value);
public void safeSetValue(String value) throws InvalidParameterException, Exception {
ValidationResult validationResult = valueHelper.validate(this, value);
if (!validationResult.isOk()) {
StringBuilder invalidParamMsg = new StringBuilder();
invalidParamMsg.append("Cannot set value ")
.append(value)
.append(" to key ")
.append(keyName)
.append(". ")
.append(StringUtils.isNotEmpty(validationResult.getDetails()) ? validationResult.getDetails() : "");
throw new InvalidParameterException(invalidParamMsg.toString());
}
this.value = valueHelper.setValue(value);
}
-
实际调用了 PasswordValueHelper 的 validate 方法。
-
判断传递的密码修改方式是否为 Interactive。
public static final String INTERACTIVE_MODE = "Interactive";
......
if (StringUtils.isNotBlank(value) && value.equalsIgnoreCase(INTERACTIVE_MODE)) {
return new ValidationResult(true);
}
- 如果是 Interactive 方式,则要求输入密码。
public static String startPasswordDialog(String user) throws IOException {
return startPasswordDialog(user, "Please enter a password");
}
- 这种方式,设置值会进行加密,获取值会进行解密操作。
@Override
public String getValue(String value) throws GeneralSecurityException {
/*
* The idea of this method would normally be to decrypt and return
* the decrypted value. Due to security reasons, we do not wish to return
* the real value. Just an indication if we have a value in the DB or not.
* So if there's no value we return "Empty".
* If there's a value we try to decrypt. On success we return "Set",
* On failure we return an error.
*/
String returnedValue = "Empty";
if (value != null && !value.equals("")){
try {
decrypt(value);
returnedValue = "Set";
}
catch (Exception exception) {
String msg = "Failed to decrypt the current value.";
console.writeLine(msg);
log.error(exception);
throw new GeneralSecurityException(msg);
}
}
return returnedValue;
}
/**
* this method is ignoring the value! if the value is "Interactive" it will open a console input for the password
* else it will look for the password from a file
*
* @return The user's encrypted password
*/
@Override
public String setValue(String value) throws GeneralSecurityException {
String returnedValue = null;
String password = null;
try {
password = extractPasswordValue(value);
if (StringUtils.isBlank(password)) {
return StringUtils.EMPTY;
}
returnedValue = encrypt(password);
}
catch (Throwable exception) {
String msg = "Failed to encrypt the current value.";
console.writeLine(msg);
log.error(msg, exception);
throw new GeneralSecurityException(msg);
}
return returnedValue;
}
- 进行 RSA 加解密需要的相关参数信息,读取了配置文件 10-setup-pki.conf。
ENGINE_PKI="/home/xxx/ovirt-engine/etc/pki/ovirt-engine"
ENGINE_PKI_CA="/home/xxx/ovirt-engine/etc/pki/ovirt-engine/ca.pem"
ENGINE_PKI_ENGINE_CERT="/home/xxx/ovirt-engine/etc/pki/ovirt-engine/certs/engine.cer"
ENGINE_PKI_TRUST_STORE="/home/xxx/ovirt-engine/etc/pki/ovirt-engine/.truststore"
ENGINE_PKI_TRUST_STORE_PASSWORD="mypass"
ENGINE_PKI_ENGINE_STORE="/home/xxx/ovirt-engine/etc/pki/ovirt-engine/keys/engine.p12"
ENGINE_PKI_ENGINE_STORE_PASSWORD="mypass"
ENGINE_PKI_ENGINE_STORE_ALIAS="1"
- 未设置密码修改方式场景,未明文定义密码值则采用文件修改方式。
password = EngineConfigLogic.getPassFromFile((StringUtils.isNotBlank(value)) ? value : parser.getAdminPassFile());
- 最终通过 update 方法,修改数据库中内置参数信息。
res = (getConfigDAO().updateKey(configKey) == 1);
网友评论