SLIDER-875 better handling and support of nested external components through 
addition of role.prefix


Project: http://git-wip-us.apache.org/repos/asf/incubator-slider/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-slider/commit/a4dc574c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-slider/tree/a4dc574c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-slider/diff/a4dc574c

Branch: refs/heads/develop
Commit: a4dc574c9f60e2f07906b37f8200e9488269c20d
Parents: 000b38d
Author: Billie Rinaldi <billie.rina...@gmail.com>
Authored: Wed Jul 13 09:53:47 2016 -0700
Committer: Billie Rinaldi <billie.rina...@gmail.com>
Committed: Wed Jul 13 09:53:47 2016 -0700

----------------------------------------------------------------------
 .../java/org/apache/slider/api/RoleKeys.java    |   5 +
 .../apache/slider/common/tools/SliderUtils.java |   9 ++
 .../slider/core/build/InstanceBuilder.java      | 108 +++++++++++----
 .../slider/core/conf/ConfTreeOperations.java    |  39 ++++++
 .../providers/agent/AgentClientProvider.java    |   6 +-
 .../slider/providers/agent/AgentKeys.java       |   1 +
 .../providers/agent/AgentProviderService.java   |  42 +++---
 .../slider/providers/agent/AgentUtils.java      |  30 ++--
 .../providers/agent/ComponentCommandOrder.java  | 110 ++++++++++-----
 .../slider/server/appmaster/state/AppState.java |   5 +
 .../appConfig_external_component_nested.json    |  12 ++
 .../test_min_pkg/sleep_cmd/metainfo.json        |   6 +
 .../metainfo_external_component_nested.json     |  14 ++
 .../resources_external_component_nested.json    |  12 ++
 .../TestBuildExternalComponent.groovy           | 138 +++++++++++++++++++
 .../agent/TestAgentProviderService.java         |  35 ++---
 .../agent/TestComponentCommandOrder.java        | 107 +++++++++++++-
 .../apache/slider/funtest/ResourcePaths.groovy  |   4 +
 .../funtest/misc/ExternalComponentIT.groovy     |  38 +++++
 19 files changed, 589 insertions(+), 132 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/api/RoleKeys.java
----------------------------------------------------------------------
diff --git a/slider-core/src/main/java/org/apache/slider/api/RoleKeys.java 
b/slider-core/src/main/java/org/apache/slider/api/RoleKeys.java
index 812a6b3..ce413ff 100644
--- a/slider-core/src/main/java/org/apache/slider/api/RoleKeys.java
+++ b/slider-core/src/main/java/org/apache/slider/api/RoleKeys.java
@@ -35,6 +35,11 @@ public interface RoleKeys {
   String ROLE_GROUP = "role.group";
 
   /**
+   * The prefix of a role: {@value}
+   */
+  String ROLE_PREFIX = "role.prefix";
+
+  /**
    * Status report: number actually granted : {@value} 
    */
   String ROLE_ACTUAL_INSTANCES = "role.actual.instances";

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java 
b/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
index c48e109..b798d4d 100644
--- a/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
+++ b/slider-core/src/main/java/org/apache/slider/common/tools/SliderUtils.java
@@ -123,6 +123,8 @@ import java.util.zip.GZIPOutputStream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
+import static org.apache.slider.common.SliderKeys.COMPONENT_SEPARATOR;
+
 /**
  * These are slider-specific Util methods
  */
@@ -2612,4 +2614,11 @@ public final class SliderUtils {
     }
     return buffer.toString();
   }
