// Copyright 2016 Twitter. All rights reserved. // // Licensed 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.twitter.heron.spi.utils; import java.io.File; import java.io.IOException; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.logging.Logger; import com.twitter.heron.api.Config; import com.twitter.heron.api.generated.TopologyAPI; import com.twitter.heron.common.basics.ByteAmount; /** * Utility to process TopologyAPI.Topology proto */ public final class TopologyUtils { private static final Logger LOG = Logger.getLogger(TopologyUtils.class.getName()); private TopologyUtils() { } public static TopologyAPI.Topology getTopology(String topologyDefnFile) { try { byte[] topologyDefn = Files.readAllBytes(Paths.get(topologyDefnFile)); TopologyAPI.Topology topology = TopologyAPI.Topology.parseFrom(topologyDefn); if (!TopologyUtils.verifyTopology(topology)) { throw new RuntimeException("Topology object is Malformed"); } return topology; } catch (IOException e) { throw new RuntimeException("Failed to read/parse content of " + topologyDefnFile, e); } } public static String getConfigWithDefault( List<TopologyAPI.Config.KeyValue> config, String key, String defaultValue) { for (TopologyAPI.Config.KeyValue kv : config) { if (kv.getKey().equals(key)) { return kv.getValue(); } } return defaultValue; } public static Long getConfigWithDefault( List<TopologyAPI.Config.KeyValue> config, String key, Long defaultValue) { return Long.parseLong(getConfigWithDefault(config, key, Long.toString(defaultValue))); } public static Integer getConfigWithDefault( List<TopologyAPI.Config.KeyValue> config, String key, Integer defaultValue) { return Integer.parseInt(getConfigWithDefault(config, key, Integer.toString(defaultValue))); } public static Double getConfigWithDefault( List<TopologyAPI.Config.KeyValue> config, String key, Double defaultValue) { return Double.parseDouble(getConfigWithDefault(config, key, Double.toString(defaultValue))); } public static ByteAmount getConfigWithDefault( List<TopologyAPI.Config.KeyValue> config, String key, ByteAmount defaultValue) { long defaultBytes = defaultValue.asBytes(); return ByteAmount.fromBytes(getConfigWithDefault(config, key, defaultBytes)); } public static String getConfigWithException( List<TopologyAPI.Config.KeyValue> config, String key) { for (TopologyAPI.Config.KeyValue kv : config) { if (kv.getKey().equals(key)) { return kv.getValue(); } } throw new RuntimeException("Missing config for required key " + key); } public static Map<String, Integer> getComponentParallelism(TopologyAPI.Topology topology) { Map<String, Integer> parallelismMap = new HashMap<>(); for (TopologyAPI.Spout spout : topology.getSpoutsList()) { String componentName = spout.getComp().getName(); String parallelism = getConfigWithException( spout.getComp().getConfig().getKvsList(), Config.TOPOLOGY_COMPONENT_PARALLELISM).trim(); parallelismMap.put(componentName, Integer.parseInt(parallelism)); } for (TopologyAPI.Bolt bolt : topology.getBoltsList()) { String componentName = bolt.getComp().getName(); String parallelism = getConfigWithException( bolt.getComp().getConfig().getKvsList(), Config.TOPOLOGY_COMPONENT_PARALLELISM).trim(); parallelismMap.put(componentName, Integer.parseInt(parallelism)); } return parallelismMap; } public static String getInstanceJvmOptions(TopologyAPI.Topology topology) { List<TopologyAPI.Config.KeyValue> topologyConfig = topology.getTopologyConfig().getKvsList(); return getConfigWithDefault(topologyConfig, Config.TOPOLOGY_WORKER_CHILDOPTS, ""); } public static String getComponentJvmOptions(TopologyAPI.Topology topology) { List<TopologyAPI.Config.KeyValue> topologyConfig = topology.getTopologyConfig().getKvsList(); return getConfigWithDefault(topologyConfig, Config.TOPOLOGY_COMPONENT_JVMOPTS, ""); } public static int getTotalInstance(TopologyAPI.Topology topology) { Map<String, Integer> parallelismMap = getComponentParallelism(topology); int numInstances = 0; for (int parallelism : parallelismMap.values()) { numInstances += parallelism; } return numInstances; } /** * Verify if the given topology has all the necessary information */ public static boolean verifyTopology(TopologyAPI.Topology topology) { if (!topology.hasName() || topology.getName().isEmpty()) { LOG.severe("Missing topology name"); return false; } if (topology.getName().contains(".") || topology.getName().contains("/")) { LOG.severe("Invalid topology name. Topology name shouldn't have . or /"); return false; } // Only verify ram map string well-formed. getComponentRamMapConfig(topology); // Verify all bolts input streams exist. First get all output streams. Set<String> outputStreams = new HashSet<>(); for (TopologyAPI.Spout spout : topology.getSpoutsList()) { for (TopologyAPI.OutputStream stream : spout.getOutputsList()) { outputStreams.add( stream.getStream().getComponentName() + "/" + stream.getStream().getId()); } } for (TopologyAPI.Bolt bolt : topology.getBoltsList()) { for (TopologyAPI.OutputStream stream : bolt.getOutputsList()) { outputStreams.add( stream.getStream().getComponentName() + "/" + stream.getStream().getId()); } } // Match output streams with input streams. for (TopologyAPI.Bolt bolt : topology.getBoltsList()) { for (TopologyAPI.InputStream stream : bolt.getInputsList()) { String key = stream.getStream().getComponentName() + "/" + stream.getStream().getId(); if (!outputStreams.contains(key)) { LOG.severe("Invalid input stream " + key + " existing streams are " + outputStreams); return false; } } } // TODO(nbhagat): Should we enforce all output stream must be consumed? return true; } public static String getAdditionalClassPath(TopologyAPI.Topology topology) { List<TopologyAPI.Config.KeyValue> topologyConfig = topology.getTopologyConfig().getKvsList(); return getConfigWithDefault(topologyConfig, Config.TOPOLOGY_ADDITIONAL_CLASSPATH, ""); } /** * Parses the value in Config.TOPOLOGY_COMPONENT_RAMMAP, * and returns a map containing only component specified. * Returns a empty map if the Config is not set * * @param topology the topology def * @return a map (componentName -> ram required) */ public static Map<String, ByteAmount> getComponentRamMapConfig(TopologyAPI.Topology topology) { List<TopologyAPI.Config.KeyValue> topologyConfig = topology.getTopologyConfig().getKvsList(); Map<String, ByteAmount> ramMap = new HashMap<>(); // Get the set of component names to make sure the config only specifies valid component name Set<String> componentNames = getComponentParallelism(topology).keySet(); // Parse the config value String ramMapStr = getConfigWithDefault( topologyConfig, Config.TOPOLOGY_COMPONENT_RAMMAP, (String) null); if (ramMapStr != null) { String[] ramMapTokens = ramMapStr.split(","); for (String token : ramMapTokens) { if (token.trim().isEmpty()) { continue; } String[] componentAndRam = token.split(":"); if (componentAndRam.length != 2) { throw new RuntimeException("Malformed component rammap"); } if (!componentNames.contains(componentAndRam[0])) { throw new RuntimeException("Invalid component. " + componentAndRam[0] + " not found"); } long requiredRam = Long.parseLong(componentAndRam[1]); ramMap.put(componentAndRam[0], ByteAmount.fromBytes(requiredRam)); } } return ramMap; } // TODO: in a PR of it's own rename this to getNumStreamManagers to be correct public static int getNumContainers(TopologyAPI.Topology topology) { List<TopologyAPI.Config.KeyValue> topologyConfig = topology.getTopologyConfig().getKvsList(); return Integer.parseInt(TopologyUtils.getConfigWithDefault( topologyConfig, Config.TOPOLOGY_STMGRS, "1").trim()); } // TODO(nbhagat): libs is dependent on pants for building. Instead take classpath as argument. public static String makeClassPath(TopologyAPI.Topology topology, String originalPackageFile) { String originalPackage = new File(originalPackageFile).getName(); StringBuilder classPathBuilder = new StringBuilder(); // TODO(nbhagat): Take type of package as argument. if (originalPackage.endsWith(".jar")) { // Bundled jar classPathBuilder.append(originalPackage); } else { // Bundled tar String topologyJar = originalPackage.replace(".tar.gz", "").replace(".tar", "") + ".jar"; classPathBuilder.append(String.format("libs/*:%s", topologyJar)); } String additionalClasspath = TopologyUtils.getAdditionalClassPath(topology); if (!additionalClasspath.isEmpty()) { classPathBuilder.append(":"); classPathBuilder.append(TopologyUtils.getAdditionalClassPath(topology)); } return classPathBuilder.toString(); } public static String lookUpTopologyDefnFile(String dir, String filename) { String pattern = String.format("glob:%s/%s.defn", dir, filename); PathMatcher matcher = FileSystems.getDefault().getPathMatcher(pattern); for (File file : new File(dir).listFiles()) { if (matcher.matches(file.toPath())) { // We would return the first one matched return file.getPath(); } } throw new IllegalStateException("Failed to find topology defn file"); } }