/* * 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.flume.agent.embedded; import java.util.Arrays; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import org.apache.flume.FlumeException; import org.apache.flume.annotations.InterfaceAudience; import org.apache.flume.annotations.InterfaceStability; import org.apache.flume.conf.BasicConfigurationConstants; import org.apache.flume.conf.channel.ChannelType; import org.apache.flume.conf.sink.SinkProcessorType; import org.apache.flume.conf.sink.SinkType; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; /** * Stores publicly accessible configuration constants and private * configuration constants and methods. */ @InterfaceAudience.Public @InterfaceStability.Stable public class EmbeddedAgentConfiguration { public static final String SEPERATOR = "."; private static final Joiner JOINER = Joiner.on(SEPERATOR); private static final String TYPE = "type"; /** * Prefix for source properties */ public static final String SOURCE = "source"; /** * Prefix for channel properties */ public static final String CHANNEL = "channel"; /** * Prefix for sink processor properties */ public static final String SINK_PROCESSOR = "processor"; /** * Space delimited list of sink names: e.g. sink1 sink2 sink3 */ public static final String SINKS = "sinks"; public static final String SINKS_PREFIX = join(SINKS, ""); /** * Source type, choices are `embedded' */ public static final String SOURCE_TYPE = join(SOURCE, TYPE); /** * Prefix for passing configuration parameters to the source */ public static final String SOURCE_PREFIX = join(SOURCE, ""); /** * Channel type, choices are `memory' or `file' */ public static final String CHANNEL_TYPE = join(CHANNEL, TYPE); /** * Prefix for passing configuration parameters to the channel */ public static final String CHANNEL_PREFIX = join(CHANNEL, ""); /** * Sink processor type, choices are `default', `failover' or `load_balance' */ public static final String SINK_PROCESSOR_TYPE = join(SINK_PROCESSOR, TYPE); /** * Prefix for passing configuration parameters to the sink processor */ public static final String SINK_PROCESSOR_PREFIX = join(SINK_PROCESSOR, ""); /** * Embedded source which provides simple in-memory transfer to channel. * Use this source via the put,putAll methods on the EmbeddedAgent. This * is the only supported source to use for Embedded Agents. */ public static final String SOURCE_TYPE_EMBEDDED = EmbeddedSource.class.getName(); private static final String SOURCE_TYPE_EMBEDDED_ALIAS = "EMBEDDED"; /** * Memory channel which stores events in heap. See Flume User Guide for * configuration information. This is the recommended channel to use for * Embedded Agents. */ public static final String CHANNEL_TYPE_MEMORY = ChannelType.MEMORY.name(); /** * Spillable Memory channel which stores events in heap. See Flume User Guide for * configuration information. This is the recommended channel to use for * Embedded Agents. */ public static final String CHANNEL_TYPE_SPILLABLEMEMORY = ChannelType.SPILLABLEMEMORY.name(); /** * File based channel which stores events in on local disk. See Flume User * Guide for configuration information. */ public static final String CHANNEL_TYPE_FILE = ChannelType.FILE.name(); /** * Avro sink which can send events to a downstream avro source. This is the * only supported sink for Embedded Agents. */ public static final String SINK_TYPE_AVRO = SinkType.AVRO.name(); /** * Default sink processors which may be used when there is only a single sink. */ public static final String SINK_PROCESSOR_TYPE_DEFAULT = SinkProcessorType.DEFAULT.name(); /** * Failover sink processor. See Flume User Guide for configuration * information. */ public static final String SINK_PROCESSOR_TYPE_FAILOVER = SinkProcessorType.FAILOVER.name(); /** * Load balancing sink processor. See Flume User Guide for configuration * information. */ public static final String SINK_PROCESSOR_TYPE_LOAD_BALANCE = SinkProcessorType.LOAD_BALANCE.name(); private static final String[] ALLOWED_SOURCES = { SOURCE_TYPE_EMBEDDED_ALIAS, SOURCE_TYPE_EMBEDDED, }; private static final String[] ALLOWED_CHANNELS = { CHANNEL_TYPE_MEMORY, CHANNEL_TYPE_FILE }; private static final String[] ALLOWED_SINKS = { SINK_TYPE_AVRO }; private static final String[] ALLOWED_SINK_PROCESSORS = { SINK_PROCESSOR_TYPE_DEFAULT, SINK_PROCESSOR_TYPE_FAILOVER, SINK_PROCESSOR_TYPE_LOAD_BALANCE }; private static final ImmutableList<String> DISALLOWED_SINK_NAMES = ImmutableList.of("source", "channel", "processor"); private static void validate(String name, Map<String, String> properties) throws FlumeException { if (properties.containsKey(SOURCE_TYPE)) { checkAllowed(ALLOWED_SOURCES, properties.get(SOURCE_TYPE)); } checkRequired(properties, CHANNEL_TYPE); checkAllowed(ALLOWED_CHANNELS, properties.get(CHANNEL_TYPE)); checkRequired(properties, SINKS); String sinkNames = properties.get(SINKS); for (String sink : sinkNames.split("\\s+")) { if (DISALLOWED_SINK_NAMES.contains(sink.toLowerCase(Locale.ENGLISH))) { throw new FlumeException("Sink name " + sink + " is one of the" + " disallowed sink names: " + DISALLOWED_SINK_NAMES); } String key = join(sink, TYPE); checkRequired(properties, key); checkAllowed(ALLOWED_SINKS, properties.get(key)); } checkRequired(properties, SINK_PROCESSOR_TYPE); checkAllowed(ALLOWED_SINK_PROCESSORS, properties.get(SINK_PROCESSOR_TYPE)); } /** * Folds embedded configuration structure into an agent configuration. * Should only be called after validate returns without error. * * @param name - agent name * @param properties - embedded agent configuration * @return configuration applicable to a flume agent */ @InterfaceAudience.Private @InterfaceStability.Unstable static Map<String, String> configure(String name, Map<String, String> properties) throws FlumeException { validate(name, properties); // we are going to modify the properties as we parse the config properties = new HashMap<String, String>(properties); if (!properties.containsKey(SOURCE_TYPE) || SOURCE_TYPE_EMBEDDED_ALIAS.equalsIgnoreCase(properties.get(SOURCE_TYPE))) { properties.put(SOURCE_TYPE, SOURCE_TYPE_EMBEDDED); } String sinkNames = properties.remove(SINKS); String strippedName = name.replaceAll("\\s+",""); String sourceName = "source-" + strippedName; String channelName = "channel-" + strippedName; String sinkGroupName = "sink-group-" + strippedName; /* * Now we are going to process the user supplied configuration * and generate an agent configuration. This is only to supply * a simpler client api than passing in an entire agent configuration. */ // user supplied config -> agent configuration Map<String, String> result = Maps.newHashMap(); /* * First we are going to setup all the root level pointers. I.E * point the agent at the components, sink group at sinks, and * source at the channel. */ // point agent at source result.put(join(name, BasicConfigurationConstants.CONFIG_SOURCES), sourceName); // point agent at channel result.put(join(name, BasicConfigurationConstants.CONFIG_CHANNELS), channelName); // point agent at sinks result.put(join(name, BasicConfigurationConstants.CONFIG_SINKS), sinkNames); // points the agent at the sinkgroup result.put(join(name, BasicConfigurationConstants.CONFIG_SINKGROUPS), sinkGroupName); // points the sinkgroup at the sinks result.put(join(name, BasicConfigurationConstants.CONFIG_SINKGROUPS, sinkGroupName, SINKS), sinkNames); // points the source at the channel result.put(join(name, BasicConfigurationConstants.CONFIG_SOURCES, sourceName, BasicConfigurationConstants.CONFIG_CHANNELS), channelName); // Properties will be modified during iteration so we need a // copy of the keys. Set<String> userProvidedKeys = new HashSet<String>(properties.keySet()); /* * Second process the sink configuration and point the sinks * at the channel. */ for (String sink : sinkNames.split("\\s+")) { for (String key : userProvidedKeys) { String value = properties.get(key); if (key.startsWith(sink + SEPERATOR)) { properties.remove(key); result.put(join(name, BasicConfigurationConstants.CONFIG_SINKS, key), value); } } // point the sink at the channel result.put(join(name, BasicConfigurationConstants.CONFIG_SINKS, sink, BasicConfigurationConstants.CONFIG_CHANNEL), channelName); } /* * Third, process all remaining configuration items, prefixing them * correctly and then passing them on to the agent. */ userProvidedKeys = new HashSet<String>(properties.keySet()); for (String key : userProvidedKeys) { String value = properties.get(key); if (key.startsWith(SOURCE_PREFIX)) { // users use `source' but agent needs the actual source name key = key.replaceFirst(SOURCE, sourceName); result.put(join(name, BasicConfigurationConstants.CONFIG_SOURCES, key), value); } else if (key.startsWith(CHANNEL_PREFIX)) { // users use `channel' but agent needs the actual channel name key = key.replaceFirst(CHANNEL, channelName); result.put(join(name, BasicConfigurationConstants.CONFIG_CHANNELS, key), value); } else if (key.startsWith(SINK_PROCESSOR_PREFIX)) { // agent.sinkgroups.sinkgroup.processor.* result.put(join(name, BasicConfigurationConstants.CONFIG_SINKGROUPS, sinkGroupName, key), value); } else { // XXX should we simply ignore this? throw new FlumeException("Unknown configuration " + key); } } return result; } private static void checkAllowed(String[] allowedTypes, String type) { boolean isAllowed = false; type = type.trim(); for (String allowedType : allowedTypes) { if (allowedType.equalsIgnoreCase(type)) { isAllowed = true; break; } } if (!isAllowed) { throw new FlumeException("Component type of " + type + " is not in " + "allowed types of " + Arrays.toString(allowedTypes)); } } private static void checkRequired(Map<String, String> properties, String name) { if (!properties.containsKey(name)) { throw new FlumeException("Required parameter not found " + name); } } private static String join(String... parts) { return JOINER.join(parts); } private EmbeddedAgentConfiguration() {} }