+
+  public static String trimPrefix(String prefix) {
+    if (prefix != null && prefix.endsWith(COMPONENT_SEPARATOR)) {
+      return prefix.substring(0, prefix.length()-1);
+    }
+    return prefix;
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/core/build/InstanceBuilder.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/core/build/InstanceBuilder.java 
b/slider-core/src/main/java/org/apache/slider/core/build/InstanceBuilder.java
index 4afdf7c..6a7975e 100644
--- 
a/slider-core/src/main/java/org/apache/slider/core/build/InstanceBuilder.java
+++ 
b/slider-core/src/main/java/org/apache/slider/core/build/InstanceBuilder.java
@@ -69,6 +69,7 @@ import static 
org.apache.slider.api.OptionKeys.INTERNAL_SNAPSHOT_CONF_PATH;
 import static org.apache.slider.api.OptionKeys.ZOOKEEPER_HOSTS;
 import static org.apache.slider.api.OptionKeys.ZOOKEEPER_PATH;
 import static org.apache.slider.api.OptionKeys.ZOOKEEPER_QUORUM;
+import static org.apache.slider.api.RoleKeys.ROLE_PREFIX;
 import static org.apache.slider.common.SliderKeys.COMPONENT_AM;
 import static org.apache.slider.common.SliderKeys.COMPONENT_SEPARATOR;
 import static org.apache.slider.common.SliderKeys.COMPONENT_TYPE;
@@ -85,7 +86,7 @@ public class InstanceBuilder {
   private final CoreFileSystem coreFS;
   private final InstancePaths instancePaths;
   private AggregateConf instanceDescription;
-  private Map<Path, Path> externalAppDefs = new HashMap<>();
+  private Map<String, Path> externalAppDefs = new HashMap<>();
   private TreeSet<Integer> priorities = new TreeSet<>();
 
   private static final Logger log =
@@ -272,8 +273,7 @@ public class InstanceBuilder {
         continue;
       }
       Map<String, String> options = entry.getValue();
-      if (options.containsKey(COMPONENT_TYPE) &&
-          EXTERNAL_COMPONENT.equals(options.get(COMPONENT_TYPE))) {
+      if (EXTERNAL_COMPONENT.equals(options.get(COMPONENT_TYPE))) {
         externalComponents.add(entry.getKey());
       }
     }
@@ -284,17 +284,37 @@ public class InstanceBuilder {
 
   private void mergeExternalComponent(ConfTreeOperations ops,
       ConfTreeOperations externalOps, String externalComponent,
-      Integer priority) {
+      Integer priority) throws BadConfigException {
     for (String subComponent : externalOps.getComponentNames()) {
       if (COMPONENT_AM.equals(subComponent)) {
         continue;
       }
+      String prefix = externalComponent + COMPONENT_SEPARATOR;
       log.debug("Merging options for {} into {}", subComponent,
-          externalComponent + COMPONENT_SEPARATOR + subComponent);
-      MapOperations subComponentOps = ops.getOrAddComponent(externalComponent +
-          COMPONENT_SEPARATOR + subComponent);
+          prefix + subComponent);
+      MapOperations subComponentOps = ops.getOrAddComponent(
+          prefix + subComponent);
+      if (priority == null) {
+        SliderUtils.mergeMaps(subComponentOps,
+            ops.getComponent(externalComponent).options);
+        subComponentOps.remove(COMPONENT_TYPE);
+      }
+
       SliderUtils.mergeMapsIgnorePrefixes(subComponentOps,
           externalOps.getComponent(subComponent), PREFIXES_TO_SKIP);
+
+      // add prefix to existing prefix
+      String existingPrefix = subComponentOps.get(ROLE_PREFIX);
+      if (existingPrefix != null) {
+        if (!subComponent.startsWith(existingPrefix)) {
+          throw new BadConfigException("Bad prefix " + existingPrefix +
+              " for subcomponent " + subComponent + " of " + 
externalComponent);
+        }
+        prefix = prefix + existingPrefix;
+      }
+      subComponentOps.set(ROLE_PREFIX, prefix);
+
+      // adjust priority
       if (priority != null) {
         subComponentOps.put(ResourceKeys.COMPONENT_PRIORITY,
             Integer.toString(priority));
@@ -322,10 +342,6 @@ public class InstanceBuilder {
       if (COMPONENT_AM.equals(entry.getKey())) {
         continue;
       }
-      if (entry.getKey().contains(COMPONENT_SEPARATOR)) {
-        throw new BadConfigException("Components must not contain " +
-            COMPONENT_SEPARATOR + ": " + entry.getKey());
-      }
       if (entry.getValue().containsKey(ResourceKeys.COMPONENT_PRIORITY)) {
         priorities.add(Integer.parseInt(entry.getValue().get(
             ResourceKeys.COMPONENT_PRIORITY)));
@@ -358,31 +374,73 @@ public class InstanceBuilder {
         throw new BadConfigException("Couldn't read configuration for " +
             "external component " + component);
       }
-      String externalAppDef = componentConf.getAppConfOperations()
-          .getGlobalOptions().get(AgentKeys.APP_DEF);
+
+      ConfTreeOperations componentAppConf = 
componentConf.getAppConfOperations();
+      String externalAppDef = componentAppConf.get(AgentKeys.APP_DEF);
       if (SliderUtils.isSet(externalAppDef)) {
         Path newAppDef = new Path(coreFS.buildAppDefDirPath(clustername),
             component + "_" + SliderKeys.DEFAULT_APP_PKG);
-        componentConf.getAppConfOperations().set(AgentKeys.APP_DEF, newAppDef);
-        externalAppDefs.put(newAppDef, new Path(externalAppDef));
+        componentAppConf.set(AgentKeys.APP_DEF, newAppDef);
+        componentAppConf.append(AgentKeys.APP_DEF_ORIGINAL, externalAppDef);
+        log.info("Copying external appdef {} to {} for {}", externalAppDef,
+            newAppDef, component);
+        externalAppDefs.put(externalAppDef, newAppDef);
+        externalAppDef = newAppDef.toString();
       }
+
       for (String rcomp : componentConf.getResourceOperations()
           .getComponentNames()) {
         if (COMPONENT_AM.equals(rcomp)) {
           continue;
         }
         log.debug("Adding component {} to appConf for {}", rcomp, component);
-        componentConf.getAppConfOperations().getOrAddComponent(rcomp);
+        componentAppConf.getOrAddComponent(rcomp);
       }
-      SliderUtils.mergeMaps(
-          componentConf.getAppConfOperations().getGlobalOptions().options,
-          appConf.getComponent(component).options);
-      componentConf.getAppConfOperations().getGlobalOptions()
-          .remove(COMPONENT_TYPE);
       componentConf.resolve();
 
-      mergeExternalComponent(appConf, componentConf.getAppConfOperations(),
-          component, null);
+      for (String rcomp : componentConf.getResourceOperations()
+          .getComponentNames()) {
+        if (COMPONENT_AM.equals(rcomp)) {
+          continue;
+        }
+        String componentAppDef = componentAppConf.getComponentOpt(
+            rcomp, AgentKeys.APP_DEF, null);
+        if (SliderUtils.isUnset(componentAppDef) ||
+            componentAppDef.equals(externalAppDef)) {
+          continue;
+        }
+        if (externalAppDefs.containsKey(componentAppDef)) {
+          log.info("Using external appdef {} for {}",
+              externalAppDefs.get(componentAppDef), rcomp);
+        } else {
+          String existingPrefix = componentAppConf.getComponentOpt(rcomp,
+              ROLE_PREFIX, null);
+          if (SliderUtils.isUnset(existingPrefix)) {
+            existingPrefix = "";
+          } else {
+            existingPrefix = COMPONENT_SEPARATOR + SliderUtils.trimPrefix(
+                existingPrefix);
+          }
+          Path newAppDef = new Path(coreFS.buildAppDefDirPath(clustername),
+              component + existingPrefix + "_" + SliderKeys.DEFAULT_APP_PKG);
+          externalAppDefs.put(componentAppDef, newAppDef);
+          log.info("Copying external appdef {} to {} for {}", componentAppDef,
+              newAppDef, component + COMPONENT_SEPARATOR + rcomp);
+        }
+        componentAppConf.setComponentOpt(rcomp, AgentKeys.APP_DEF,
+            externalAppDefs.get(componentAppDef).toString());
+        componentAppConf.appendComponentOpt(rcomp,
+            AgentKeys.APP_DEF_ORIGINAL, componentAppDef);
+      }
+      Set<Path> newAppDefs = new HashSet<>();
+      newAppDefs.addAll(externalAppDefs.values());
+      if (newAppDefs.size() != externalAppDefs.size()) {
+        throw new IllegalStateException("Values repeat in external appdefs "
+            + externalAppDefs);
+      }
+      log.info("External appdefs after {}: {}", component, externalAppDefs);
+
+      mergeExternalComponent(appConf, componentAppConf, component, null);
       mergeExternalComponent(resources, componentConf.getResourceOperations(),
           component, getNextPriority());
     }
@@ -411,8 +469,8 @@ public class InstanceBuilder {
       action = new ConfDirSnapshotAction(appconfdir);
     }
     persister.save(instanceDescription, action);
-    for (Entry<Path, Path> appDef : externalAppDefs.entrySet()) {
-      SliderUtils.copy(conf, appDef.getValue(), appDef.getKey());
+    for (Entry<String, Path> appDef : externalAppDefs.entrySet()) {
+      SliderUtils.copy(conf, new Path(appDef.getKey()), appDef.getValue());
     }
   }
 

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/core/conf/ConfTreeOperations.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/core/conf/ConfTreeOperations.java 
b/slider-core/src/main/java/org/apache/slider/core/conf/ConfTreeOperations.java
index 4a0ae41..038513e 100644
--- 
a/slider-core/src/main/java/org/apache/slider/core/conf/ConfTreeOperations.java
+++ 
b/slider-core/src/main/java/org/apache/slider/core/conf/ConfTreeOperations.java
@@ -214,6 +214,23 @@ public class ConfTreeOperations {
   public String get(String key) {
     return globalOptions.get(key);
   }
+  /**
+   * append to a global option
+   * @param key key
+   * @return value
+   *
+   */
+  public String append(String key, String value) {
+    if (SliderUtils.isUnset(value)) {
+      return null;
+    }
+    if (globalOptions.containsKey(key)) {
+      globalOptions.put(key, globalOptions.get(key) + "," + value);
+    } else {
+      globalOptions.put(key, value);
+    }
+    return globalOptions.get(key);
+  }
 
   /**
    * Propagate all global keys matching a prefix
@@ -471,4 +488,26 @@ public class ConfTreeOperations {
     setComponentOpt(role, option, Long.toString(val));
   }
 
+  /**
+   * append to a component option
+   * @param key key
+   * @return value
+   *
+   */
+  public String appendComponentOpt(String role, String key, String value) {
+    if (SliderUtils.isUnset(value)) {
+      return null;
+    }
+    MapOperations roleopts = getComponent(role);
+    if (roleopts == null) {
+      return null;
+    }
+
+    if (roleopts.containsKey(key)) {
+      roleopts.put(key, roleopts.get(key) + "," + value);
+    } else {
+      roleopts.put(key, value);
+    }
+    return roleopts.get(key);
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
index 6eae75e..8c0a2e4 100644
--- 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
+++ 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentClientProvider.java
@@ -184,7 +184,8 @@ public class AgentClientProvider extends 
AbstractClientProvider
 
       if (metaInfo != null) {
         Component componentDef = metaInfo.getApplicationComponent(
-            getMetainfoComponentName(name));
+            getMetainfoComponentName(name,
+                instanceDefinition.getAppConfOperations()));
         if (componentDef == null) {
           throw new BadConfigException(
               "Component %s is not a member of application.", name);
@@ -214,7 +215,8 @@ public class AgentClientProvider extends 
AbstractClientProvider
       // fileSystem may be null for tests
       if (metaInfo != null) {
         Component componentDef = metaInfo.getApplicationComponent(
-            getMetainfoComponentName(name));
+            getMetainfoComponentName(name,
+                instanceDefinition.getAppConfOperations()));
         // already checked it wasn't null
 
         // ensure that intance count is 0 for client components

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/providers/agent/AgentKeys.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentKeys.java 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentKeys.java
index 8514401..565d73d 100644
--- a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentKeys.java
+++ b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentKeys.java
@@ -68,6 +68,7 @@ public interface AgentKeys {
   String AGENT_MAIN_SCRIPT = "agent/main.py";
 
   String APP_DEF = "application.def";
+  String APP_DEF_ORIGINAL = "application.def.original";
   String ADDON_PREFIX = "application.addon.";
   String ADDONS = "application.addons";
   String AGENT_VERSION = "agent.version";

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
index 66fac99..452122f 100644
--- 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
+++ 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentProviderService.java
@@ -133,7 +133,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import static org.apache.slider.providers.agent.AgentUtils.getMetainfoMapKey;
+import static org.apache.slider.api.RoleKeys.ROLE_PREFIX;
 import static 
org.apache.slider.server.appmaster.web.rest.RestPaths.SLIDER_PATH_AGENTS;
 
 /**
@@ -288,7 +288,8 @@ public class AgentProviderService extends 
AbstractProviderService implements
                              SliderFileSystem fileSystem,
                              String roleGroup)
       throws IOException, SliderException {
-    String mapKey = getMetainfoMapKey(roleGroup);
+    String mapKey = instanceDefinition.getAppConfOperations()
+        .getComponentOpt(roleGroup, ROLE_PREFIX, DEFAULT_METAINFO_MAP_KEY);
     String appDef = SliderUtils.getApplicationDefinitionPath(
         instanceDefinition.getAppConfOperations(), roleGroup);
     MapOperations component = null;
@@ -319,16 +320,17 @@ public class AgentProviderService extends 
AbstractProviderService implements
               log.info("Modifying external metainfo component name to {}",
                   comp.getName());
             }
-            String commandPrefix = mapKey.substring(0,
-                mapKey.indexOf(COMPONENT_SEPARATOR)+1);
             for (CommandOrder co : commandOrders) {
               log.info("Adding prefix {} to command order {}",
-                  commandPrefix, co);
-              co.setCommand(commandPrefix + co.getCommand());
-              co.setRequires(commandPrefix + co.getRequires());
+                  mapKey, co);
+              co.setCommand(mapKey + co.getCommand());
+              co.setRequires(mapKey + co.getRequires());
             }
           }
-          commandOrder.mergeCommandOrders(commandOrders);
+          log.debug("Merging command orders {} for {}", commandOrders,
+              roleGroup);
+          commandOrder.mergeCommandOrders(commandOrders,
+              instanceDefinition.getResourceOperations());
           Map<String, DefaultConfig> defaultConfigs =
               initializeDefaultConfigs(fileSystem, appDef, metaInfo);
           metaInfoMap.put(mapKey, new MetainfoHolder(metaInfo, 
defaultConfigs));
@@ -1393,7 +1395,10 @@ public class AgentProviderService extends 
AbstractProviderService implements
 
   @VisibleForTesting
   protected Metainfo getMetaInfo(String roleGroup) {
-    MetainfoHolder mh = this.metaInfoMap.get(getMetainfoMapKey(roleGroup));
+    ConfTreeOperations appConf = getAmState().getAppConfSnapshot();
+    String mapKey = appConf.getComponentOpt(roleGroup, ROLE_PREFIX,
+        DEFAULT_METAINFO_MAP_KEY);
+    MetainfoHolder mh = this.metaInfoMap.get(mapKey);
     if (mh == null) {
       return null;
     }
@@ -1468,7 +1473,10 @@ public class AgentProviderService extends 
AbstractProviderService implements
   }
 
   protected Map<String, DefaultConfig> getDefaultConfigs(String roleGroup) {
-    return metaInfoMap.get(getMetainfoMapKey(roleGroup)).defaultConfigs;
+    ConfTreeOperations appConf = getAmState().getAppConfSnapshot();
+    String mapKey = appConf.getComponentOpt(roleGroup, ROLE_PREFIX,
+        DEFAULT_METAINFO_MAP_KEY);
+    return metaInfoMap.get(mapKey).defaultConfigs;
   }
 
   private int getHeartbeatMonitorInterval() {
@@ -2900,14 +2908,14 @@ public class AgentProviderService extends 
AbstractProviderService implements
     tokens.put("${NN_HOST}", URI.create(nnuri).getHost());
     tokens.put("${ZK_HOST}", appConf.get(OptionKeys.ZOOKEEPER_HOSTS));
     tokens.put("${DEFAULT_ZK_PATH}", appConf.get(OptionKeys.ZOOKEEPER_PATH));
-    String mapKey = getMetainfoMapKey(componentGroup);
+    String prefix = appConf.getComponentOpt(componentGroup, ROLE_PREFIX,
+        null);
     String dataDirSuffix = "";
-    if (!DEFAULT_METAINFO_MAP_KEY.equals(mapKey)) {
-      dataDirSuffix = "_" + mapKey.substring(0, mapKey.length()-1);
+    if (prefix == null) {
+      prefix = "";
     } else {
-      mapKey = "";
+      dataDirSuffix = "_" + SliderUtils.trimPrefix(prefix);
     }
-    mapKey = mapKey.toLowerCase();
     tokens.put("${DEFAULT_DATA_DIR}", getAmState()
         .getInternalsSnapshot()
         .getGlobalOptions()
@@ -2915,8 +2923,8 @@ public class AgentProviderService extends 
AbstractProviderService implements
     tokens.put("${JAVA_HOME}", appConf.get(AgentKeys.JAVA_HOME));
     tokens.put("${COMPONENT_NAME}", componentName);
     tokens.put("${COMPONENT_NAME.lc}", componentName.toLowerCase());
-    tokens.put("${COMPONENT_PREFIX}", mapKey);
-    tokens.put("${COMPONENT_PREFIX.lc}", mapKey.toLowerCase());
+    tokens.put("${COMPONENT_PREFIX}", prefix);
+    tokens.put("${COMPONENT_PREFIX.lc}", prefix.toLowerCase());
     if (!componentName.equals(componentGroup) && 
componentName.startsWith(componentGroup)) {
       tokens.put("${COMPONENT_ID}", 
componentName.substring(componentGroup.length()));
     }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/providers/agent/AgentUtils.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentUtils.java 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentUtils.java
index ed5108c..23e05a3 100644
--- 
a/slider-core/src/main/java/org/apache/slider/providers/agent/AgentUtils.java
+++ 
b/slider-core/src/main/java/org/apache/slider/providers/agent/AgentUtils.java
@@ -21,6 +21,7 @@ import org.apache.hadoop.fs.FileSystem;
 import org.apache.hadoop.fs.Path;
 import org.apache.slider.common.tools.SliderFileSystem;
 import org.apache.slider.common.tools.SliderUtils;
+import org.apache.slider.core.conf.ConfTreeOperations;
 import org.apache.slider.core.exceptions.BadConfigException;
 import 
org.apache.slider.providers.agent.application.metadata.AbstractMetainfoParser;
 import 
org.apache.slider.providers.agent.application.metadata.AddonPackageMetainfoParser;
@@ -35,8 +36,7 @@ import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 
-import static org.apache.slider.common.SliderKeys.COMPONENT_SEPARATOR;
-import static 
org.apache.slider.providers.agent.AgentKeys.DEFAULT_METAINFO_MAP_KEY;
+import static org.apache.slider.api.RoleKeys.ROLE_PREFIX;
 
 /**
  *
@@ -135,24 +135,16 @@ public class AgentUtils {
     return new DefaultConfigParser().parse(configStream);
   }
 
-  static String getMetainfoMapKey(String roleGroup) {
-    if (roleGroup == null) {
-      return DEFAULT_METAINFO_MAP_KEY;
-    }
-    int lastIndex = roleGroup.lastIndexOf(COMPONENT_SEPARATOR);
-    if (lastIndex == -1) {
-      return DEFAULT_METAINFO_MAP_KEY;
-    } else {
-      return roleGroup.substring(0, lastIndex+1);
-    }
-  }
-
-  static String getMetainfoComponentName(String roleGroup) {
-    int lastIndex = roleGroup.lastIndexOf(COMPONENT_SEPARATOR);
-    if (lastIndex == -1) {
+  static String getMetainfoComponentName(String roleGroup,
+      ConfTreeOperations appConf) throws BadConfigException {
+    String prefix = appConf.getComponentOpt(roleGroup, ROLE_PREFIX, null);
+    if (prefix == null) {
       return roleGroup;
-    } else {
-      return roleGroup.substring(lastIndex+1);
     }
+    if (!roleGroup.startsWith(prefix)) {
+      throw new BadConfigException("Component " + roleGroup + " doesn't start" 
+
+          " with prefix " + prefix);
+    }
+    return roleGroup.substring(prefix.length());
   }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/providers/agent/ComponentCommandOrder.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/providers/agent/ComponentCommandOrder.java
 
b/slider-core/src/main/java/org/apache/slider/providers/agent/ComponentCommandOrder.java
index e2c879e..4abac7a 100644
--- 
a/slider-core/src/main/java/org/apache/slider/providers/agent/ComponentCommandOrder.java
+++ 
b/slider-core/src/main/java/org/apache/slider/providers/agent/ComponentCommandOrder.java
@@ -18,6 +18,8 @@
 
 package org.apache.slider.providers.agent;
 
+import org.apache.slider.common.tools.SliderUtils;
+import org.apache.slider.core.conf.ConfTreeOperations;
 import org.apache.slider.providers.agent.application.metadata.CommandOrder;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -25,9 +27,12 @@ import org.slf4j.LoggerFactory;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 
+import static org.apache.slider.api.RoleKeys.ROLE_PREFIX;
+
 /**
  * Stores the command dependency order for all components in a service. 
<commandOrder>
  * <command>SUPERVISOR-START</command> <requires>NIMBUS-STARTED</requires> 
</commandOrder> Means, SUPERVISOR START
@@ -39,19 +44,36 @@ public class ComponentCommandOrder {
   private static char SPLIT_CHAR = '-';
   Map<Command, Map<String, List<ComponentState>>> dependencies =
       new HashMap<Command, Map<String, List<ComponentState>>>();
+  Map<String, Collection<String>> prefixRoleMap = new HashMap<>();
+  Map<String, String> rolePrefixMap = new HashMap<>();
 
   public ComponentCommandOrder() {}
 
-  public ComponentCommandOrder(List<CommandOrder> commandOrders) {
-    mergeCommandOrders(commandOrders);
+  public ComponentCommandOrder(List<CommandOrder> commandOrders,
+      ConfTreeOperations resources) {
+    mergeCommandOrders(commandOrders, resources);
   }
 
-  void mergeCommandOrders(List<CommandOrder> commandOrders) {
+  void mergeCommandOrders(List<CommandOrder> commandOrders,
+      ConfTreeOperations resources) {
+    for (String component : resources.getComponentNames()) {
+      String prefix = SliderUtils.trimPrefix(
+          resources.getComponentOpt(component, ROLE_PREFIX, null));
+      if (prefix != null) {
+        rolePrefixMap.put(component, prefix);
+        if (!prefixRoleMap.containsKey(prefix)) {
+          prefixRoleMap.put(prefix, new HashSet<String>());
+        }
+        prefixRoleMap.get(prefix).add(component);
+      }
+    }
     if (commandOrders != null && commandOrders.size() > 0) {
       for (CommandOrder commandOrder : commandOrders) {
-        ComponentCommand componentCmd = 
getComponentCommand(commandOrder.getCommand());
+        ComponentCommand componentCmd = getComponentCommand(
+            commandOrder.getCommand(), resources);
         String requires = commandOrder.getRequires();
-        List<ComponentState> requiredStates = parseRequiredStates(requires);
+        List<ComponentState> requiredStates = parseRequiredStates(requires,
+            resources);
         if (requiredStates.size() > 0) {
           Map<String, List<ComponentState>> compDep = 
dependencies.get(componentCmd.command);
           if (compDep == null) {
@@ -71,7 +93,8 @@ public class ComponentCommandOrder {
     }
   }
 
-  private List<ComponentState> parseRequiredStates(String requires) {
+  private List<ComponentState> parseRequiredStates(String requires,
+      ConfTreeOperations resources) {
     if (requires == null || requires.length() < 2) {
       throw new IllegalArgumentException("Input cannot be null and must 
contain component and state.");
     }
@@ -79,13 +102,14 @@ public class ComponentCommandOrder {
     String[] componentStates = requires.split(",");
     List<ComponentState> retList = new ArrayList<ComponentState>();
     for (String componentStateStr : componentStates) {
-      retList.add(getComponentState(componentStateStr));
+      retList.add(getComponentState(componentStateStr, resources));
     }
 
     return retList;
   }
 
-  private ComponentCommand getComponentCommand(String compCmdStr) {
+  private ComponentCommand getComponentCommand(String compCmdStr,
+      ConfTreeOperations resources) {
     if (compCmdStr == null || compCmdStr.trim().length() < 2) {
       throw new IllegalArgumentException("Input cannot be null and must 
contain component and command.");
     }
@@ -98,6 +122,11 @@ public class ComponentCommandOrder {
     String compStr = compCmdStr.substring(0, splitIndex);
     String cmdStr = compCmdStr.substring(splitIndex + 1);
 
+    if (resources.getComponent(compStr) == null && 
!prefixRoleMap.containsKey(compStr)) {
+      throw new IllegalArgumentException("Component " + compStr + " specified" 
+
+          " in command order does not exist");
+    }
+
     Command cmd = Command.valueOf(cmdStr);
 
     if (cmd != Command.START) {
@@ -106,7 +135,8 @@ public class ComponentCommandOrder {
     return new ComponentCommand(compStr, cmd);
   }
 
-  private ComponentState getComponentState(String compStStr) {
+  private ComponentState getComponentState(String compStStr,
+      ConfTreeOperations resources) {
     if (compStStr == null || compStStr.trim().length() < 2) {
       throw new IllegalArgumentException("Input cannot be null.");
     }
@@ -119,6 +149,11 @@ public class ComponentCommandOrder {
     String compStr = compStStr.substring(0, splitIndex);
     String stateStr = compStStr.substring(splitIndex + 1);
 
+    if (resources.getComponent(compStr) == null && 
!prefixRoleMap.containsKey(compStr)) {
+      throw new IllegalArgumentException("Component " + compStr + " specified" 
+
+          " in command order does not exist");
+    }
+
     State state = State.valueOf(stateStr);
     if (state != State.STARTED && state != State.INSTALLED) {
       throw new IllegalArgumentException("Dependency order can only be 
specified against STARTED/INSTALLED.");
@@ -129,40 +164,43 @@ public class ComponentCommandOrder {
   // dependency is still on component level, but not package level
   // so use component name to check dependency, not component-package
   public boolean canExecute(String component, Command command, 
Collection<ComponentInstanceState> currentStates) {
-    boolean canExecute = true;
-    if (dependencies.containsKey(command) && 
dependencies.get(command).containsKey(component)) {
-      List<ComponentState> required = dependencies.get(command).get(component);
-      for (ComponentState stateToMatch : required) {
-        for (ComponentInstanceState currState : currentStates) {
-          log.debug("Checking schedule {} {} against dependency {} is {}",
-                    component, command, currState.getComponentName(), 
currState.getState());
-          if (currState.getComponentName().equals(stateToMatch.componentName)) 
{
-            if (currState.getState() != stateToMatch.state) {
-              if (stateToMatch.state == State.STARTED) {
+    if (!dependencies.containsKey(command)) {
+      return true;
+    }
+    List<ComponentState> required = new ArrayList<>();
+    if (dependencies.get(command).containsKey(component)) {
+      required.addAll(dependencies.get(command).get(component));
+    }
+    String prefix = rolePrefixMap.get(component);
+    if (prefix != null && dependencies.get(command).containsKey(prefix)) {
+      required.addAll(dependencies.get(command).get(prefix));
+    }
+
+    for (ComponentState stateToMatch : required) {
+      for (ComponentInstanceState currState : currentStates) {
+        log.debug("Checking schedule {} {} against dependency {} is {}",
+            component, command, currState.getComponentName(), 
currState.getState());
+        if (currState.getComponentName().equals(stateToMatch.componentName) ||
+            (prefixRoleMap.containsKey(stateToMatch.componentName) &&
+                
prefixRoleMap.get(stateToMatch.componentName).contains(currState.getComponentName())))
 {
+          if (currState.getState() != stateToMatch.state) {
+            if (stateToMatch.state == State.STARTED) {
+              log.info("Cannot schedule {} {} as dependency {} is {}",
+                  component, command, currState.getComponentName(), 
currState.getState());
+              return false;
+            } else {
+              //state is INSTALLED
+              if (currState.getState() != State.STARTING && 
currState.getState() != State.STARTED) {
                 log.info("Cannot schedule {} {} as dependency {} is {}",
-                         component, command, currState.getComponentName(), 
currState.getState());
-                canExecute = false;
-              } else {
-                //state is INSTALLED
-                if (currState.getState() != State.STARTING && 
currState.getState() != State.STARTED) {
-                  log.info("Cannot schedule {} {} as dependency {} is {}",
-                           component, command, currState.getComponentName(), 
currState.getState());
-                  canExecute = false;
-                }
+                    component, command, currState.getComponentName(), 
currState.getState());
+                return false;
               }
             }
           }
-          if (!canExecute) {
-            break;
-          }
-        }
-        if (!canExecute) {
-          break;
         }
       }
     }
-
-    return canExecute;
+    return true;
   }
 
   static class ComponentState {

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
 
b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
index 3213d93..3ba766f 100644
--- 
a/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
+++ 
b/slider-core/src/main/java/org/apache/slider/server/appmaster/state/AppState.java
@@ -1808,6 +1808,11 @@ public class AppState {
         SliderUtils.mergeMapsIgnoreDuplicateKeys(cd.getRole(rolename),
             groupOptions.options);
       }
+      String prefix = instanceDefinition.getAppConfOperations()
+          .getComponentOpt(role.getGroup(), ROLE_PREFIX, null);
+      if (SliderUtils.isSet(prefix)) {
+        cd.setRoleOpt(rolename, ROLE_PREFIX, SliderUtils.trimPrefix(prefix));
+      }
       List<String> instances = instanceMap.get(rolename);
       int nodeCount = instances != null ? instances.size(): 0;
       cd.setRoleOpt(rolename, COMPONENT_INSTANCES,

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/appConfig_external_component_nested.json
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/appConfig_external_component_nested.json
 
b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/appConfig_external_component_nested.json
new file mode 100644
index 0000000..f67f83a
--- /dev/null
+++ 
b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/appConfig_external_component_nested.json
@@ -0,0 +1,12 @@
+{
+  "schema": "http://example.org/specification/v2.0.0";,
+  "metadata": {
+  },
+  "global": {
+  },
+  "components": {
+    "test-external-component": {
+      "site.global.component_type": "external"
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo.json
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo.json 
b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo.json
index 073d1ff..bfe2afb 100644
--- a/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo.json
+++ b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo.json
@@ -2,6 +2,12 @@
   "schemaVersion": "2.1",
   "application": {
     "name": "SLEEPER",
+    "commandOrders": [
+      {
+        "command": "SLEEP_100-START",
+        "requires": "SLEEP_LONG-STARTED"
+      }
+    ],
     "components": [
       {
         "name": "SLEEP_100",

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo_external_component_nested.json
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo_external_component_nested.json
 
b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo_external_component_nested.json
new file mode 100644
index 0000000..43a2200
--- /dev/null
+++ 
b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/metainfo_external_component_nested.json
@@ -0,0 +1,14 @@
+{
+  "schemaVersion": "2.1",
+  "application": {
+    "name": "SLEEPER",
+    "commandOrders": [
+      {
+        "command": "test-external-component-test_sleep-SLEEP_100-START",
+        "requires": "test-external-component-SLEEP_100-STARTED"
+      }
+    ],
+    "components": [
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/resources_external_component_nested.json
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/resources_external_component_nested.json
 
b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/resources_external_component_nested.json
new file mode 100644
index 0000000..8aa86b4
--- /dev/null
+++ 
b/slider-core/src/test/app_packages/test_min_pkg/sleep_cmd/resources_external_component_nested.json
@@ -0,0 +1,12 @@
+{
+  "schema" : "http://example.org/specification/v2.0.0";,
+  "metadata" : {
+  },
+  "global" : {
+  },
+  "components": {
+    "slider-appmaster": {
+      "yarn.memory": "384"
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestBuildExternalComponent.groovy
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestBuildExternalComponent.groovy
 
b/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestBuildExternalComponent.groovy
new file mode 100644
index 0000000..8dd693e
--- /dev/null
+++ 
b/slider-core/src/test/groovy/org/apache/slider/agent/standalone/TestBuildExternalComponent.groovy
@@ -0,0 +1,138 @@
+/*
+ * 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.slider.agent.standalone
+
+import groovy.transform.CompileStatic
+import groovy.util.logging.Slf4j
+import org.apache.hadoop.fs.Path
+import org.apache.slider.agent.AgentMiniClusterTestBase
+import org.apache.slider.api.ResourceKeys
+import org.apache.slider.api.RoleKeys
+import org.apache.slider.client.SliderClient
+import org.apache.slider.common.SliderKeys
+import org.apache.slider.common.params.SliderActions
+import org.apache.slider.common.tools.SliderFileSystem
+import org.apache.slider.common.tools.SliderUtils
+import org.apache.slider.core.conf.AggregateConf
+import org.apache.slider.core.main.ServiceLauncher
+import org.apache.slider.providers.agent.AgentKeys
+import org.junit.Test
+
+import static org.apache.slider.common.params.Arguments.*
+
+@CompileStatic
+@Slf4j
+
+class TestBuildExternalComponent extends AgentMiniClusterTestBase {
+
+  @Test
+  public void testExternalComponentBuild() throws Throwable {
+    String clustername = createMiniCluster("", configuration, 1, true)
+
+    describe "verify external components"
+
+    String echo = "echo"
+    ServiceLauncher<SliderClient> launcher = createOrBuildCluster(
+        SliderActions.ACTION_BUILD,
+        clustername,
+        [(echo): 1],
+        [ARG_RES_COMP_OPT, echo, ResourceKeys.COMPONENT_PRIORITY, "2"],
+        true,
+        false,
+        agentDefOptions)
+    SliderClient sliderClient = launcher.service
+    addToTeardown(sliderClient);
+
+    // verify the cluster exists
+    assert 0 == sliderClient.actionExists(clustername, false)
+
+    String parent1 = clustername + "_ext"
+    launcher = createOrBuildCluster(
+      SliderActions.ACTION_BUILD,
+      parent1,
+      [(echo): 1],
+      [ARG_COMP_OPT, clustername, COMPONENT_TYPE, EXTERNAL_COMPONENT,
+       ARG_RES_COMP_OPT, echo, ResourceKeys.COMPONENT_PRIORITY, "3"],
+      true,
+      false,
+      agentDefOptions)
+    sliderClient = launcher.service
+    addToTeardown(sliderClient);
+
+    // verify the cluster exists
+    assert 0 == sliderClient.actionExists(parent1, false)
+    // verify generated conf
+    def aggregateConf = sliderClient.loadPersistedClusterDescription(parent1)
+    assert 3 == aggregateConf.resourceOperations.componentNames.size()
+    assert 
aggregateConf.resourceOperations.componentNames.contains(COMPONENT_AM)
+    assert aggregateConf.resourceOperations.componentNames.contains(echo)
+    assert 
aggregateConf.resourceOperations.componentNames.contains(clustername + 
COMPONENT_SEPARATOR + echo)
+
+    aggregateConf.resolve()
+    assert aggregateConf.appConfOperations.get(AgentKeys.APP_DEF).equals(
+      aggregateConf.appConfOperations.getComponentOpt(echo, AgentKeys.APP_DEF,
+        aggregateConf.appConfOperations.get(AgentKeys.APP_DEF)))
+    SliderFileSystem sfs = createSliderFileSystem()
+    String appdefdir = sfs.buildAppDefDirPath(parent1)
+    checkComponent(aggregateConf, clustername + COMPONENT_SEPARATOR + echo, 
appdefdir)
+
+    String parent2 = "parent"
+    launcher = createOrBuildCluster(
+      SliderActions.ACTION_BUILD,
+      parent2,
+      [(echo): 1],
+      [ARG_COMP_OPT, clustername, COMPONENT_TYPE, EXTERNAL_COMPONENT,
+       ARG_COMP_OPT, parent1, COMPONENT_TYPE, EXTERNAL_COMPONENT,
+       ARG_RES_COMP_OPT, echo, ResourceKeys.COMPONENT_PRIORITY, "4"],
+      true,
+      false,
+      agentDefOptions)
+    sliderClient = launcher.service
+    addToTeardown(sliderClient);
+
+    // verify the cluster exists
+    assert 0 == sliderClient.actionExists(parent2, false)
+    // verify generated conf
+    aggregateConf = sliderClient.loadPersistedClusterDescription(parent2)
+    assert 5 == aggregateConf.resourceOperations.componentNames.size()
+    assert 
aggregateConf.resourceOperations.componentNames.contains(COMPONENT_AM)
+    assert aggregateConf.resourceOperations.componentNames.contains(echo)
+    assert 
aggregateConf.resourceOperations.componentNames.contains(clustername + 
COMPONENT_SEPARATOR + echo)
+    assert aggregateConf.resourceOperations.componentNames.contains(parent1 + 
COMPONENT_SEPARATOR + echo)
+    assert aggregateConf.resourceOperations.componentNames.contains(parent1 + 
COMPONENT_SEPARATOR + clustername + COMPONENT_SEPARATOR + echo)
+
+    aggregateConf.resolve()
+    assert aggregateConf.appConfOperations.get(AgentKeys.APP_DEF).equals(
+      aggregateConf.appConfOperations.getComponentOpt(echo, AgentKeys.APP_DEF,
+        aggregateConf.appConfOperations.get(AgentKeys.APP_DEF)))
+    appdefdir = sfs.buildAppDefDirPath(parent2)
+    checkComponent(aggregateConf, clustername + COMPONENT_SEPARATOR + echo, 
appdefdir)
+    checkComponent(aggregateConf, parent1 + COMPONENT_SEPARATOR + echo, 
appdefdir)
+    checkComponent(aggregateConf, parent1 + COMPONENT_SEPARATOR + clustername 
+ COMPONENT_SEPARATOR + echo, appdefdir)
+  }
+
+  private void checkComponent(AggregateConf aggConf, String component,
+                              String appdefdir) {
+    String path = new Path(appdefdir, SliderUtils.trimPrefix(
+      aggConf.appConfOperations.getComponentOpt(component, RoleKeys
+        .ROLE_PREFIX, null)) + "_" + SliderKeys.DEFAULT_APP_PKG).toString()
+    assert path.equals(aggConf.appConfOperations.getComponentOpt(component,
+      AgentKeys.APP_DEF, null))
+  }
+}

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentProviderService.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentProviderService.java
 
b/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentProviderService.java
index 07d21d7..a6d52b4 100644
--- 
a/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentProviderService.java
+++ 
b/slider-core/src/test/java/org/apache/slider/providers/agent/TestAgentProviderService.java
@@ -152,10 +152,6 @@ public class TestAgentProviderService {
                                                + "          
<command>HBASE_REGIONSERVER-START</command>\n"
                                                + "          
<requires>HBASE_MASTER-STARTED</requires>\n"
                                                + "        </commandOrder>\n"
-                                               + "        <commandOrder>\n"
-                                               + "          
<command>A-START</command>\n"
-                                               + "          
<requires>B-STARTED</requires>\n"
-                                               + "        </commandOrder>\n"
                                                + "      </commandOrders>\n"
                                                + "      <components>\n"
                                                + "        <component>\n"
@@ -434,6 +430,11 @@ public class TestAgentProviderService {
         .put(AgentKeys.AGENT_CONF, ".");
     instanceDefinition.getAppConfOperations().getGlobalOptions()
         .put(AgentKeys.AGENT_VERSION, ".");
+
+    instanceDefinition.getResourceOperations().getOrAddComponent(
+        "HBASE_MASTER");
+    instanceDefinition.getResourceOperations().getOrAddComponent(
+        "HBASE_REGIONSERVER");
     return instanceDefinition;
   }
 
@@ -530,13 +531,7 @@ public class TestAgentProviderService {
     assertNotNull(registryViewForProviders);
 
     ContainerLaunchContext ctx = createNiceMock(ContainerLaunchContext.class);
-    AggregateConf instanceDefinition = new AggregateConf();
-
-    instanceDefinition.setInternal(tree);
-    instanceDefinition.setAppConf(tree);
-    
instanceDefinition.getAppConfOperations().getGlobalOptions().put(AgentKeys.APP_DEF,
 ".");
-    
instanceDefinition.getAppConfOperations().getGlobalOptions().put(AgentKeys.AGENT_CONF,
 ".");
-    
instanceDefinition.getAppConfOperations().getGlobalOptions().put(AgentKeys.AGENT_VERSION,
 ".");
+    AggregateConf instanceDefinition = prepareConfForAgentStateTests();
 
     Container container = createNiceMock(Container.class);
     ProviderRole role_hm = new ProviderRole("HBASE_MASTER", 1);
@@ -1116,19 +1111,15 @@ public class TestAgentProviderService {
     Assert.assertEquals(found, 2);
 
     List<CommandOrder> cmdOrders = application.getCommandOrders();
-    Assert.assertEquals(cmdOrders.size(), 2);
+    Assert.assertEquals(cmdOrders.size(), 1);
     found = 0;
     for (CommandOrder co : application.getCommandOrders()) {
       if (co.getCommand().equals("HBASE_REGIONSERVER-START")) {
         Assert.assertTrue(co.getRequires().equals("HBASE_MASTER-STARTED"));
         found++;
       }
-      if (co.getCommand().equals("A-START")) {
-        Assert.assertEquals(co.getRequires(), "B-STARTED");
-        found++;
-      }
     }
-    Assert.assertEquals(found, 2);
+    Assert.assertEquals(found, 1);
 
     List<ConfigFile> configFiles = application.getConfigFiles();
     Assert.assertEquals(configFiles.size(), 2);
@@ -1204,8 +1195,6 @@ public class TestAgentProviderService {
     // Start of HBASE_RS depends on the start of HBASE_MASTER
     InputStream metainfo_1 = new 
ByteArrayInputStream(metainfo_1_str.getBytes());
     Metainfo metainfo = new MetainfoParser().fromXmlStream(metainfo_1);
-    ConfTree tree = new ConfTree();
-    tree.global.put(InternalKeys.INTERNAL_APPLICATION_IMAGE_PATH, ".");
 
     Configuration conf = new Configuration();
     AgentProviderService aps = createAgentProviderService(conf);
@@ -1213,13 +1202,7 @@ public class TestAgentProviderService {
     assertNotNull(registryViewForProviders);
     
     ContainerLaunchContext ctx = createNiceMock(ContainerLaunchContext.class);
-    AggregateConf instanceDefinition = new AggregateConf();
-
-    instanceDefinition.setInternal(tree);
-    instanceDefinition.setAppConf(tree);
-    
instanceDefinition.getAppConfOperations().getGlobalOptions().put(AgentKeys.APP_DEF,
 ".");
-    
instanceDefinition.getAppConfOperations().getGlobalOptions().put(AgentKeys.AGENT_CONF,
 ".");
-    
instanceDefinition.getAppConfOperations().getGlobalOptions().put(AgentKeys.AGENT_VERSION,
 ".");
+    AggregateConf instanceDefinition = prepareConfForAgentStateTests();
 
     Container container = createNiceMock(Container.class);
     ProviderRole role_hm = new ProviderRole("HBASE_MASTER", 1);

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-core/src/test/java/org/apache/slider/providers/agent/TestComponentCommandOrder.java
----------------------------------------------------------------------
diff --git 
a/slider-core/src/test/java/org/apache/slider/providers/agent/TestComponentCommandOrder.java
 
b/slider-core/src/test/java/org/apache/slider/providers/agent/TestComponentCommandOrder.java
index c123fbb..0e3e3ad 100644
--- 
a/slider-core/src/test/java/org/apache/slider/providers/agent/TestComponentCommandOrder.java
+++ 
b/slider-core/src/test/java/org/apache/slider/providers/agent/TestComponentCommandOrder.java
@@ -18,20 +18,38 @@
 
 package org.apache.slider.providers.agent;
 
+import org.apache.slider.core.conf.ConfTree;
+import org.apache.slider.core.conf.ConfTreeOperations;
 import org.apache.slider.providers.agent.application.metadata.CommandOrder;
 import org.apache.slider.server.appmaster.model.mock.MockContainerId;
 import org.junit.Assert;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import java.util.Arrays;
+import java.util.List;
+
+import static org.apache.slider.api.RoleKeys.ROLE_PREFIX;
 
 public class TestComponentCommandOrder {
   protected static final Logger log =
       LoggerFactory.getLogger(TestComponentCommandOrder.class);
   private final MockContainerId containerId = new MockContainerId(1);
 
+  private static ConfTreeOperations resources = new ConfTreeOperations(
+      new ConfTree());
+
+  @BeforeClass
+  public static void init() {
+    resources.getOrAddComponent("A");
+    resources.getOrAddComponent("B");
+    resources.getOrAddComponent("C");
+    resources.getOrAddComponent("D");
+    resources.getOrAddComponent("E");
+  }
+
   @Test
   public void testComponentCommandOrder() throws Exception {
     CommandOrder co1 = new CommandOrder();
@@ -44,7 +62,8 @@ public class TestComponentCommandOrder {
     co3.setCommand("B-START");
     co3.setRequires("C-STARTED,D-STARTED,E-INSTALLED");
 
-    ComponentCommandOrder cco = new ComponentCommandOrder(Arrays.asList(co1, 
co2, co3));
+    ComponentCommandOrder cco = new ComponentCommandOrder(
+        Arrays.asList(co1, co2, co3), resources);
     ComponentInstanceState cisB = new ComponentInstanceState("B",
         containerId, "aid");
     ComponentInstanceState cisC = new ComponentInstanceState("C", containerId, 
"aid");
@@ -100,13 +119,14 @@ public class TestComponentCommandOrder {
     cisB.setState(State.STARTED);
     cisC.setState(State.STARTED);
 
-    ComponentCommandOrder cco = new ComponentCommandOrder(Arrays.asList(co));
+    ComponentCommandOrder cco = new ComponentCommandOrder(Arrays.asList(co),
+        resources);
     Assert.assertTrue(cco.canExecute("A", Command.START, Arrays.asList(cisB, 
cisC)));
 
     co.setCommand(" A-STAR");
     co.setRequires("B-STARTED , C-STARTED");
     try {
-      cco = new ComponentCommandOrder(Arrays.asList(co));
+      cco = new ComponentCommandOrder(Arrays.asList(co), resources);
       Assert.fail("Instantiation should have failed.");
     } catch (IllegalArgumentException ie) {
       log.info(ie.getMessage());
@@ -115,7 +135,7 @@ public class TestComponentCommandOrder {
     co.setCommand(" -START");
     co.setRequires("B-STARTED , C-STARTED");
     try {
-      cco = new ComponentCommandOrder(Arrays.asList(co));
+      cco = new ComponentCommandOrder(Arrays.asList(co), resources);
       Assert.fail("Instantiation should have failed.");
     } catch (IllegalArgumentException ie) {
       log.info(ie.getMessage());
@@ -124,7 +144,7 @@ public class TestComponentCommandOrder {
     co.setCommand(" A-START");
     co.setRequires("B-STRTED , C-STARTED");
     try {
-      cco = new ComponentCommandOrder(Arrays.asList(co));
+      cco = new ComponentCommandOrder(Arrays.asList(co), resources);
       Assert.fail("Instantiation should have failed.");
     } catch (IllegalArgumentException ie) {
       log.info(ie.getMessage());
@@ -133,7 +153,7 @@ public class TestComponentCommandOrder {
     co.setCommand(" A-START");
     co.setRequires("B-STARTED , C-");
     try {
-      cco = new ComponentCommandOrder(Arrays.asList(co));
+      cco = new ComponentCommandOrder(Arrays.asList(co), resources);
       Assert.fail("Instantiation should have failed.");
     } catch (IllegalArgumentException ie) {
       log.info(ie.getMessage());
@@ -142,10 +162,83 @@ public class TestComponentCommandOrder {
     co.setCommand(" A-INSTALL");
     co.setRequires("B-STARTED");
     try {
-      cco = new ComponentCommandOrder(Arrays.asList(co));
+      cco = new ComponentCommandOrder(Arrays.asList(co), resources);
+      Assert.fail("Instantiation should have failed.");
+    } catch (IllegalArgumentException ie) {
+      log.info(ie.getMessage());
+    }
+  }
+
+  @Test
+  public void testComponentCommandOrderBadComponent() throws Exception {
+    ConfTreeOperations resourcesGood = new ConfTreeOperations(new ConfTree());
+    resourcesGood.getOrAddComponent("A");
+    resourcesGood.getOrAddComponent("Z");
+    ConfTreeOperations resourcesBad = new ConfTreeOperations(new ConfTree());
+
+    CommandOrder co1 = new CommandOrder();
+    co1.setCommand("A-START");
+    co1.setRequires("Z-STARTED");
+    CommandOrder co2 = new CommandOrder();
+    co2.setCommand("Z-START");
+    co2.setRequires("A-STARTED");
+
+    ComponentCommandOrder cco = new ComponentCommandOrder(
+        Arrays.asList(co1), resourcesGood);
+    try {
+      cco = new ComponentCommandOrder(Arrays.asList(co1), resourcesBad);
+      Assert.fail("Instantiation should have failed.");
+    } catch (IllegalArgumentException ie) {
+      log.info(ie.getMessage());
+    }
+
+    cco = new ComponentCommandOrder(Arrays.asList(co2), resourcesGood);
+    try {
+      cco = new ComponentCommandOrder(Arrays.asList(co2), resourcesBad);
       Assert.fail("Instantiation should have failed.");
     } catch (IllegalArgumentException ie) {
       log.info(ie.getMessage());
     }
   }
+
+  @Test
+  public void testComponentCommandOrderPrefixes() throws Exception {
+    ConfTreeOperations resources = new ConfTreeOperations(new ConfTree());
+    resources.getOrAddComponent("a-A").put(ROLE_PREFIX, "a-");
+    resources.getOrAddComponent("b-B1").put(ROLE_PREFIX, "b-");
+    resources.getOrAddComponent("b-B2").put(ROLE_PREFIX, "b-");
+    resources.getOrAddComponent("c-C").put(ROLE_PREFIX, "c-");
+
+    CommandOrder co1 = new CommandOrder();
+    co1.setCommand("b-START");
+    co1.setRequires("a-STARTED");
+    CommandOrder co2 = new CommandOrder();
+    co2.setCommand("c-START");
+    co2.setRequires("b-STARTED");
+
+    ComponentCommandOrder cco = new ComponentCommandOrder(
+        Arrays.asList(co1, co2), resources);
+
+    ComponentInstanceState cisA = new ComponentInstanceState("a-A", 
containerId, "aid");
+    ComponentInstanceState cisB1 = new ComponentInstanceState("b-B1", 
containerId, "aid");
+    ComponentInstanceState cisB2 = new ComponentInstanceState("b-B2", 
containerId, "aid");
+    ComponentInstanceState cisC = new ComponentInstanceState("c-C", 
containerId, "aid");
+    cisA.setState(State.INSTALLED);
+    cisB1.setState(State.INSTALLED);
+    cisB2.setState(State.INSTALLED);
+    cisC.setState(State.INSTALLED);
+    List<ComponentInstanceState> states = Arrays.asList(cisA, cisB1, cisB2, 
cisC);
+    Assert.assertTrue(cco.canExecute("a-A", Command.START, states));
+    Assert.assertFalse(cco.canExecute("b-B1", Command.START, states));
+    Assert.assertFalse(cco.canExecute("b-B2", Command.START, states));
+    Assert.assertFalse(cco.canExecute("c-C", Command.START, states));
+    cisA.setState(State.STARTED);
+    Assert.assertTrue(cco.canExecute("b-B1", Command.START, states));
+    Assert.assertTrue(cco.canExecute("b-B2", Command.START, states));
+    Assert.assertFalse(cco.canExecute("c-C", Command.START, states));
+    cisB1.setState(State.STARTED);
+    Assert.assertFalse(cco.canExecute("c-C", Command.START, states));
+    cisB2.setState(State.STARTED);
+    Assert.assertTrue(cco.canExecute("c-C", Command.START, states));
+  }
 }

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-funtest/src/test/groovy/org/apache/slider/funtest/ResourcePaths.groovy
----------------------------------------------------------------------
diff --git 
a/slider-funtest/src/test/groovy/org/apache/slider/funtest/ResourcePaths.groovy 
b/slider-funtest/src/test/groovy/org/apache/slider/funtest/ResourcePaths.groovy
index 1cb6f0f..13919df 100644
--- 
a/slider-funtest/src/test/groovy/org/apache/slider/funtest/ResourcePaths.groovy
+++ 
b/slider-funtest/src/test/groovy/org/apache/slider/funtest/ResourcePaths.groovy
@@ -40,4 +40,8 @@ interface ResourcePaths {
 
   String EXTERNAL_RESOURCES = 
"$SLIDER_CORE_APP_PACKAGES/test_min_pkg/sleep_cmd/resources_external_component.json"
   String EXTERNAL_APPCONFIG = 
"$SLIDER_CORE_APP_PACKAGES/test_min_pkg/sleep_cmd/appConfig_external_component.json"
+
+  String NESTED_RESOURCES = 
"$SLIDER_CORE_APP_PACKAGES/test_min_pkg/sleep_cmd/resources_external_component_nested.json"
+  String NESTED_META = 
"$SLIDER_CORE_APP_PACKAGES/test_min_pkg/sleep_cmd/metainfo_external_component_nested.json"
+  String NESTED_APPCONFIG = 
"$SLIDER_CORE_APP_PACKAGES/test_min_pkg/sleep_cmd/appConfig_external_component_nested.json"
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-slider/blob/a4dc574c/slider-funtest/src/test/groovy/org/apache/slider/funtest/misc/ExternalComponentIT.groovy
----------------------------------------------------------------------
diff --git 
a/slider-funtest/src/test/groovy/org/apache/slider/funtest/misc/ExternalComponentIT.groovy
 
b/slider-funtest/src/test/groovy/org/apache/slider/funtest/misc/ExternalComponentIT.groovy
index b5e0270..292508a 100644
--- 
a/slider-funtest/src/test/groovy/org/apache/slider/funtest/misc/ExternalComponentIT.groovy
+++ 
b/slider-funtest/src/test/groovy/org/apache/slider/funtest/misc/ExternalComponentIT.groovy
@@ -40,6 +40,7 @@ public class ExternalComponentIT extends AgentCommandTestBase
 
   static String NAME = "test-external-component"
   static String EXT_NAME = "test_sleep"
+  static String NESTED_NAME = "test-external-component-nested"
 
   static String BUILD_APPCONFIG = ResourcePaths.SLEEP_APPCONFIG
   static String BUILD_RESOURCES = ResourcePaths.EXTERNAL_RESOURCES
@@ -47,23 +48,30 @@ public class ExternalComponentIT extends 
AgentCommandTestBase
   static String TEST_APPCONFIG = ResourcePaths.EXTERNAL_APPCONFIG
   static String TEST_RESOURCES = ResourcePaths.EXTERNAL_RESOURCES
   static String TEST_METAINFO = ResourcePaths.SLEEP_META
+  static String NEST_APPCONFIG = ResourcePaths.NESTED_APPCONFIG
+  static String NEST_RESOURCES = ResourcePaths.NESTED_RESOURCES
+  static String NEST_METAINFO = ResourcePaths.NESTED_META
   public static final String SLEEP_100 = "SLEEP_100"
   public static final String SLEEP_LONG = "SLEEP_LONG"
   public static final String EXT_SLEEP_100 = EXT_NAME +
     SliderKeys.COMPONENT_SEPARATOR + SLEEP_100
   public static final String EXT_SLEEP_LONG = EXT_NAME +
     SliderKeys.COMPONENT_SEPARATOR + SLEEP_LONG
+  public static final String NESTED_PREFIX = NAME +
+    SliderKeys.COMPONENT_SEPARATOR
 
   @Before
   public void prepareCluster() {
     setupCluster(NAME)
     setupCluster(EXT_NAME)
+    setupCluster(NESTED_NAME)
   }
 
   @After
   public void destroyCluster() {
     cleanup(NAME)
     cleanup(EXT_NAME)
+    cleanup(NESTED_NAME)
   }
 
   @Test
@@ -129,5 +137,35 @@ public class ExternalComponentIT extends 
AgentCommandTestBase
     expectLiveContainerCountReached(NAME, EXT_SLEEP_100, 0,
       CONTAINER_LAUNCH_TIMEOUT)
 
+    cleanup(NAME)
+
+    describe NESTED_NAME
+
+    slider(0, [ACTION_BUILD, NAME, ARG_METAINFO, TEST_METAINFO,
+               ARG_TEMPLATE, TEST_APPCONFIG, ARG_RESOURCES, TEST_RESOURCES])
+
+    slider(0, [ACTION_CREATE, NESTED_NAME, ARG_METAINFO, NEST_METAINFO,
+               ARG_TEMPLATE, NEST_APPCONFIG, ARG_RESOURCES, NEST_RESOURCES])
+
+    ensureApplicationIsUp(NESTED_NAME)
+    status(0, NESTED_NAME)
+
+    cd = execStatus(NESTED_NAME)
+
+    assert 5 == cd.statistics.size()
+    assert cd.statistics.keySet().containsAll([SliderKeys.COMPONENT_AM,
+                                               NESTED_PREFIX + SLEEP_100,
+                                               NESTED_PREFIX + SLEEP_LONG,
+                                               NESTED_PREFIX + EXT_SLEEP_100,
+                                               NESTED_PREFIX + EXT_SLEEP_LONG])
+
+    expectLiveContainerCountReached(NESTED_NAME, NESTED_PREFIX + SLEEP_LONG, 1,
+      CONTAINER_LAUNCH_TIMEOUT)
+    expectLiveContainerCountReached(NESTED_NAME, NESTED_PREFIX + 
EXT_SLEEP_LONG, 1,
+      CONTAINER_LAUNCH_TIMEOUT)
+    expectLiveContainerCountReached(NESTED_NAME, NESTED_PREFIX + SLEEP_100, 0,
+      CONTAINER_LAUNCH_TIMEOUT)
+    expectLiveContainerCountReached(NESTED_NAME, NESTED_PREFIX + 
EXT_SLEEP_100, 0,
+      CONTAINER_LAUNCH_TIMEOUT)
   }
 }

Reply via email to