You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@metron.apache.org by ce...@apache.org on 2016/09/26 19:15:20 UTC

[2/3] incubator-metron git commit: METRON-452: Add rudimentary configuration management functions to Stellar closes apache/incubator-metron#269

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/main/java/org/apache/metron/management/ConfigurationFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ConfigurationFunctions.java b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ConfigurationFunctions.java
new file mode 100644
index 0000000..ea9f93a
--- /dev/null
+++ b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ConfigurationFunctions.java
@@ -0,0 +1,301 @@
+/**
+ * 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.metron.management;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.base.Joiner;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import org.apache.commons.configuration.ConfigurationUtils;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.framework.recipes.cache.ChildData;
+import org.apache.curator.framework.recipes.cache.TreeCache;
+import org.apache.curator.framework.recipes.cache.TreeCacheEvent;
+import org.apache.curator.framework.recipes.cache.TreeCacheListener;
+import org.apache.log4j.Logger;
+import org.apache.metron.common.Constants;
+import org.apache.metron.common.configuration.ConfigurationType;
+import org.apache.metron.common.configuration.ConfigurationsUtils;
+import org.apache.metron.common.configuration.SensorParserConfig;
+import org.apache.metron.common.configuration.enrichment.SensorEnrichmentConfig;
+import org.apache.metron.common.dsl.Context;
+import org.apache.metron.common.dsl.ParseException;
+import org.apache.metron.common.dsl.Stellar;
+import org.apache.metron.common.dsl.StellarFunction;
+import org.apache.metron.common.utils.ConversionUtils;
+import org.apache.metron.common.utils.JSONUtils;
+
+import java.util.*;
+
+public class ConfigurationFunctions {
+  private static final Logger LOG = Logger.getLogger(ConfigurationFunctions.class);
+  private static EnumMap<ConfigurationType, Object> configMap = new EnumMap<ConfigurationType, Object>(ConfigurationType.class) {{
+    for(ConfigurationType ct : ConfigurationType.values()) {
+      put(ct, Collections.synchronizedMap(new HashMap<String, String>()));
+    }
+    put(ConfigurationType.GLOBAL, "");
+    put(ConfigurationType.PROFILER, "");
+  }};
+  private static synchronized void setupTreeCache(Context context) throws Exception {
+    try {
+      Optional<Object> treeCacheOpt = context.getCapability("treeCache");
+      if (treeCacheOpt.isPresent()) {
+        return;
+      }
+    }
+    catch(IllegalStateException ex) {
+
+    }
+    Optional<Object> clientOpt = context.getCapability(Context.Capabilities.ZOOKEEPER_CLIENT);
+    if(!clientOpt.isPresent()) {
+      throw new IllegalStateException("I expected a zookeeper client to exist and it did not.  Please connect to zookeeper.");
+    }
+    CuratorFramework client = (CuratorFramework) clientOpt.get();
+    TreeCache cache = new TreeCache(client, Constants.ZOOKEEPER_TOPOLOGY_ROOT);
+    TreeCacheListener listener = new TreeCacheListener() {
+      @Override
+      public void childEvent(CuratorFramework client, TreeCacheEvent event) throws Exception {
+        if (event.getType().equals(TreeCacheEvent.Type.NODE_ADDED) || event.getType().equals(TreeCacheEvent.Type.NODE_UPDATED)) {
+          String path = event.getData().getPath();
+          byte[] data = event.getData().getData();
+          String sensor = Iterables.getLast(Splitter.on("/").split(path), null);
+          if (path.startsWith(ConfigurationType.PARSER.getZookeeperRoot())) {
+            Map<String, String> sensorMap = (Map<String, String>)configMap.get(ConfigurationType.PARSER);
+            sensorMap.put(sensor, new String(data));
+          } else if (ConfigurationType.GLOBAL.getZookeeperRoot().equals(path)) {
+            configMap.put(ConfigurationType.GLOBAL, new String(data));
+          } else if (ConfigurationType.PROFILER.getZookeeperRoot().equals(path)) {
+            configMap.put(ConfigurationType.PROFILER, new String(data));
+          } else if (path.startsWith(ConfigurationType.ENRICHMENT.getZookeeperRoot())) {
+            Map<String, String> sensorMap = (Map<String, String>)configMap.get(ConfigurationType.ENRICHMENT);
+            sensorMap.put(sensor, new String(data));
+          }
+        }
+        else if(event.getType().equals(TreeCacheEvent.Type.NODE_REMOVED)) {
+          String path = event.getData().getPath();
+          String sensor = Iterables.getLast(Splitter.on("/").split(path), null);
+          if (path.startsWith(ConfigurationType.PARSER.getZookeeperRoot())) {
+            Map<String, String> sensorMap = (Map<String, String>)configMap.get(ConfigurationType.PARSER);
+            sensorMap.remove(sensor);
+          }
+          else if (path.startsWith(ConfigurationType.ENRICHMENT.getZookeeperRoot())) {
+            Map<String, String> sensorMap = (Map<String, String>)configMap.get(ConfigurationType.ENRICHMENT);
+            sensorMap.remove(sensor);
+          }
+          else if (ConfigurationType.PROFILER.getZookeeperRoot().equals(path)) {
+            configMap.put(ConfigurationType.PROFILER, null);
+          }
+          else if (ConfigurationType.GLOBAL.getZookeeperRoot().equals(path)) {
+            configMap.put(ConfigurationType.GLOBAL, null);
+          }
+        }
+      }
+    };
+    cache.getListenable().addListener(listener);
+    cache.start();
+    for(ConfigurationType ct : ConfigurationType.values()) {
+      switch(ct) {
+        case GLOBAL:
+        case PROFILER:
+          {
+            String data = "";
+            try {
+              byte[] bytes = ConfigurationsUtils.readFromZookeeper(ct.getZookeeperRoot(), client);
+              data = new String(bytes);
+            }
+            catch(Exception ex) {
+
+            }
+            configMap.put(ct, data);
+          }
+          break;
+        case ENRICHMENT:
+        case PARSER:
+          {
+            List<String> sensorTypes = client.getChildren().forPath(ct.getZookeeperRoot());
+            Map<String, String> sensorMap = (Map<String, String>)configMap.get(ct);
+            for(String sensorType : sensorTypes) {
+              sensorMap.put(sensorType, new String(ConfigurationsUtils.readFromZookeeper(ct.getZookeeperRoot() + "/" + sensorType, client)));
+            }
+          }
+          break;
+      }
+    }
+    context.addCapability("treeCache", () -> cache);
+  }
+
+  @Stellar(
+           namespace = "CONFIG"
+          ,name = "GET"
+          ,description = "Retrieve a Metron configuration from zookeeper."
+          ,params = {"type - One of ENRICHMENT, PARSER, GLOBAL, PROFILER"
+                    , "sensor - Sensor to retrieve (required for enrichment and parser, not used for profiler and global)"
+                    , "emptyIfNotPresent - If true, then return an empty, minimally viable config"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class ConfigGet implements StellarFunction {
+    boolean initialized = false;
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      ConfigurationType type = ConfigurationType.valueOf((String)args.get(0));
+      boolean emptyIfNotPresent = true;
+
+      switch(type) {
+        case GLOBAL:
+        case PROFILER:
+          return configMap.get(type);
+        case PARSER: {
+          String sensor = (String) args.get(1);
+          if(args.size() > 2) {
+            emptyIfNotPresent = ConversionUtils.convert(args.get(2), Boolean.class);
+          }
+          Map<String, String> sensorMap = (Map<String, String>) configMap.get(type);
+          String ret = sensorMap.get(sensor);
+          if (ret == null && emptyIfNotPresent ) {
+            SensorParserConfig config = new SensorParserConfig();
+            config.setSensorTopic(sensor);
+            try {
+              ret = JSONUtils.INSTANCE.toJSON(config, true);
+            } catch (JsonProcessingException e) {
+              LOG.error("Unable to serialize default object: " + e.getMessage(), e);
+              throw new ParseException("Unable to serialize default object: " + e.getMessage(), e);
+            }
+          }
+          return ret;
+        }
+        case ENRICHMENT: {
+          String sensor = (String) args.get(1);
+          if(args.size() > 2) {
+            emptyIfNotPresent = ConversionUtils.convert(args.get(2), Boolean.class);
+          }
+          Map<String, String> sensorMap = (Map<String, String>) configMap.get(type);
+          String ret = sensorMap.get(sensor);
+          if (ret == null && emptyIfNotPresent ) {
+            SensorEnrichmentConfig config = new SensorEnrichmentConfig();
+            config.setIndex(sensor);
+            try {
+              ret = JSONUtils.INSTANCE.toJSON(config, true);
+            } catch (JsonProcessingException e) {
+              LOG.error("Unable to serialize default object: " + e.getMessage(), e);
+              throw new ParseException("Unable to serialize default object: " + e.getMessage(), e);
+            }
+          }
+          return ret;
+        }
+        default:
+          throw new UnsupportedOperationException("Unable to support type " + type);
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+      try {
+        setupTreeCache(context);
+      } catch (Exception e) {
+        LOG.error("Unable to initialize: " + e.getMessage(), e);
+      }
+      finally {
+        initialized = true;
+      }
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return initialized;
+    }
+  }
+  @Stellar(
+           namespace = "CONFIG"
+          ,name = "PUT"
+          ,description = "Updates a Metron config to Zookeeper."
+          ,params = {"type - One of ENRICHMENT, PARSER, GLOBAL, PROFILER"
+                    ,"config - The config (a string in JSON form) to update"
+                    , "sensor - Sensor to retrieve (required for enrichment and parser, not used for profiler and global)"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class ConfigPut implements StellarFunction {
+    private CuratorFramework client;
+    private boolean initialized = false;
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      ConfigurationType type = ConfigurationType.valueOf((String)args.get(0));
+      String config = (String)args.get(1);
+      if(config == null) {
+        return null;
+      }
+      try {
+        switch (type) {
+          case GLOBAL:
+            ConfigurationsUtils.writeGlobalConfigToZookeeper(config.getBytes(), client);
+            break;
+          case PROFILER:
+            ConfigurationsUtils.writeProfilerConfigToZookeeper(config.getBytes(), client);
+            break;
+          case ENRICHMENT:
+          {
+            String sensor = (String) args.get(2);
+            if(sensor == null) {
+              return null;
+            }
+            ConfigurationsUtils.writeSensorEnrichmentConfigToZookeeper(sensor, config.getBytes(), client);
+          }
+          break;
+          case PARSER:
+            {
+            String sensor = (String) args.get(2);
+              if(sensor == null) {
+              return null;
+            }
+            ConfigurationsUtils.writeSensorParserConfigToZookeeper(sensor, config.getBytes(), client);
+          }
+          break;
+        }
+      }
+      catch(Exception ex) {
+        LOG.error("Unable to put config: " + ex.getMessage(), ex);
+        throw new ParseException("Unable to put config: " + ex.getMessage(), ex);
+      }
+      return null;
+    }
+
+    @Override
+    public void initialize(Context context) {
+      Optional<Object> clientOpt = context.getCapability(Context.Capabilities.ZOOKEEPER_CLIENT);
+      if(!clientOpt.isPresent()) {
+        throw new IllegalStateException("I expected a zookeeper client to exist and it did not.  Please connect to zookeeper.");
+      }
+      client = (CuratorFramework) clientOpt.get();
+      try {
+        setupTreeCache(context);
+      } catch (Exception e) {
+        LOG.error("Unable to initialize: " + e.getMessage(), e);
+      }
+      finally {
+        initialized = true;
+      }
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return initialized;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/main/java/org/apache/metron/management/EnrichmentConfigFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/main/java/org/apache/metron/management/EnrichmentConfigFunctions.java b/metron-platform/metron-management/src/main/java/org/apache/metron/management/EnrichmentConfigFunctions.java
new file mode 100644
index 0000000..b1566ca
--- /dev/null
+++ b/metron-platform/metron-management/src/main/java/org/apache/metron/management/EnrichmentConfigFunctions.java
@@ -0,0 +1,345 @@
+/**
+ * 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.metron.management;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.jakewharton.fliptables.FlipTable;
+import org.apache.log4j.Logger;
+import org.apache.metron.common.configuration.FieldTransformer;
+import org.apache.metron.common.configuration.enrichment.EnrichmentConfig;
+import org.apache.metron.common.configuration.enrichment.SensorEnrichmentConfig;
+import org.apache.metron.common.dsl.Context;
+import org.apache.metron.common.dsl.ParseException;
+import org.apache.metron.common.dsl.Stellar;
+import org.apache.metron.common.dsl.StellarFunction;
+import org.apache.metron.common.utils.ConversionUtils;
+import org.apache.metron.common.utils.JSONUtils;
+
+import java.util.*;
+
+import static org.apache.metron.common.configuration.ConfigurationType.ENRICHMENT;
+
+public class EnrichmentConfigFunctions {
+
+  private static final Logger LOG = Logger.getLogger(ConfigurationFunctions.class);
+  public enum Type {
+    ENRICHMENT, THREAT_INTEL, THREATINTEL;
+  }
+  public static Map<String, Object> getStellarHandler(EnrichmentConfig enrichmentConfig) {
+    Map<String, Object> fieldMap = enrichmentConfig.getFieldMap();
+    Map<String, Object> stellarHandler = (Map<String, Object>) fieldMap.getOrDefault("stellar", new HashMap<>());
+    fieldMap.put("stellar", stellarHandler);
+    stellarHandler.putIfAbsent("config", new LinkedHashMap<String, Object>());
+    return stellarHandler;
+  }
+
+  public static EnrichmentConfig getConfig(SensorEnrichmentConfig sensorConfig, Type type) {
+      EnrichmentConfig enrichmentConfig = null;
+      switch(type) {
+        case ENRICHMENT:
+          enrichmentConfig = sensorConfig.getEnrichment();
+          break;
+        case THREAT_INTEL:
+        case THREATINTEL:
+          enrichmentConfig = sensorConfig.getThreatIntel();
+      }
+      return enrichmentConfig;
+  }
+
+  @Stellar(
+           namespace = "ENRICHMENT_STELLAR_TRANSFORM"
+          ,name = "PRINT"
+          ,description = "Retrieve stellar enrichment transformations."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"type - ENRICHMENT or THREAT_INTEL"
+                    }
+          ,returns = "The String representation of the transformations"
+          )
+  public static class GetStellarTransformation implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+      SensorEnrichmentConfig configObj;
+      String[] headers = new String[] { "Group", "Field", "Transformation"};
+      if(config == null || config.isEmpty()) {
+        return FlipTable.of(headers, new String[0][3]);
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      Type type = Type.valueOf((String) args.get(1));
+      EnrichmentConfig enrichmentConfig = getConfig(configObj, type);
+
+      Map<String, Object> stellarHandler = getStellarHandler(enrichmentConfig);
+      Map<String, Object> transforms = (Map<String, Object>) stellarHandler.get("config");
+      List<String[]> objs = new ArrayList<>();
+      for(Map.Entry<String, Object> kv : transforms.entrySet()) {
+        if(kv.getValue() instanceof Map) {
+          Map<String, String> groupMap = (Map<String, String>) kv.getValue();
+          for(Map.Entry<String, String> groupKv : groupMap.entrySet()) {
+            objs.add(new String[]{kv.getKey(), groupKv.getKey(), groupKv.getValue().toString()});
+          }
+        }
+        else {
+          objs.add(new String[]{"(default)", kv.getKey(), kv.getValue().toString()});
+        }
+      }
+      String[][] data = new String[objs.size()][3];
+      for(int i = 0;i < objs.size();++i) {
+        data[i] = objs.get(i);
+      }
+      return FlipTable.of(headers, data);
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "ENRICHMENT_STELLAR_TRANSFORM"
+          ,name = "ADD"
+          ,description = "Add stellar field transformation."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"type - ENRICHMENT or THREAT_INTEL"
+                    ,"stellarTransforms - A Map associating fields to stellar expressions"
+                    ,"group - Group to add to (optional)"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class AddStellarTransformation implements StellarFunction{
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      int i = 0;
+      String config = (String) args.get(i++);
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        throw new IllegalStateException("Invalid config: " + config);
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      Type type = Type.valueOf((String) args.get(i++));
+      EnrichmentConfig enrichmentConfig = getConfig(configObj, type);
+
+      Map<String, Object> stellarHandler = getStellarHandler(enrichmentConfig);
+
+      Map<String, String> transformsToAdd = (Map<String, String>) args.get(i++);
+      String group = null;
+      if(i < args.size() ) {
+        group = (String) args.get(i++);
+      }
+      Map<String, Object> baseTransforms = (Map<String, Object>) stellarHandler.get("config");
+      Map<String, Object> groupMap = baseTransforms;
+      if(group != null) {
+        groupMap = (Map<String, Object>) baseTransforms.getOrDefault(group, new LinkedHashMap<>());
+        baseTransforms.put(group, groupMap);
+      }
+      for(Map.Entry<String, String> kv : transformsToAdd.entrySet()) {
+        groupMap.put(kv.getKey(), kv.getValue());
+      }
+      if(group != null && groupMap.isEmpty()) {
+        baseTransforms.remove(group);
+      }
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "ENRICHMENT_STELLAR_TRANSFORM"
+          ,name = "REMOVE"
+          ,description = "Remove one or more stellar field transformations."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"type - ENRICHMENT or THREAT_INTEL"
+                    ,"stellarTransforms - A list of removals"
+                    ,"group - Group to remove from (optional)"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class RemoveStellarTransformation implements StellarFunction{
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      int i = 0;
+      String config = (String) args.get(i++);
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        throw new IllegalStateException("Invalid config: " + config);
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      Type type = Type.valueOf((String) args.get(i++));
+      EnrichmentConfig enrichmentConfig = getConfig(configObj, type);
+
+      Map<String, Object> stellarHandler = getStellarHandler(enrichmentConfig);
+
+      List<String> removals = (List<String>) args.get(i++);
+      String group = null;
+      if(i < args.size() ) {
+        group = (String) args.get(i++);
+      }
+      Map<String, Object> baseTransforms = (Map<String, Object>) stellarHandler.get("config");
+      Map<String, Object> groupMap = baseTransforms;
+      if(group != null) {
+        groupMap = (Map<String, Object>) baseTransforms.getOrDefault(group, new LinkedHashMap<>());
+        baseTransforms.put(group, groupMap);
+      }
+      for(String remove : removals) {
+        groupMap.remove(remove);
+      }
+      if(group != null && groupMap.isEmpty()) {
+        baseTransforms.remove(group);
+      }
+      if(baseTransforms.isEmpty()) {
+        enrichmentConfig.getFieldMap().remove("stellar");
+      }
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "ENRICHMENT"
+          ,name = "SET_BATCH"
+          ,description = "Set batch size"
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"size - batch size (integer)"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class SetBatchSize implements StellarFunction{
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      int i = 0;
+      String config = (String) args.get(i++);
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        throw new IllegalStateException("Invalid config: " + config);
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      int batchSize = 5;
+      if(args.size() > 1) {
+        batchSize = ConversionUtils.convert(args.get(i++), Integer.class);
+      }
+      configObj.setBatchSize(batchSize);
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "ENRICHMENT"
+          ,name = "SET_INDEX"
+          ,description = "Set the index for the enrichment"
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"sensor - sensor name"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class SetIndex implements StellarFunction{
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      int i = 0;
+      String config = (String) args.get(i++);
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        throw new IllegalStateException("Invalid config: " + config);
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      String sensorName = ConversionUtils.convert(args.get(i++), String.class);
+      if(sensorName == null) {
+        throw new IllegalStateException("Invalid sensor name: " + config);
+      }
+      configObj.setIndex(sensorName);
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java
new file mode 100644
index 0000000..7ceb7af
--- /dev/null
+++ b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ParserConfigFunctions.java
@@ -0,0 +1,211 @@
+/**
+ * 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.metron.management;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.base.Splitter;
+import com.google.common.collect.Iterables;
+import com.jakewharton.fliptables.FlipTable;
+import org.apache.log4j.Logger;
+import org.apache.metron.common.configuration.ConfigurationType;
+import org.apache.metron.common.configuration.FieldTransformer;
+import org.apache.metron.common.configuration.SensorParserConfig;
+import org.apache.metron.common.dsl.*;
+import org.apache.metron.common.field.transformation.FieldTransformation;
+import org.apache.metron.common.field.transformation.FieldTransformations;
+import org.apache.metron.common.stellar.shell.StellarExecutor;
+import org.apache.metron.common.utils.JSONUtils;
+import org.jboss.aesh.console.Console;
+
+import java.util.*;
+
+import static org.apache.metron.common.configuration.ConfigurationType.PARSER;
+
+public class ParserConfigFunctions {
+  private static final Logger LOG = Logger.getLogger(ConfigurationFunctions.class);
+
+  private static void pruneEmptyStellarTransformers(SensorParserConfig config) {
+    List<FieldTransformer> toRemove = new ArrayList<>();
+    List<FieldTransformer> fieldTransformations = config.getFieldTransformations();
+    for(FieldTransformer transformer : fieldTransformations) {
+      if(transformer.getFieldTransformation().getClass().getName()
+              .equals(FieldTransformations.STELLAR.getMappingClass().getName())
+        && transformer.getConfig().isEmpty()
+        ) {
+          toRemove.add(transformer);
+      }
+    }
+    for(FieldTransformer t : toRemove) {
+      fieldTransformations.remove(t);
+    }
+  }
+  private static FieldTransformer getStellarTransformer(SensorParserConfig config) {
+    List<FieldTransformer> fieldTransformations = config.getFieldTransformations();
+    FieldTransformer stellarTransformer = null;
+    for(FieldTransformer transformer : fieldTransformations) {
+      if(transformer.getFieldTransformation().getClass().getName()
+              .equals(FieldTransformations.STELLAR.getMappingClass().getName())) {
+        stellarTransformer = transformer;
+      }
+    }
+    if(stellarTransformer == null) {
+      stellarTransformer = new FieldTransformer();
+      stellarTransformer.setConfig(new LinkedHashMap<>());
+      stellarTransformer.setTransformation(FieldTransformations.STELLAR.toString());
+      fieldTransformations.add(stellarTransformer);
+    }
+    return stellarTransformer;
+  }
+
+  @Stellar(
+           namespace = "PARSER_STELLAR_TRANSFORM"
+          ,name = "PRINT"
+          ,description = "Retrieve stellar field transformations."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    }
+          ,returns = "The String representation of the transformations"
+          )
+  public static class PrintStellarTransformation implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+      if(config == null) {
+        return null;
+      }
+      SensorParserConfig configObj = (SensorParserConfig) PARSER.deserialize(config);
+      FieldTransformer stellarTransformer = getStellarTransformer(configObj);
+      String[] headers = new String[] { "Field", "Transformation"};
+      String[][] data = new String[stellarTransformer.getConfig().size()][2];
+      int i = 0;
+      for(Map.Entry<String, Object> kv : stellarTransformer.getConfig().entrySet()) {
+        data[i++] = new String[] {kv.getKey(), kv.getValue().toString()};
+      }
+      return FlipTable.of(headers, data);
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "PARSER_STELLAR_TRANSFORM"
+          ,name = "REMOVE"
+          ,description = "Remove stellar field transformation."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"stellarTransforms - A list of stellar transforms to remove"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class RemoveStellarTransformation implements StellarFunction{
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+      if(config == null) {
+        return null;
+      }
+      SensorParserConfig configObj = (SensorParserConfig) PARSER.deserialize(config);
+      FieldTransformer stellarTransformer = getStellarTransformer(configObj);
+      List<String> removals = (List<String>)args.get(1);
+      if(removals == null || removals.isEmpty()) {
+        return config;
+      }
+      for(String removal : removals) {
+        stellarTransformer.getConfig().remove(removal);
+      }
+      List<String> output = new ArrayList<>();
+      output.addAll(stellarTransformer.getConfig().keySet());
+      stellarTransformer.setOutput(output);
+      pruneEmptyStellarTransformers(configObj);
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "PARSER_STELLAR_TRANSFORM"
+          ,name = "ADD"
+          ,description = "Add stellar field transformation."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"stellarTransforms - A Map associating fields to stellar expressions"
+                    }
+          ,returns = "The String representation of the config in zookeeper"
+          )
+  public static class AddStellarTransformation implements StellarFunction{
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+      if(config == null) {
+        return null;
+      }
+      SensorParserConfig configObj = (SensorParserConfig) PARSER.deserialize(config);
+      FieldTransformer stellarTransformer = getStellarTransformer(configObj);
+      Map<String, String> additionalTransforms = (Map<String, String>) args.get(1);
+      if(additionalTransforms == null || additionalTransforms.isEmpty()) {
+        return config;
+      }
+      for(Map.Entry<String, String> kv : additionalTransforms.entrySet()) {
+        stellarTransformer.getConfig().put(kv.getKey(), kv.getValue());
+
+      }
+      List<String> output = new ArrayList<>();
+      output.addAll(stellarTransformer.getConfig().keySet());
+      stellarTransformer.setOutput(output);
+
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java
new file mode 100644
index 0000000..300805c
--- /dev/null
+++ b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ShellFunctions.java
@@ -0,0 +1,188 @@
+/**
+ * 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.metron.management;
+
+import com.jakewharton.fliptables.FlipTable;
+import org.apache.commons.lang3.text.WordUtils;
+import org.apache.metron.common.dsl.*;
+import org.apache.metron.common.stellar.shell.StellarExecutor;
+import org.apache.metron.common.utils.ConversionUtils;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ShellFunctions {
+
+  @Stellar(
+           namespace = "SHELL"
+          ,name = "MAP2TABLE"
+          ,description = "Take a map and return a table"
+          ,params = {"map - Map"
+                    }
+          ,returns = "The map in table form"
+          )
+  public static class Map2Table extends BaseStellarFunction {
+
+    @Override
+    public Object apply(List<Object> args) {
+      if(args.size() < 1) {
+        return null;
+      }
+      Map<Object, Object> map = (Map<Object, Object>) args.get(0);
+      if(map == null) {
+        map = new HashMap<>();
+      }
+      String[] headers = {"KEY", "VALUE"};
+      String[][] data = new String[map.size()][2];
+      int i = 0;
+      for(Map.Entry<Object, Object> kv : map.entrySet()) {
+        data[i++] = new String[] {kv.getKey().toString(), kv.getValue().toString()};
+      }
+      return FlipTable.of(headers, data);
+    }
+  }
+
+  @Stellar(
+           namespace = "SHELL"
+          ,name = "LIST_VARS"
+          ,description = "Return the variables in a tabular form"
+          ,params = {
+             "wrap : Length of string to wrap the columns"
+                    }
+          ,returns = "A tabular representation of the variables."
+          )
+  public static class ListVars implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+
+      Map<String, StellarExecutor.VariableResult> variables = (Map<String, StellarExecutor.VariableResult>) context.getCapability(StellarExecutor.SHELL_VARIABLES).get();
+      String[] headers = {"VARIABLE", "VALUE", "EXPRESSION"};
+      String[][] data = new String[variables.size()][3];
+      int wordWrap = -1;
+      if(args.size() > 0) {
+        wordWrap = ConversionUtils.convert(args.get(0), Integer.class);
+      }
+      int i = 0;
+      for(Map.Entry<String, StellarExecutor.VariableResult> kv : variables.entrySet()) {
+        StellarExecutor.VariableResult result = kv.getValue();
+        data[i++] = new String[] { toWrappedString(kv.getKey().toString(), wordWrap)
+                                 , toWrappedString(result.getResult(), wordWrap)
+                                 , toWrappedString(result.getExpression(), wordWrap)
+                                 };
+      }
+      return FlipTable.of(headers, data);
+    }
+
+    private static String toWrappedString(Object o, int wrap) {
+      String s = "" + o;
+      if(wrap <= 0) {
+        return s;
+      }
+      return WordUtils.wrap(s, wrap);
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "SHELL"
+          ,name = "VARS2MAP"
+          ,description = "Take a set of variables and return a map"
+          ,params = {"variables* - variable names to use to create map "
+                    }
+          ,returns = "A map associating the variable name with the stellar expression."
+          )
+  public static class Var2Map implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      Map<String, StellarExecutor.VariableResult> variables = (Map<String, StellarExecutor.VariableResult>) context.getCapability(StellarExecutor.SHELL_VARIABLES).get();
+      LinkedHashMap<String, String> ret = new LinkedHashMap<>();
+      for(Object arg : args) {
+        if(arg == null) {
+          continue;
+        }
+        String variable = (String)arg;
+        StellarExecutor.VariableResult result = variables.get(variable);
+        if(result != null && result.getExpression() != null) {
+          ret.put(variable, result.getExpression());
+        }
+      }
+      return ret;
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "SHELL"
+          ,name = "GET_EXPRESSION"
+          ,description = "Get a stellar expression from a variable"
+          ,params = {"variable - variable name"
+                    }
+          ,returns = "The stellar expression associated with the variable."
+          )
+  public static class GetExpression implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      Map<String, StellarExecutor.VariableResult> variables = (Map<String, StellarExecutor.VariableResult>) context.getCapability(StellarExecutor.SHELL_VARIABLES).get();
+      if(args.size() == 0) {
+        return null;
+      }
+      String variable = (String) args.get(0);
+      if(variable == null) {
+        return null;
+      }
+      StellarExecutor.VariableResult result = variables.get(variable);
+      if(result != null && result.getExpression() != null) {
+        return result.getExpression();
+      }
+      return null;
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/main/java/org/apache/metron/management/ThreatTriageFunctions.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/main/java/org/apache/metron/management/ThreatTriageFunctions.java b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ThreatTriageFunctions.java
new file mode 100644
index 0000000..966f281
--- /dev/null
+++ b/metron-platform/metron-management/src/main/java/org/apache/metron/management/ThreatTriageFunctions.java
@@ -0,0 +1,293 @@
+/**
+ * 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.metron.management;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.jakewharton.fliptables.FlipTable;
+import org.apache.log4j.Logger;
+import org.apache.metron.common.configuration.enrichment.SensorEnrichmentConfig;
+import org.apache.metron.common.configuration.enrichment.threatintel.ThreatIntelConfig;
+import org.apache.metron.common.configuration.enrichment.threatintel.ThreatTriageConfig;
+import org.apache.metron.common.dsl.Context;
+import org.apache.metron.common.dsl.ParseException;
+import org.apache.metron.common.dsl.Stellar;
+import org.apache.metron.common.dsl.StellarFunction;
+import org.apache.metron.common.utils.ConversionUtils;
+import org.apache.metron.common.utils.JSONUtils;
+
+import java.io.IOException;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.apache.metron.common.configuration.ConfigurationType.ENRICHMENT;
+import static org.apache.metron.management.EnrichmentConfigFunctions.getConfig;
+
+public class ThreatTriageFunctions {
+  private static final Logger LOG = Logger.getLogger(ConfigurationFunctions.class);
+
+  @Stellar(
+           namespace = "THREAT_TRIAGE"
+          ,name = "PRINT"
+          ,description = "Retrieve stellar enrichment transformations."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    }
+          ,returns = "The String representation of the threat triage rules"
+          )
+  public static class GetStellarTransformation implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        configObj = new SensorEnrichmentConfig();
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      ThreatIntelConfig tiConfig = (ThreatIntelConfig) getConfig(configObj, EnrichmentConfigFunctions.Type.THREAT_INTEL);
+      if(tiConfig == null) {
+        return "";
+      }
+      ThreatTriageConfig triageConfig = tiConfig.getTriageConfig();
+      if(triageConfig == null) {
+        return "";
+      }
+      Map<String, Number> triageRules = triageConfig.getRiskLevelRules();
+      if(triageRules == null) {
+        triageRules = new LinkedHashMap<>();
+      }
+      String[] headers = new String[] {"Triage Rule", "Score"};
+      String[][] data = new String[triageRules.size()][2];
+      int i = 0;
+      for(Map.Entry<String, Number> kv : triageRules.entrySet()) {
+        double d = kv.getValue().doubleValue();
+        String val = d == (long)d ? String.format("%d", (long)d) : String.format("%s", d);
+        data[i++]  = new String[] {kv.getKey(), val};
+      }
+      String ret = FlipTable.of(headers, data);
+      if(!triageRules.isEmpty()) {
+        ret += "\n\n";
+
+        ret += "Aggregation: " + triageConfig.getAggregator().name();
+      }
+      return ret;
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "THREAT_TRIAGE"
+          ,name = "ADD"
+          ,description = "Add a threat triage rule."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"stellarTransforms - A Map associating stellar rules to scores"
+                    }
+          ,returns = "The String representation of the threat triage rules"
+          )
+  public static class AddStellarTransformation implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        configObj = new SensorEnrichmentConfig();
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      ThreatIntelConfig tiConfig = (ThreatIntelConfig) getConfig(configObj, EnrichmentConfigFunctions.Type.THREAT_INTEL);
+      if(tiConfig == null) {
+        tiConfig = new ThreatIntelConfig();
+        configObj.setThreatIntel(tiConfig);
+      }
+      ThreatTriageConfig triageConfig = tiConfig.getTriageConfig();
+      if(triageConfig == null) {
+        triageConfig = new ThreatTriageConfig();
+        tiConfig.setTriageConfig(triageConfig);
+      }
+      Map<String, Number> triageRules = triageConfig.getRiskLevelRules();
+      if(triageRules == null) {
+        triageRules = new LinkedHashMap<>();
+        triageConfig.setRiskLevelRules(triageRules);
+      }
+      Map<String, Object> newRules = (Map<String, Object>) args.get(1);
+      for(Map.Entry<String, Object> kv : newRules.entrySet()) {
+        if(kv.getKey() == null || kv.getKey().equals("null")) {
+          continue;
+        }
+        Double ret = ConversionUtils.convert(kv.getValue(), Double.class);
+        triageConfig.getRiskLevelRules().put(kv.getKey(), ret);
+      }
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "THREAT_TRIAGE"
+          ,name = "REMOVE"
+          ,description = "Remove stellar threat triage rule(s)."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"stellarTransforms - A list of stellar rules to remove"
+                    }
+          ,returns = "The String representation of the enrichment config"
+          )
+  public static class RemoveStellarTransformation implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        configObj = new SensorEnrichmentConfig();
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+      ThreatIntelConfig tiConfig = (ThreatIntelConfig) getConfig(configObj, EnrichmentConfigFunctions.Type.THREAT_INTEL);
+      if(tiConfig == null) {
+        tiConfig = new ThreatIntelConfig();
+        configObj.setThreatIntel(tiConfig);
+      }
+      ThreatTriageConfig triageConfig = tiConfig.getTriageConfig();
+      if(triageConfig == null) {
+        triageConfig = new ThreatTriageConfig();
+        tiConfig.setTriageConfig(triageConfig);
+      }
+      Map<String, Number> triageRules = triageConfig.getRiskLevelRules();
+      if(triageRules == null) {
+        triageRules = new LinkedHashMap<>();
+        triageConfig.setRiskLevelRules(triageRules);
+      }
+      List<String> rulesToRemove = (List<String>) args.get(1);
+      for(String rule : rulesToRemove) {
+        triageConfig.getRiskLevelRules().remove(rule);
+      }
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+
+  @Stellar(
+           namespace = "THREAT_TRIAGE"
+          ,name = "SET_AGGREGATOR"
+          ,description = "Set the threat triage aggregator."
+          ,params = {"sensorConfig - Sensor config to add transformation to."
+                    ,"aggregator - Aggregator to use.  One of MIN, MAX, MEAN, SUM, POSITIVE_MEAN"
+                    ,"aggregatorConfig - Optional config for aggregator"
+                    }
+          ,returns = "The String representation of the enrichment config"
+          )
+  public static class SetAggregator implements StellarFunction {
+
+    @Override
+    public Object apply(List<Object> args, Context context) throws ParseException {
+      String config = (String) args.get(0);
+
+      SensorEnrichmentConfig configObj;
+      if(config == null || config.isEmpty()) {
+        configObj = new SensorEnrichmentConfig();
+      }
+      else {
+        configObj = (SensorEnrichmentConfig) ENRICHMENT.deserialize(config);
+      }
+
+      ThreatIntelConfig tiConfig = (ThreatIntelConfig) getConfig(configObj, EnrichmentConfigFunctions.Type.THREAT_INTEL);
+      if(tiConfig == null) {
+        tiConfig = new ThreatIntelConfig();
+        configObj.setThreatIntel(tiConfig);
+      }
+      ThreatTriageConfig triageConfig = tiConfig.getTriageConfig();
+      if(triageConfig == null) {
+        triageConfig = new ThreatTriageConfig();
+        tiConfig.setTriageConfig(triageConfig);
+      }
+      Map<String, Number> triageRules = triageConfig.getRiskLevelRules();
+      if(triageRules == null) {
+        triageRules = new LinkedHashMap<>();
+        triageConfig.setRiskLevelRules(triageRules);
+      }
+      String aggregator = (String) args.get(1);
+      triageConfig.setAggregator(aggregator);
+      if(args.size() > 2) {
+        Map<String, Object> aggConfig = (Map<String, Object>) args.get(2);
+        triageConfig.setAggregationConfig(aggConfig);
+      }
+      try {
+        return JSONUtils.INSTANCE.toJSON(configObj, true);
+      } catch (JsonProcessingException e) {
+        LOG.error("Unable to convert object to JSON: " + configObj, e);
+        return config;
+      }
+
+    }
+
+    @Override
+    public void initialize(Context context) {
+
+    }
+
+    @Override
+    public boolean isInitialized() {
+      return true;
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/test/java/org/apache/metron/management/ConfigurationFunctionsTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/test/java/org/apache/metron/management/ConfigurationFunctionsTest.java b/metron-platform/metron-management/src/test/java/org/apache/metron/management/ConfigurationFunctionsTest.java
new file mode 100644
index 0000000..0b7ea7d
--- /dev/null
+++ b/metron-platform/metron-management/src/test/java/org/apache/metron/management/ConfigurationFunctionsTest.java
@@ -0,0 +1,240 @@
+/**
+ * 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.metron.management;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableMap;
+import com.google.common.io.Files;
+import org.adrianwalker.multilinestring.Multiline;
+import org.apache.commons.cli.PosixParser;
+import org.apache.curator.framework.CuratorFramework;
+import org.apache.curator.test.TestingServer;
+import org.apache.metron.TestConstants;
+import org.apache.metron.common.cli.ConfigurationManager;
+import org.apache.metron.common.configuration.ConfigurationsUtils;
+import org.apache.metron.common.dsl.Context;
+import org.apache.metron.common.dsl.ParseException;
+import org.apache.metron.common.stellar.StellarTest;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.HashMap;
+
+import static org.apache.metron.TestConstants.PARSER_CONFIGS_PATH;
+import static org.apache.metron.TestConstants.SAMPLE_CONFIG_PATH;
+import static org.apache.metron.management.utils.FileUtils.slurp;
+
+public class ConfigurationFunctionsTest {
+  private TestingServer testZkServer;
+  private CuratorFramework client;
+  private String zookeeperUrl;
+  private Context context = new Context.Builder()
+            .with(Context.Capabilities.ZOOKEEPER_CLIENT, () -> client)
+            .build();
+  @Before
+  public void setup() throws Exception {
+    testZkServer = new TestingServer(true);
+    zookeeperUrl = testZkServer.getConnectString();
+    client = ConfigurationsUtils.getClient(zookeeperUrl);
+    client.start();
+
+    pushConfigs(SAMPLE_CONFIG_PATH);
+    pushConfigs(PARSER_CONFIGS_PATH);
+
+
+  }
+
+  private void pushConfigs(String inputPath) throws Exception {
+    String[] args = new String[]{
+            "-z", zookeeperUrl
+            , "--mode", "PUSH"
+            , "--input_dir", inputPath
+    };
+    ConfigurationManager manager = new ConfigurationManager();
+    manager.run(ConfigurationManager.ConfigurationOptions.parse(new PosixParser(), args));
+  }
+
+
+  static String goodBroParserConfig = slurp(PARSER_CONFIGS_PATH + "/parsers/bro.json");
+
+  /**
+{
+  "sensorTopic" : "brop",
+  "parserConfig" : { },
+  "fieldTransformations" : [ ]
+}*/
+  @Multiline
+  static String defaultBropParserConfig;
+
+
+  @Test
+  public void testParserGetHappyPath() {
+
+    Object out = StellarTest.run("CONFIG_GET('PARSER', 'bro')", new HashMap<>(), context);
+    Assert.assertEquals(goodBroParserConfig, out);
+  }
+
+  @Test
+  public void testParserGetMissWithoutDefault() {
+
+    {
+      Object out = StellarTest.run("CONFIG_GET('PARSER', 'brop', false)", new HashMap<>(), context);
+      Assert.assertNull(out);
+    }
+  }
+
+  @Test
+  public void testParserGetMissWithDefault() {
+
+    {
+      Object out = StellarTest.run("CONFIG_GET('PARSER', 'brop')", new HashMap<>(), context);
+      Assert.assertEquals(defaultBropParserConfig, out);
+    }
+    {
+      Object out = StellarTest.run("CONFIG_GET('PARSER', 'brop', true)", new HashMap<>(), context);
+      Assert.assertEquals(defaultBropParserConfig, out);
+    }
+  }
+
+  static String goodTestEnrichmentConfig = slurp( SAMPLE_CONFIG_PATH + "/enrichments/test.json");
+
+  /**
+{
+  "index" : "brop",
+  "batchSize" : 0,
+  "enrichment" : {
+    "fieldMap" : { },
+    "fieldToTypeMap" : { },
+    "config" : { }
+  },
+  "threatIntel" : {
+    "fieldMap" : { },
+    "fieldToTypeMap" : { },
+    "config" : { },
+    "triageConfig" : {
+      "riskLevelRules" : { },
+      "aggregator" : "MAX",
+      "aggregationConfig" : { }
+    }
+  },
+  "configuration" : { }
+}*/
+  @Multiline
+  static String defaultBropEnrichmentConfig;
+
+
+  @Test
+  public void testEnrichmentGetHappyPath() {
+
+    Object out = StellarTest.run("CONFIG_GET('ENRICHMENT', 'test')", new HashMap<>(), context);
+    Assert.assertEquals(goodTestEnrichmentConfig, out.toString().trim());
+  }
+
+  @Test
+  public void testEnrichmentGetMissWithoutDefault() {
+
+    {
+      Object out = StellarTest.run("CONFIG_GET('ENRICHMENT', 'brop', false)", new HashMap<>(), context);
+      Assert.assertNull(out);
+    }
+  }
+
+  @Test
+  public void testEnrichmentGetMissWithDefault() {
+
+    {
+      Object out = StellarTest.run("CONFIG_GET('ENRICHMENT', 'brop')", new HashMap<>(), context);
+      Assert.assertEquals(defaultBropEnrichmentConfig, out.toString().trim());
+    }
+    {
+      Object out = StellarTest.run("CONFIG_GET('ENRICHMENT', 'brop', true)", new HashMap<>(), context);
+      Assert.assertEquals(defaultBropEnrichmentConfig, out.toString().trim());
+    }
+  }
+
+  static String goodGlobalConfig = slurp( SAMPLE_CONFIG_PATH+ "/global.json");
+
+  @Test
+  public void testGlobalGet() {
+
+    Object out = StellarTest.run("CONFIG_GET('GLOBAL')", new HashMap<>(), context);
+    Assert.assertEquals(goodGlobalConfig, out.toString().trim());
+  }
+
+  @Test
+  public void testGlobalPut() {
+
+    Object out = StellarTest.run("CONFIG_GET('GLOBAL')", new HashMap<>(), context);
+    Assert.assertEquals(goodGlobalConfig, out.toString().trim());
+  }
+
+  @Test(expected=ParseException.class)
+  public void testGlobalPutBad() {
+    StellarTest.run("CONFIG_PUT('GLOBAL', 'foo bar')", new HashMap<>(), context);
+  }
+
+  @Test
+  public void testEnrichmentPut() throws InterruptedException {
+    String brop= (String) StellarTest.run("CONFIG_GET('ENRICHMENT', 'testEnrichmentPut')", new HashMap<>(), context);
+    StellarTest.run("CONFIG_PUT('ENRICHMENT', config, 'testEnrichmentPut')", ImmutableMap.of("config", brop), context);
+    boolean foundMatch = false;
+    for(int i = 0;i < 10 && !foundMatch;++i) {
+      String bropNew = (String) StellarTest.run("CONFIG_GET('ENRICHMENT', 'testEnrichmentPut', false)", new HashMap<>(), context);
+      foundMatch =  brop.equals(bropNew);
+      if(foundMatch) {
+        break;
+      }
+      Thread.sleep(2000);
+    }
+    Assert.assertTrue(foundMatch);
+  }
+
+  @Test(expected= ParseException.class)
+  public void testEnrichmentPutBad() throws InterruptedException {
+    {
+      StellarTest.run("CONFIG_PUT('ENRICHMENT', config, 'brop')", ImmutableMap.of("config", "foo bar"), context);
+    }
+  }
+
+  @Test
+  public void testParserPut() throws InterruptedException {
+    String brop= (String) StellarTest.run("CONFIG_GET('PARSER', 'testParserPut')", new HashMap<>(), context);
+    StellarTest.run("CONFIG_PUT('PARSER', config, 'testParserPut')", ImmutableMap.of("config", brop), context);
+    boolean foundMatch = false;
+    for(int i = 0;i < 10 && !foundMatch;++i) {
+      String bropNew = (String) StellarTest.run("CONFIG_GET('PARSER', 'testParserPut', false)", new HashMap<>(), context);
+      foundMatch =  brop.equals(bropNew);
+      if(foundMatch) {
+        break;
+      }
+      Thread.sleep(2000);
+    }
+    Assert.assertTrue(foundMatch);
+  }
+
+  @Test(expected= ParseException.class)
+  public void testParserPutBad() throws InterruptedException {
+    {
+      StellarTest.run("CONFIG_PUT('PARSER', config, 'brop')", ImmutableMap.of("config", "foo bar"), context);
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/test/java/org/apache/metron/management/EnrichmentConfigFunctionsTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/test/java/org/apache/metron/management/EnrichmentConfigFunctionsTest.java b/metron-platform/metron-management/src/test/java/org/apache/metron/management/EnrichmentConfigFunctionsTest.java
new file mode 100644
index 0000000..850991a
--- /dev/null
+++ b/metron-platform/metron-management/src/test/java/org/apache/metron/management/EnrichmentConfigFunctionsTest.java
@@ -0,0 +1,385 @@
+/**
+ * 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.metron.management;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.google.common.collect.ImmutableMap;
+import org.adrianwalker.multilinestring.Multiline;
+import org.apache.metron.common.configuration.enrichment.EnrichmentConfig;
+import org.apache.metron.common.configuration.enrichment.SensorEnrichmentConfig;
+import org.apache.metron.common.dsl.Context;
+import org.apache.metron.common.dsl.ParseException;
+import org.apache.metron.common.dsl.StellarFunctions;
+import org.apache.metron.common.stellar.StellarProcessor;
+import org.apache.metron.common.stellar.StellarTest;
+import org.apache.metron.common.stellar.shell.StellarExecutor;
+import org.apache.metron.common.utils.JSONUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.metron.common.configuration.ConfigurationType.ENRICHMENT;
+
+@RunWith(Parameterized.class)
+public class EnrichmentConfigFunctionsTest {
+
+  String configStr = emptyTransformationsConfig();
+  Map<String, StellarExecutor.VariableResult> variables;
+  Context context = null;
+  String enrichmentType = null;
+  String group = null;
+  public EnrichmentConfigFunctionsTest(String enrichmentType, String group) {
+    this.enrichmentType = enrichmentType;
+    this.group = group;
+  }
+
+  public static String emptyTransformationsConfig() {
+    SensorEnrichmentConfig config = new SensorEnrichmentConfig();
+    config.setIndex("dummy");
+    config.setBatchSize(5);
+    try {
+      return JSONUtils.INSTANCE.toJSON(config, true);
+    } catch (JsonProcessingException e) {
+      throw new IllegalStateException(e);
+    }
+  }
+
+  @Parameterized.Parameters
+  public static Collection<Object[]> types() {
+    // each test will be run against these values for windowSize
+    return Arrays.asList(new Object[][]{
+      {"ENRICHMENT", "group"}
+    , {"ENRICHMENT", null}
+    , {"THREAT_INTEL", "group"}
+    , {"THREAT_INTEL", null}
+    });
+  }
+
+
+
+  @Before
+  public void setup() {
+    variables = ImmutableMap.of(
+            "upper", new StellarExecutor.VariableResult("TO_UPPER('foo')", "FOO"),
+            "lower", new StellarExecutor.VariableResult("TO_LOWER('FOO')", "foo")
+    );
+
+    context = new Context.Builder()
+            .with(StellarExecutor.SHELL_VARIABLES, () -> variables)
+            .build();
+  }
+
+  static Map<String, Object> toMap(String... k) {
+    Map<String, Object> ret = new HashMap<>();
+    for(int i = 0;i < k.length;i+=2) {
+      ret.put(k[i], k[i+1]);
+    }
+    return ret;
+  }
+  private int size(Map<String, Object> stellarFunctions) {
+    if(group == null) {
+      return stellarFunctions.size();
+    }
+    else {
+      return ((Map<String, Object>)stellarFunctions.getOrDefault(group, new HashMap<>())).size();
+    }
+  }
+  private Object get(Map<String, Object> stellarFunctions, String key) {
+    if(group == null) {
+      return stellarFunctions.get(key);
+    }
+    else {
+      return ((Map<String, Object>)stellarFunctions.get(group)).get(key);
+    }
+  }
+
+  private EnrichmentConfig getEnrichmentConfig(String configStr) {
+    SensorEnrichmentConfig sensorConfig = (SensorEnrichmentConfig) ENRICHMENT.deserialize(configStr);
+    switch (enrichmentType) {
+      case "ENRICHMENT":
+        return sensorConfig.getEnrichment();
+      case "THREAT_INTEL":
+        return sensorConfig.getThreatIntel();
+    }
+    return null;
+  }
+
+  private static Map<String, Object> getStellarMappings(EnrichmentConfig config) {
+    Map<String, Object> fieldMap = config.getFieldMap();
+    if (fieldMap == null) {
+      return new HashMap<>();
+    }
+    Map<String, Object> stellarMap = (Map<String, Object>) fieldMap.get("stellar");
+    if (stellarMap == null) {
+      return new HashMap<>();
+    }
+    return (Map<String, Object>) stellarMap.get("config");
+  }
+
+  private Object run(String rule, Map<String, Object> variables) {
+    StellarProcessor processor = new StellarProcessor();
+    return processor.parse(rule, x -> variables.get(x), StellarFunctions.FUNCTION_RESOLVER(), context);
+  }
+
+  @Test
+  public void testAddEmpty() {
+
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('upper'), group)"
+            , toMap("config", configStr
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    Map<String, Object> stellarFunctions = getStellarMappings(getEnrichmentConfig(newConfig));
+    Assert.assertEquals(1, size(stellarFunctions));
+    Assert.assertEquals(variables.get("upper").getExpression(), get(stellarFunctions,"upper"));
+  }
+
+
+
+  @Test
+  public void testAddHasExisting() {
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('upper'), group)"
+            ,toMap( "config", configStr
+                  , "type", enrichmentType
+                  , "group", group
+                  )
+
+            );
+    newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('lower'), group)"
+            , toMap("config",newConfig
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    Map<String, Object> stellarFunctions = getStellarMappings(getEnrichmentConfig(newConfig));
+    Assert.assertEquals(2, size(stellarFunctions));
+    Assert.assertEquals(variables.get("upper").getExpression(), get(stellarFunctions,"upper"));
+    Assert.assertEquals(variables.get("lower").getExpression(), get(stellarFunctions,"lower"));
+  }
+
+  @Test
+  public void testAddMalformed() {
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('foo'), group)"
+            , toMap("config", configStr
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    Map<String, Object> stellarFunctions = getStellarMappings(getEnrichmentConfig(newConfig));
+    Assert.assertEquals(0, size(stellarFunctions));
+  }
+
+  @Test
+  public void testAddDuplicate() {
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('upper'), group)"
+            , toMap("config", configStr
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('upper'), group)"
+            , toMap("config",newConfig
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    Map<String, Object> stellarFunctions = getStellarMappings(getEnrichmentConfig(newConfig));
+    Assert.assertEquals(1, size(stellarFunctions));
+    Assert.assertEquals(variables.get("upper").getExpression(), get(stellarFunctions,"upper"));
+  }
+
+  @Test
+  public void testRemove() {
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('upper', 'lower'), group)"
+            , toMap("config", configStr
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_REMOVE(config, type, ['upper'], group)"
+            , toMap("config",newConfig
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    Map<String, Object> stellarFunctions = getStellarMappings(getEnrichmentConfig(newConfig));
+    Assert.assertEquals(1, size(stellarFunctions));
+    Assert.assertEquals(variables.get("lower").getExpression(), get(stellarFunctions,"lower"));
+  }
+
+  @Test
+  public void testRemoveMultiple() {
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('upper', 'lower'), group)"
+            , toMap("config", configStr
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_REMOVE(config, type, ['upper', 'lower'], group)"
+            , toMap("config",newConfig
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    Map<String, Object> stellarFunctions = getStellarMappings(getEnrichmentConfig(newConfig));
+    Assert.assertEquals(0, size(stellarFunctions));
+  }
+
+  @Test
+  public void testRemoveMissing() {
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('lower'), group)"
+            , toMap("config", configStr
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_REMOVE(config, type, ['upper'], group)"
+            , toMap("config",newConfig
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    Map<String, Object> stellarFunctions = getStellarMappings(getEnrichmentConfig(newConfig));
+    Assert.assertEquals(1, size(stellarFunctions));
+    Assert.assertEquals(variables.get("lower").getExpression(), get(stellarFunctions,"lower"));
+  }
+
+  /**
+\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
+\u2551 Group \u2502 Field \u2502 Transformation  \u2551
+\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
+\u2551 group \u2502 upper \u2502 TO_UPPER('foo') \u2551
+\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d
+   */
+  @Multiline
+  static String testPrintExpectedWithGroup;
+  /**
+\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
+\u2551 Group     \u2502 Field \u2502 Transformation  \u2551
+\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
+\u2551 (default) \u2502 upper \u2502 TO_UPPER('foo') \u2551
+\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d
+   */
+  @Multiline
+  static String testPrintExpectedWithoutGroup;
+  @Test
+  public void testPrint() {
+    String newConfig = (String) run(
+            "ENRICHMENT_STELLAR_TRANSFORM_ADD(config, type, SHELL_VARS2MAP('upper'), group)"
+            , toMap("config", configStr
+                    , "type", enrichmentType
+                    , "group", group
+            )
+    );
+    String out = (String) run("ENRICHMENT_STELLAR_TRANSFORM_PRINT(config, type)"
+            , toMap("config", newConfig
+                   ,"type", enrichmentType
+                  )
+    );
+    if(group == null) {
+      Assert.assertEquals(testPrintExpectedWithoutGroup, out);
+    }
+    else {
+      Assert.assertEquals(testPrintExpectedWithGroup, out);
+    }
+  }
+
+  /**
+\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
+\u2551 Group \u2502 Field \u2502 Transformation \u2551
+\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
+\u2551 (empty)                        \u2551
+\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d
+   */
+  @Multiline
+  static String testPrintEmptyExpected;
+
+  @Test
+  public void testPrintEmpty() {
+    String out = (String) run("ENRICHMENT_STELLAR_TRANSFORM_PRINT(config, type)"
+            , toMap("config", configStr
+                   ,"type", enrichmentType
+                  )
+    );
+    Assert.assertEquals(testPrintEmptyExpected, out);
+  }
+
+  @Test
+  public void testPrintNull() {
+
+    String out = (String) run("ENRICHMENT_STELLAR_TRANSFORM_PRINT(config, type)"
+            , toMap("config", configStr ,"type", enrichmentType)
+    );
+    Assert.assertEquals(testPrintEmptyExpected, out);
+  }
+
+  @Test
+  public void testSetBatch() {
+    String out = (String) run("ENRICHMENT_SET_BATCH(config, 10)"
+                             , toMap("config", configStr)
+    );
+    SensorEnrichmentConfig config = (SensorEnrichmentConfig)ENRICHMENT.deserialize(out);
+    Assert.assertEquals(config.getBatchSize(), 10);
+  }
+
+  @Test(expected=ParseException.class)
+  public void testSetBatchBad() {
+    String out = (String) run("ENRICHMENT_SET_BATCH(config, 10)"
+                             , new HashMap<>()
+    );
+    SensorEnrichmentConfig config = (SensorEnrichmentConfig)ENRICHMENT.deserialize(out);
+    Assert.assertEquals(config.getBatchSize(), 10);
+  }
+
+  @Test
+  public void testSetIndex() {
+    String out = (String) run("ENRICHMENT_SET_INDEX(config, 'foo')"
+            , toMap("config", configStr)
+    );
+    SensorEnrichmentConfig config = (SensorEnrichmentConfig)ENRICHMENT.deserialize(out);
+    Assert.assertEquals("foo", config.getIndex());
+  }
+
+  @Test(expected= ParseException.class)
+  public void testSetIndexBad() {
+    String out = (String) run("ENRICHMENT_SET_INDEX(config, NULL)"
+            , new HashMap<>()
+    );
+    SensorEnrichmentConfig config = (SensorEnrichmentConfig)ENRICHMENT.deserialize(out);
+    Assert.assertNotNull(config.getIndex());
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-metron/blob/c85c7426/metron-platform/metron-management/src/test/java/org/apache/metron/management/ParserConfigFunctionsTest.java
----------------------------------------------------------------------
diff --git a/metron-platform/metron-management/src/test/java/org/apache/metron/management/ParserConfigFunctionsTest.java b/metron-platform/metron-management/src/test/java/org/apache/metron/management/ParserConfigFunctionsTest.java
new file mode 100644
index 0000000..c8e5591
--- /dev/null
+++ b/metron-platform/metron-management/src/test/java/org/apache/metron/management/ParserConfigFunctionsTest.java
@@ -0,0 +1,180 @@
+/**
+ * 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.metron.management;
+
+import com.google.common.collect.ImmutableMap;
+import org.adrianwalker.multilinestring.Multiline;
+import org.apache.metron.common.configuration.FieldTransformer;
+import org.apache.metron.common.configuration.SensorParserConfig;
+import org.apache.metron.common.dsl.Context;
+import org.apache.metron.common.stellar.StellarTest;
+import org.apache.metron.common.stellar.shell.StellarExecutor;
+import org.json.simple.JSONObject;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.apache.metron.TestConstants.PARSER_CONFIGS_PATH;
+import static org.apache.metron.management.utils.FileUtils.slurp;
+import static org.apache.metron.common.configuration.ConfigurationType.PARSER;
+
+public class ParserConfigFunctionsTest {
+
+  String emptyTransformationsConfig = slurp(PARSER_CONFIGS_PATH + "/parsers/bro.json");
+  String existingTransformationsConfig = slurp(PARSER_CONFIGS_PATH + "/parsers/squid.json");
+  Map<String, StellarExecutor.VariableResult> variables ;
+  Context context = null;
+  @Before
+  public void setup() {
+    variables = ImmutableMap.of(
+            "upper" , new StellarExecutor.VariableResult("TO_UPPER('foo')", "FOO"),
+            "lower" , new StellarExecutor.VariableResult("TO_LOWER('FOO')", "foo")
+    );
+
+    context = new Context.Builder()
+            .with(StellarExecutor.SHELL_VARIABLES , () -> variables)
+            .build();
+  }
+
+  public Map<String, Object> transform(String parserConfig){
+    return transform(parserConfig, new HashMap<>());
+  }
+
+  public Map<String, Object> transform(String parserConfig, Map<String, Object> variables){
+    JSONObject ret = new JSONObject(variables);
+    SensorParserConfig sensorParserConfig = (SensorParserConfig) PARSER.deserialize(parserConfig);
+    sensorParserConfig.init();
+    for (FieldTransformer handler : sensorParserConfig.getFieldTransformations()) {
+      if (handler != null) {
+        handler.transformAndUpdate(ret, sensorParserConfig.getParserConfig(), context);
+      }
+    }
+    return ret;
+  }
+
+  @Test
+  public void testAddEmpty() {
+    String newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+    Map<String, Object> transformations = transform(newConfig);
+    Assert.assertEquals(1, transformations.size());
+    Assert.assertEquals("FOO", transformations.get("upper") );
+  }
+
+  @Test
+  public void testAddHasExisting() {
+    String newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))"
+            , ImmutableMap.of("config", existingTransformationsConfig )
+            , context
+    );
+    Map<String, Object> transformations = transform(newConfig, ImmutableMap.of("url", "http://www.google.com"));
+    //squid already has 2 transformations, we just added url, which makes 3
+    Assert.assertEquals(4, transformations.size());
+    Assert.assertEquals("FOO", transformations.get("upper") );
+  }
+
+  @Test
+  public void testAddMalformed() {
+    String newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('blah'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+    Map<String, Object> transformations = transform(newConfig);
+    Assert.assertEquals(0, transformations.size());
+  }
+
+  @Test
+  public void testAddDuplicate() {
+    String newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+    newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))", ImmutableMap.of("config", newConfig), context);
+    Map<String, Object> transformations = transform(newConfig);
+    Assert.assertEquals(1, transformations.size());
+    Assert.assertEquals("FOO", transformations.get("upper") );
+  }
+
+  @Test
+  public void testRemove() {
+    String newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+    newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_REMOVE(config, ['upper'])", ImmutableMap.of("config", newConfig), context);
+    Map<String, Object> transformations = transform(newConfig);
+    Assert.assertEquals(0, transformations.size());
+  }
+
+  @Test
+  public void testRemoveMultiple() {
+    String newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper', 'lower'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+    newConfig = (String)StellarTest.run("PARSER_STELLAR_TRANSFORM_REMOVE(config, ['upper', 'lower'])", ImmutableMap.of("config", newConfig), context);
+    Map<String, Object> transformations = transform(newConfig);
+    Assert.assertEquals(0, transformations.size());
+  }
+
+  @Test
+  public void testRemoveMissing() {
+    {
+      String newConfig = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+      newConfig = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_REMOVE(config, ['lower'])", ImmutableMap.of("config", newConfig), context);
+      Map<String, Object> transformations = transform(newConfig);
+      Assert.assertEquals(1, transformations.size());
+      Assert.assertEquals("FOO", transformations.get("upper"));
+    }
+    {
+      String newConfig = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+      newConfig = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_REMOVE(config, [''])", ImmutableMap.of("config", newConfig), context);
+      Map<String, Object> transformations = transform(newConfig);
+      Assert.assertEquals(1, transformations.size());
+      Assert.assertEquals("FOO", transformations.get("upper"));
+    }
+  }
+
+  /**
+\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
+\u2551 Field \u2502 Transformation  \u2551
+\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u256a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
+\u2551 upper \u2502 TO_UPPER('foo') \u2551
+\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d
+   */
+  @Multiline
+  static String testPrintExpected;
+  @Test
+  public void testPrint() {
+    String newConfig = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_ADD(config, SHELL_VARS2MAP('upper'))", ImmutableMap.of("config", emptyTransformationsConfig), context);
+    String out = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_PRINT(config )", ImmutableMap.of("config", newConfig), context);
+    Assert.assertEquals(testPrintExpected, out);
+  }
+  /**
+\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2564\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
+\u2551 Field \u2502 Transformation \u2551
+\u2560\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2567\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563
+\u2551 (empty)                \u2551
+\u255a\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255d
+   */
+  @Multiline
+  static String testPrintEmptyExpected;
+
+  @Test
+  public void testPrintEmpty() {
+    String out = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_PRINT(config )", ImmutableMap.of("config", emptyTransformationsConfig), context);
+    Assert.assertEquals(testPrintEmptyExpected, out);
+  }
+
+  @Test
+  public void testPrintNull() {
+
+    String out = (String) StellarTest.run("PARSER_STELLAR_TRANSFORM_PRINT(config )", new HashMap<>(), context);
+    Assert.assertNull( out);
+  }
+}