/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ambari.server.state.stack.upgrade;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlAttribute;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlEnum;
import javax.xml.bind.annotation.XmlEnumValue;
import javax.xml.bind.annotation.XmlRootElement;
import javax.xml.bind.annotation.XmlType;
import org.apache.ambari.server.state.Cluster;
import org.apache.ambari.server.state.Config;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Objects;
/**
* The {@link ConfigUpgradeChangeDefinition} represents a configuration change. This change can be
* defined with conditional statements that will only set values if a condition
* passes:
* <p/>
*
* <pre>
* {@code
* <definition>
* <condition type="hive-site" key="hive.server2.transport.mode" value="binary">
* <type>hive-site</type>
* <key>hive.server2.thrift.port</key>
* <value>10010</value>
* </condition>
* <condition type="hive-site" key="hive.server2.transport.mode" value="http">
* <type>hive-site</type>
* <key>hive.server2.http.port</key>
* <value>10011</value>
* </condition>
* </definition>
* }
* </pre>
*
* It's also possible to simple set values directly without a precondition
* check.
*
* <pre>
* {@code
* <definition xsi:type="configure">
* <type>hive-site</type>
* <set key="hive.server2.thrift.port" value="10010"/>
* <set key="foo" value="bar"/>
* <set key="foobar" value="baz"/>
* </definition>
* }
* </pre>
*
*/
@XmlRootElement
@XmlAccessorType(XmlAccessType.FIELD)
public class ConfigUpgradeChangeDefinition {
private static Logger LOG = LoggerFactory.getLogger(ConfigUpgradeChangeDefinition.class);
/**
* The key that represents the configuration type to change (ie hdfs-site).
*/
public static final String PARAMETER_CONFIG_TYPE = "configure-task-config-type";
/**
* Setting key/value pairs can be several per task, so they're passed in as a
* json-ified list of objects.
*/
public static final String PARAMETER_KEY_VALUE_PAIRS = "configure-task-key-value-pairs";
/**
* Transfers can be several per task, so they're passed in as a json-ified
* list of objects.
*/
public static final String PARAMETER_TRANSFERS = "configure-task-transfers";
/**
* Replacements can be several per task, so they're passed in as a json-ified list of
* objects.
*/
public static final String PARAMETER_REPLACEMENTS = "configure-task-replacements";
public static final String actionVerb = "Configuring";
public static final Float DEFAULT_PRIORITY = 1.0f;
/**
* An optional brief description of config changes.
*/
@XmlAttribute(name = "summary")
public String summary;
@XmlAttribute(name = "id", required = true)
public String id;
@XmlElement(name="type")
private String configType;
@XmlElement(name = "set")
private List<ConfigurationKeyValue> keyValuePairs;
@XmlElement(name = "transfer")
private List<Transfer> transfers;
@XmlElement(name="replace")
private List<Replace> replacements;
@XmlElement(name="regex-replace")
private List<RegexReplace> regexReplacements;
/**
* Insert new content into an existing value by either prepending or
* appending. Each {@link Insert} will only run if:
* <ul>
* <li>The key specified by {@link Insert#key} exists.
* <li>The content specified by {@link Insert#value} is not found in the key's
* existing content.
* </ul>
*/
@XmlElement(name = "insert")
private List<Insert> inserts;
/**
* @return the config type
*/
public String getConfigType() {
return configType;
}
/**
* @return the list of <set key=foo value=bar/> items
*/
public List<ConfigurationKeyValue> getKeyValuePairs() {
return keyValuePairs;
}
/**
* @return the list of transfers, checking for appropriate null fields.
*/
public List<Transfer> getTransfers() {
if (null == transfers) {
return Collections.emptyList();
}
List<Transfer> list = new ArrayList<>();
for (Transfer t : transfers) {
switch (t.operation) {
case COPY:
case MOVE:
if (null != t.fromKey && null != t.toKey) {
list.add(t);
} else {
LOG.warn(String.format("Transfer %s is invalid", t));
}
break;
case DELETE:
if (null != t.deleteKey) {
list.add(t);
} else {
LOG.warn(String.format("Transfer %s is invalid", t));
}
break;
}
}
return list;
}
/**
* @return the replacement tokens, never {@code null}
*/
public List<Replace> getReplacements() {
if (null == replacements) {
return Collections.emptyList();
}
List<Replace> list = new ArrayList<>();
for (Replace r : replacements) {
if (null == r.key || null == r.find || null == r.replaceWith) {
LOG.warn(String.format("Replacement %s is invalid", r));
continue;
}
list.add(r);
}
return list;
}
/**
* @return the replacement tokens, never {@code null}
*/
public List<Replace> getRegexReplacements(Cluster cluster) {
if (null == regexReplacements) {
return Collections.emptyList();
}
List<Replace> list = new ArrayList<>();
for (RegexReplace regexReplaceObj : regexReplacements) {
if (null == regexReplaceObj.key || null == regexReplaceObj.find || null == regexReplaceObj.replaceWith) {
LOG.warn(String.format("Replacement %s is invalid", regexReplaceObj));
continue;
}
try{
Config config = cluster.getDesiredConfigByType(configType);
Map<String, String> properties = config.getProperties();
String content = properties.get(regexReplaceObj.key);
Pattern REGEX = Pattern.compile(regexReplaceObj.find, Pattern.MULTILINE);
Matcher patternMatchObj = REGEX.matcher(content);
if (patternMatchObj.find() && patternMatchObj.groupCount()==1) {
regexReplaceObj.find = patternMatchObj.group();
Replace rep = regexReplaceObj.copyToReplaceObject();
list.add(rep);
}
}catch(Exception e){
String message = "getRegexReplacements : Error while fetching config properties : key - " + regexReplaceObj.key + " find - " + regexReplaceObj.find;
LOG.error(message, e);
}
}
return list;
}
/**
* Gets the insertion directives.
*
* @return the inserts, or an empty list (never {@code null}).
*/
public List<Insert> getInsertions() {
if (null == inserts) {
return Collections.emptyList();
}
return inserts;
}
/**
* Used for configuration updates that should mask their values from being
* printed in plain text.
*/
@XmlAccessorType(XmlAccessType.FIELD)
public static class Masked {
@XmlAttribute(name = "mask")
public boolean mask = false;
/**
* The key to read for the if condition.
*/
@XmlAttribute(name = "if-key")
public String ifKey;
/**
* The config type to read for the if condition.
*/
@XmlAttribute(name = "if-type")
public String ifType;
/**
* The property value to compare against for the if condition.
*/
@XmlAttribute(name = "if-value")
public String ifValue;
/**
* The property key state for the if condition
*/
@XmlAttribute(name = "if-key-state")
public PropertyKeyState ifKeyState;
}
/**
* A key/value pair to set in the type specified by {@link ConfigUpgradeChangeDefinition#configType}
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "set")
public static class ConfigurationKeyValue extends Masked {
@XmlAttribute(name = "key")
public String key;
@XmlAttribute(name = "value")
public String value;
@Override
public String toString() {
return Objects.toStringHelper("Set").add("key", key)
.add("value", value)
.add("ifKey", ifKey)
.add("ifType", ifType)
.add("ifValue",ifValue)
.add("ifKeyState", ifKeyState).omitNullValues().toString();
}
}
/**
* A {@code transfer} element will copy, move, or delete the value of one type/key to another type/key.
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "transfer")
public static class Transfer extends Masked {
/**
* The type of operation, such as COPY or DELETE.
*/
@XmlAttribute(name = "operation")
public TransferOperation operation;
/**
* The configuration type to copy or move from.
*/
@XmlAttribute(name = "from-type")
public String fromType;
/**
* The key to copy or move the configuration from.
*/
@XmlAttribute(name = "from-key")
public String fromKey;
/**
* The key to copy the configuration value to.
*/
@XmlAttribute(name = "to-key")
public String toKey;
/**
* The configuration key to delete, or "*" for all.
*/
@XmlAttribute(name = "delete-key")
public String deleteKey;
/**
* If {@code true}, this will ensure that any changed properties are not
* removed during a {@link TransferOperation#DELETE}.
*/
@XmlAttribute(name = "preserve-edits")
public boolean preserveEdits = false;
/**
* A default value to use when the configurations don't contain the
* {@link #fromKey}.
*/
@XmlAttribute(name = "default-value")
public String defaultValue;
/**
* A data type to convert the configuration value to when the action is
* {@link TransferOperation#COPY}.
*/
@XmlAttribute(name = "coerce-to")
public TransferCoercionType coerceTo;
/**
* The keys to keep when the action is {@link TransferOperation#DELETE}.
*/
@XmlElement(name = "keep-key")
public List<String> keepKeys = new ArrayList<>();
@Override
public String toString() {
return Objects.toStringHelper(this).add("operation", operation)
.add("fromType", fromType)
.add("fromKey", fromKey)
.add("toKey", toKey)
.add("deleteKey", deleteKey)
.add("preserveEdits",preserveEdits)
.add("defaultValue", defaultValue)
.add("coerceTo", coerceTo)
.add("ifKey", ifKey)
.add("ifType", ifType)
.add("ifValue", ifValue)
.add("ifKeyState", ifKeyState)
.add("keepKeys", keepKeys).omitNullValues().toString();
}
}
/**
* Used to replace strings in a key with other strings. More complex
* scenarios will be possible with regex (when needed)
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "replace")
public static class Replace extends Masked {
/**
* The key name
*/
@XmlAttribute(name="key")
public String key;
/**
* The string to find
*/
@XmlAttribute(name="find")
public String find;
/**
* The string to replace
*/
@XmlAttribute(name="replace-with")
public String replaceWith;
@Override
public String toString() {
return Objects.toStringHelper(this).add("key", key)
.add("find", find)
.add("replaceWith", replaceWith)
.add("ifKey", ifKey)
.add("ifType", ifType)
.add("ifValue", ifValue)
.add("ifKeyState", ifKeyState).omitNullValues().toString();
}
}
/**
* Used to replace strings in a key with other strings. More complex
* scenarios are possible with regex.
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "regex-replace")
public static class RegexReplace extends Masked{
/**
* The key name
*/
@XmlAttribute(name="key")
public String key;
/**
* The string to find
*/
@XmlAttribute(name="find")
public String find;
/**
* The string to replace
*/
@XmlAttribute(name="replace-with")
public String replaceWith;
@Override
public String toString() {
return Objects.toStringHelper(this).add("key", key)
.add("find", find)
.add("replaceWith",replaceWith)
.add("ifKey", ifKey)
.add("ifType", ifType)
.add("ifValue", ifValue)
.add("ifKeyState", ifKeyState).omitNullValues().toString();
}
/***
* Copies a RegexReplace type object to Replace object.
* @return Replace object
*/
public Replace copyToReplaceObject(){
Replace rep = new Replace();
rep.find = find;
rep.key = key;
rep.replaceWith = replaceWith;
rep.ifKey = ifKey;
rep.ifType = ifType;
rep.ifValue = ifValue;
rep.ifKeyState = ifKeyState;
return rep;
}
}
/**
* Used to replace strings in a key with other strings. More complex scenarios
* will be possible with regex (when needed). If the value specified in
* {@link Insert#value} already exists, then it is not inserted again.
*/
@XmlAccessorType(XmlAccessType.FIELD)
@XmlType(name = "insert")
public static class Insert {
/**
* The key name
*/
@XmlAttribute(name = "key", required = true)
public String key;
/**
* The value to insert.
*/
@XmlAttribute(name = "value", required = true)
public String value;
/**
* The value to insert.
*/
@XmlAttribute(name = "insert-type", required = true)
public InsertType insertType = InsertType.APPEND;
/**
* {@code true} to insert a new line before inserting the {@link #value}.
*/
@XmlAttribute(name = "newline-before")
public boolean newlineBefore = false;
/**
* {@code true} to insert a new line after inserting the {@link #value}.
*/
@XmlAttribute(name = "newline-after")
public boolean newlineAfter = false;
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return Objects.toStringHelper(this).add("insertType", insertType)
.add("key", key)
.add("value",value)
.add("newlineBefore", newlineBefore)
.add("newlineAfter", newlineAfter).omitNullValues().toString();
}
}
/**
* The {@link InsertType} defines how to use the {@link Insert} directive.
*/
@XmlEnum
public enum InsertType {
/**
* Prepend the content.
*/
@XmlEnumValue("prepend")
PREPEND,
/**
* Append the content.
*/
@XmlEnumValue("append")
APPEND
}
}