/**
* Licensed to Cloudera, Inc. under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. Cloudera, Inc. 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 com.cloudera.flume.master;
import java.io.IOException;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.Map.Entry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.cloudera.flume.conf.FlumeSpecException;
import com.cloudera.flume.conf.FlumeConfigData;
import com.cloudera.flume.reporter.ReportEvent;
import com.google.common.base.Preconditions;
import com.google.common.collect.Multimap;
/**
* This translating configuration manager encapsulates the logic for having
* multiple configuration translations. There are parent configuration and self
* configuration manager. The parent may be a configuration manager that does
* storage or another translating manager and is used to hold pre-translated
* configurations. The self manager must be a storage configuration manager for
* keeping translated configurations.
*
* If in-place changes are ok, use the same configuration manager as the parent
* and self. If not, use a different configuration manager for parent and self.
*
* Read method calls on a TranslatingConfiguraitonManager always read from the
* self manager. Write method calls write to the parent manager and write the
* translated versions to the self manager.
*/
abstract public class TranslatingConfigurationManager implements
ConfigurationManager, Translator {
static final Logger LOG = LoggerFactory
.getLogger(TranslatingConfigurationManager.class);
ConfigurationManager parentMan;
ConfigurationManager selfMan;
/**
* Create a new translator where the parent and the self are the same.
*/
public TranslatingConfigurationManager(ConfigurationManager single) {
this.parentMan = single;
this.selfMan = single;
}
/**
* Create a new translator with a parent and a self.
*/
public TranslatingConfigurationManager(ConfigurationManager parent,
ConfigurationManager self) {
this.parentMan = parent;
this.selfMan = self;
}
/**
* {@inheritDoc}
*
* Gets the translated configuration data.
*/
@Override
synchronized public FlumeConfigData getConfig(String host) {
return selfMan.getConfig(host);
}
/**
* {@inheritDoc} setConfig actually writes two entries -- the version which is
* the user entered to the parent, and a translated version that a
* heartbeating node would read into the self table.
*/
@Override
synchronized public void setConfig(String logicalnode, String flowid,
String source, String sink) throws IOException, FlumeSpecException {
// if parent and self are different, save original to parent.
if (parentMan != selfMan) {
parentMan.setConfig(logicalnode, flowid, source, sink);
FlumeConfigData fcd = parentMan.getConfig(logicalnode);
// parent may have translated
source = fcd.getSourceConfig();
sink = fcd.getSinkConfig();
}
String xsink = translateSink(logicalnode, sink);
String xsource = translateSource(logicalnode, source);
// save the translated sink that is sent to the master.
selfMan.setConfig(logicalnode, flowid, xsource, xsink);
updateAll();
}
@Override
abstract public String getName();
/**
* Internal formatting method for FlumeConfigData and translated
* FlumeConfigData
*/
static void appendHtmlTranslatedFlumeConfigData(StringBuilder html,
String name, FlumeConfigData fcd, FlumeConfigData xfcd) {
html.append("\n<tr>");
html.append("<td>" + name + "</td>");
FlumeConfigData cfg = fcd;
html.append("<td>" + new Date(cfg.timestamp) + "</td>");
html.append("<td>" + cfg.sourceConfig + "</td>");
html.append("<td>" + cfg.sinkConfig + "</td>");
if (xfcd != null) {
html.append("<td>" + new Date(xfcd.timestamp) + "</td>");
html.append("<td>" + xfcd.sourceConfig + "</td>");
html.append("<td>" + xfcd.sinkConfig + "</td>");
} else {
html.append("<td></td><td></td><td></td>");
}
html.append("</tr>\n");
}
/**
* Builds a two html tables that display parent/self configs and logical node
* mapping.
*
* TODO convert to a report, do not depend on this output.
*/
@Override
synchronized public ReportEvent getReport() {
StringBuilder html = new StringBuilder();
html
.append("<h2>Node configuration</h2>\n<table border=\"1\"><tr>"
+ "<th>Node</th><th>Version</th><th>Source</th><th>Sink</th>"
+ "<th>Translated Version</th><th>Translated Source</th><th>Translated Sink</th>"
+ "</tr>");
Map<String, FlumeConfigData> cfgs = new TreeMap<String, FlumeConfigData>(
parentMan.getAllConfigs());
Map<String, FlumeConfigData> xcfgs = new TreeMap<String, FlumeConfigData>(
selfMan.getTranslatedConfigs());
for (Entry<String, FlumeConfigData> e : cfgs.entrySet()) {
String ln = e.getKey();
appendHtmlTranslatedFlumeConfigData(html, e.getKey(), e.getValue(), xcfgs
.get(ln));
}
html.append("</table>\n\n");
// a table that has a mapping from physical nodes to logical nodes.
html.append("<h2>Physical/Logical Node mapping</h2>\n<table border=\"1\">"
+ "<tr><th>physical node</th><th>logical node</th></tr>");
Multimap<String, String> nodes = parentMan.getLogicalNodeMap();
synchronized (nodes) {
for (Entry<String, Collection<String>> e : nodes.asMap().entrySet()) {
ConfigManager.appendHtmlPhysicalLogicalMapping(html, e.getKey(), e
.getValue());
}
}
html.append("</table>\n\n");
return ReportEvent.createLegacyHtmlReport("configs", html.toString());
}
/**
* {@inheritDoc} This always just forwards to the parent.
*/
@Override
synchronized public void loadConfigFile(String file) throws IOException {
parentMan.loadConfigFile(file);
refreshAll();
}
/**
* {@inheritDoc} This always just forwards to the parent.
*/
@Override
synchronized public void saveConfigFile(String file) throws IOException {
parentMan.saveConfigFile(file);
}
/**
* Returns the self configurations.
*/
@Override
synchronized public Map<String, FlumeConfigData> getAllConfigs() {
return parentMan.getAllConfigs();
}
/**
* Returns the translations of all configuraitons
*/
synchronized public Map<String, FlumeConfigData> getTranslatedConfigs() {
return selfMan.getAllConfigs();
}
/**
* This reads a configuration and the sets it again. This updates the version
* stamp and forces nodes to update their configurations.
*
* Since this manager intercepts the logical node configuration and writes the
* user specified node to a different value, we actually read the user
* specified source-sink pair and then use this manager's setConfig method to
* include the autogenerated source-sink pair.
*/
synchronized public void refresh(String logicalNode) throws IOException {
FlumeConfigData fcd = parentMan.getConfig(logicalNode);
if (fcd == null) {
throw new IOException("original " + logicalNode + " not found");
}
try {
setConfig(logicalNode, fcd.getFlowID(), fcd.getSourceConfig(), fcd
.getSinkConfig());
} catch (FlumeSpecException e) {
throw new IOException(e);
}
}
/**
* This reads a configuration and updates the version stamp only if the new
* configuration is different from the previous configuration.
*/
synchronized public void updateAll() throws IOException {
parentMan.updateAll();
Map<String, FlumeConfigData> updates = new HashMap<String, FlumeConfigData>();
for (Entry<String, FlumeConfigData> ent : parentMan.getTranslatedConfigs()
.entrySet()) {
String node = ent.getKey();
// get the original name
FlumeConfigData fcd = ent.getValue();
String src = fcd.getSourceConfig();
String snk = fcd.getSinkConfig();
String xsnk, xsrc;
try {
xsnk = translateSink(node, snk);
xsrc = translateSource(node, src);
FlumeConfigData selfData = selfMan.getConfig(node);
if (selfData != null && xsnk.equals(selfData.getSinkConfig())
&& xsrc.equals(selfData.getSourceConfig())) {
// same as before? do nothing
LOG.debug("xsnk==snk = " + xsnk);
LOG.debug("xsrc==src = " + xsrc);
continue;
}
FlumeConfigData xfcd = new FlumeConfigData(fcd);
xfcd.setSourceConfig(xsrc);
xfcd.setSinkConfig(xsnk);
updates.put(node, xfcd);
} catch (FlumeSpecException e) {
LOG.error("Internal Error: " + e.getLocalizedMessage(), e);
throw new IOException("Internal Error: " + e.getMessage());
}
}
selfMan.setBulkConfig(updates);
}
/**
* This reads a configuration and the sets it again. This updates the version
* stamp and forces nodes to update their configurations.
*
* Since this manager intercepts the logical node configuration and writes the
* user specified node to a different value, we actually read the user
* specified source-sink pair and then use this manager's setConfig method to
* include the autogenerated source-sink pair.
*/
@Override
synchronized public void refreshAll() throws IOException {
parentMan.refreshAll();
Map<String, FlumeConfigData> updates = new HashMap<String, FlumeConfigData>();
for (Entry<String, FlumeConfigData> ent : parentMan.getTranslatedConfigs()
.entrySet()) {
String node = ent.getKey();
// get the original name
FlumeConfigData fcd = ent.getValue();
String src = fcd.getSourceConfig();
String snk = fcd.getSinkConfig();
String xsnk, xsrc;
try {
xsnk = translateSink(node, snk);
xsrc = translateSource(node, src);
FlumeConfigData xfcd = new FlumeConfigData(fcd);
xfcd.setSinkConfig(xsnk);
xfcd.setSourceConfig(xsrc);
updates.put(node, xfcd);
} catch (FlumeSpecException e) {
LOG.error("Internal Error: " + e.getLocalizedMessage(), e);
throw new IOException("Internal Error: " + e.getMessage());
}
}
selfMan.setBulkConfig(updates);
}
/**
* Updates both the parent and self managers with the set of configurations.
*/
@Override
synchronized public void setBulkConfig(Map<String, FlumeConfigData> configs)
throws IOException {
Map<String, FlumeConfigData> updates = new HashMap<String, FlumeConfigData>();
Map<String, FlumeConfigData> selfupdates = new HashMap<String, FlumeConfigData>();
for (Entry<String, FlumeConfigData> ent : configs.entrySet()) {
String node = ent.getKey();
// get the original name
String src = ent.getValue().getSourceConfig();
String snk = ent.getValue().getSinkConfig();
String xsnk, xsrc;
try {
xsnk = translateSink(node, snk);
xsrc = translateSource(node, src);
if (selfMan != parentMan) {
FlumeConfigData fcd = new FlumeConfigData(ent.getValue());
updates.put(node, fcd);
}
FlumeConfigData xfcd = new FlumeConfigData(ent.getValue());
xfcd.setSinkConfig(xsnk);
xfcd.setSourceConfig(xsrc);
selfupdates.put(node, xfcd);
} catch (FlumeSpecException e) {
LOG.error("Internal Error: " + e.getLocalizedMessage(), e);
throw new IOException("Internal Error: " + e.getMessage());
}
}
parentMan.setBulkConfig(updates);
updateAll();
}
/**
* Remove the logical node.
*/
@Override
synchronized public void removeLogicalNode(String logicNode)
throws IOException {
// only remove once if parent == self
if (parentMan != selfMan) {
parentMan.removeLogicalNode(logicNode);
}
selfMan.removeLogicalNode(logicNode);
try {
updateAll();
} catch (IOException e) {
LOG.error("Error when removing logical node " + logicNode, e);
}
}
/**
* Start the sub managers.
*/
@Override
synchronized public void start() throws IOException {
// if parent == self, only start once.
Preconditions.checkNotNull(this.parentMan,
"Trying to start with null cfgMan");
if (parentMan != selfMan) {
parentMan.start();
}
selfMan.start();
updateAll();
}
/**
* Stop the sub managers.
*/
@Override
synchronized public void stop() throws IOException {
// if parent == self, only stop once.
Preconditions.checkNotNull(this.parentMan,
"Trying to stop with null cfgMan");
if (parentMan != selfMan) {
parentMan.stop();
}
selfMan.stop();
}
// //////////////////////////////////////////////////////////////////////////////
// TODO decouple mapping from logical node configuration. Currently,
// only forward node mappings calls to parent.
/**
* {@inheritDoc}
*/
@Override
synchronized public Multimap<String, String> getLogicalNodeMap() {
return parentMan.getLogicalNodeMap();
}
/**
* {@inheritDoc}
*/
synchronized public List<String> getLogicalNode(String physNode) {
return parentMan.getLogicalNode(physNode);
}
/**
* {@inheritDoc}
*/
synchronized public Map<String, Integer> getChokeMap(String physNode) {
return parentMan.getChokeMap(physNode);
}
/**
* {@inheritDoc}
*/
@Override
synchronized public boolean addLogicalNode(String physNode, String logicNode) {
boolean result;
result = false;
if (!getLogicalNodeMap().containsValue(logicNode)) {
result = parentMan.addLogicalNode(physNode, logicNode);
}
try {
updateAll();
} catch (IOException e) {
LOG.error("Error when mapping logical->physical node" + logicNode + "->"
+ physNode, e);
}
return result;
}
/**
* {@inheritDoc}
*/
@Override
synchronized public void addChokeLimit(String physNode, String chokeID,
int limit) {
parentMan.addChokeLimit(physNode, chokeID, limit);
}
/**
* {@inheritDoc}
*/
@Override
synchronized public String getPhysicalNode(String logicalNode) {
return parentMan.getPhysicalNode(logicalNode);
}
/**
* {@inheritDoc}
*/
@Override
synchronized public void unmapLogicalNode(String physNode, String logicNode) {
parentMan.unmapLogicalNode(physNode, logicNode);
try {
updateAll();
} catch (IOException e) {
LOG.error("Error when unmapping logical node " + e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*/
@Override
synchronized public void unmapAllLogicalNodes() throws IOException {
// mapping is only on parent.
parentMan.unmapAllLogicalNodes();
try {
updateAll();
} catch (IOException e) {
LOG.error("Error when unmapping all logical nodes" + e.getMessage(), e);
}
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return getTranslatedConfigs().toString();
}
}