美文网首页Ovirt程序员
【Ovirt 笔记】engine-config 的实现原理

【Ovirt 笔记】engine-config 的实现原理

作者: 58bc06151329 | 来源:发表于2017-05-18 08:32 被阅读22次

文前说明

作为码农中的一员,需要不断的学习,我工作之余将一些分析总结和学习笔记写成博客与大家一起交流,也希望采用这种方式记录自己的学习之旅。

本文仅供学习交流使用,侵权必删。
不用于商业目的,转载请注明出处。

分析整理的版本为 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 \
    "$@"
  1. EngineConfigExecutor 执行 main 方法,通过 EngineConfigCLIParser 处理验证参数。
parser = new EngineConfigCLIParser();
parser.parse(args);
  1. 通过 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" }));
  1. 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

  • 调用 EngineConfigLogicexecute 方法。

  • 执行 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);
  • 根据关键字 AdminPasswordengine-config.properties 配置文件中获取 AdminPassword.type=Password
  • typePassword 因此根据 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);
}
  • 实际调用了 PasswordValueHelpervalidate 方法。

  • 判断传递的密码修改方式是否为 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);

相关文章

网友评论

    本文标题:【Ovirt 笔记】engine-config 的实现原理

    本文链接:https://www.haomeiwen.com/subject/psojxxtx.html