You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by al...@apache.org on 2016/06/20 21:34:46 UTC

[4/6] ambari git commit: AMBARI-12885. Dynamic stack extensions - install and upgrade support for custom services (Tim Thorpe via alejandro)

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionModule.java
new file mode 100644
index 0000000..e3f8d0d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionModule.java
@@ -0,0 +1,540 @@
+/**
+ * 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.ambari.server.stack;
+
+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 java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.state.ConfigHelper;
+import org.apache.ambari.server.state.ExtensionInfo;
+import org.apache.ambari.server.state.PropertyDependencyInfo;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.RepositoryInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.stack.ExtensionMetainfoXml;
+import org.apache.ambari.server.state.stack.RepositoryXml;
+import org.apache.ambari.server.state.stack.ServiceMetainfoXml;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Extension module which provides all functionality related to parsing and fully
+ * resolving extensions from the extension definition.
+ *
+ * An extension version is like a stack version but it contains custom services.  Linking an extension
+ * version to the current stack version allows the cluster to install the custom services contained in
+ * the extension version.
+ *
+ * <p>
+ * Each extension node is identified by name and version, contains service
+ * child nodes and may extend a single parent extension.
+ * </p>
+ *
+ * <p>
+ * Resolution of a extension is a depth first traversal up the inheritance chain where each extension node
+ * calls resolve on its parent before resolving itself.  After the parent resolve call returns, all
+ * ancestors in the inheritance tree are fully resolved.  The act of resolving the extension includes
+ * resolution of the services children of the extension as well as merging of other extension
+ * state with the fully resolved parent.
+ * </p>
+ *
+ * <p>
+ * Because a service may explicitly extend another service in a extension outside of the inheritance tree,
+ * service child node resolution involves a depth first resolution of the extension associated with the
+ * services explicit parent, if any.  This follows the same steps defined above fore extension node
+ * resolution.  After the services explicit parent is fully resolved, the services state is merged
+ * with it's parent.
+ * </p>
+ *
+ * <p>
+ * If a cycle in a extension definition is detected, an exception is thrown from the resolve call.
+ * </p>
+ *
+ */
+public class ExtensionModule extends BaseModule<ExtensionModule, ExtensionInfo> implements Validable {
+
+  /**
+   * Context which provides access to external functionality
+   */
+  private StackContext stackContext;
+
+  /**
+   * Map of child configuration modules keyed by configuration type
+   */
+  private Map<String, ConfigurationModule> configurationModules = new HashMap<String, ConfigurationModule>();
+
+  /**
+   * Map of child service modules keyed by service name
+   */
+  private Map<String, ServiceModule> serviceModules = new HashMap<String, ServiceModule>();
+
+  /**
+   * Corresponding ExtensionInfo instance
+   */
+  private ExtensionInfo extensionInfo;
+
+  /**
+   * Encapsulates IO operations on extension directory
+   */
+  private ExtensionDirectory extensionDirectory;
+
+  /**
+   * Extension id which is in the form extensionName:extensionVersion
+   */
+  private String id;
+
+  /**
+   * validity flag
+   */
+  protected boolean valid = true;
+
+  /**
+   * Logger
+   */
+  private final static Logger LOG = LoggerFactory.getLogger(ExtensionModule.class);
+
+  /**
+   * Constructor.
+   * @param extensionDirectory  represents extension directory
+   * @param extensionContext    general extension context
+   */
+  public ExtensionModule(ExtensionDirectory extensionDirectory, StackContext stackContext) {
+    this.extensionDirectory = extensionDirectory;
+    this.stackContext = stackContext;
+    this.extensionInfo = new ExtensionInfo();
+    populateExtensionInfo();
+  }
+
+  public Map<String, ServiceModule> getServiceModules() {
+	  return serviceModules;
+  }
+
+  /**
+   * Fully resolve the extension. See extension resolution description in the class documentation.
+   * If the extension has a parent, this extension will be merged against its fully resolved parent
+   * if one is specified. Merging applies to all extension state including child service and
+   * configuration modules.  Services may extend a service in another version in the
+   * same extension hierarchy or may explicitly extend a service in a different
+   * hierarchy.
+   *
+   * @param parentModule   not used.  Each extension determines its own parent since extensions don't
+   *                       have containing modules
+   * @param allStacks      all stacks modules contained in the stack definition
+   * @param commonServices all common services
+   * @param extensions     all extensions
+   *
+   * @throws AmbariException if an exception occurs during extension resolution
+   */
+  @Override
+  public void resolve(
+      ExtensionModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+    moduleState = ModuleState.VISITED;
+    checkExtensionName(allStacks);
+
+    String parentVersion = extensionInfo.getParentExtensionVersion();
+    mergeServicesWithExplicitParent(allStacks, commonServices, extensions);
+    // merge with parent version of same extension definition
+    if (parentVersion != null) {
+      mergeExtensionWithParent(parentVersion, allStacks, commonServices, extensions);
+    }
+    moduleState = ModuleState.RESOLVED;
+  }
+
+  @Override
+  public ExtensionInfo getModuleInfo() {
+    return extensionInfo;
+  }
+
+  @Override
+  public boolean isDeleted() {
+    return false;
+  }
+
+  @Override
+  public String getId() {
+    return id;
+  }
+
+  @Override
+  public void finalizeModule() {
+    finalizeChildModules(serviceModules.values());
+    finalizeChildModules(configurationModules.values());
+  }
+
+  /**
+   * Get the associated extension directory.
+   *
+   * @return associated extension directory
+   */
+  public ExtensionDirectory getExtensionDirectory() {
+    return extensionDirectory;
+  }
+
+  /**
+   * Merge the extension with its parent.
+   *
+   * @param allStacks      all stacks in stack definition
+   * @param commonServices all common services specified in the stack definition
+   * @param parentVersion  version of the extensions parent
+   *
+   * @throws AmbariException if an exception occurs merging with the parent
+   */
+  private void mergeExtensionWithParent(
+      String parentVersion, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+
+    String parentExtensionKey = extensionInfo.getName() + StackManager.PATH_DELIMITER + parentVersion;
+    ExtensionModule parentExtension = extensions.get(parentExtensionKey);
+
+    if (parentExtension == null) {
+      throw new AmbariException("Extension '" + extensionInfo.getName() + ":" + extensionInfo.getVersion() +
+          "' specifies a parent that doesn't exist");
+    }
+
+    resolveExtension(parentExtension, allStacks, commonServices, extensions);
+    /*mergeConfigurations(parentStack, allStacks, commonServices);
+    mergeRoleCommandOrder(parentStack);*/
+
+    /*if (extensionInfo.getStackHooksFolder() == null) {
+      extensionInfo.setStackHooksFolder(parentStack.getModuleInfo().getStackHooksFolder());
+    }
+
+    if (extensionInfo.getKerberosDescriptorFileLocation() == null) {
+      extensionInfo.setKerberosDescriptorFileLocation(parentStack.getModuleInfo().getKerberosDescriptorFileLocation());
+    }
+
+    if (extensionInfo.getWidgetsDescriptorFileLocation() == null) {
+      extensionInfo.setWidgetsDescriptorFileLocation(parentStack.getModuleInfo().getWidgetsDescriptorFileLocation());
+    }*/
+
+    mergeServicesWithParent(parentExtension, allStacks, commonServices, extensions);
+  }
+
+  /**
+   * Merge child services with parent extension.
+   *
+   * @param parentExtension    parent extension module
+   * @param allStacks          all stacks in stack definition
+   * @param commonServices     all common services
+   * @param extensions         all extensions
+   *
+   * @throws AmbariException if an exception occurs merging the child services with the parent extension
+   */
+  private void mergeServicesWithParent(
+      ExtensionModule parentExtension, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+    extensionInfo.getServices().clear();
+
+    LOG.info("***Merging extension services with parent: " + parentExtension.getId());
+
+    Collection<ServiceModule> mergedModules = mergeChildModules(
+        allStacks, commonServices, extensions, serviceModules, parentExtension.serviceModules);
+    for (ServiceModule module : mergedModules) {
+      serviceModules.put(module.getId(), module);
+      extensionInfo.getServices().add(module.getModuleInfo());
+    }
+  }
+
+  /**
+   * Merge services with their explicitly specified parent if one has been specified.
+   * @param allStacks      all stacks in stack definition
+   * @param commonServices all common services specified in the stack definition
+   *
+   * @throws AmbariException if an exception occurs while merging child services with their explicit parents
+   */
+  private void mergeServicesWithExplicitParent(
+        Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
+    for (ServiceModule service : serviceModules.values()) {
+      ServiceInfo serviceInfo = service.getModuleInfo();
+      String parent = serviceInfo.getParent();
+      if (parent != null) {
+        mergeServiceWithExplicitParent(service, parent, allStacks, commonServices, extensions);
+      }
+    }
+  }
+
+  /**
+   * Merge a service with its explicitly specified parent.
+   * @param service          the service to merge
+   * @param parent           the explicitly specified parent service
+   * @param allStacks        all stacks specified in the stack definition
+   * @param commonServices   all common services specified in the stack definition
+   *
+   * @throws AmbariException if an exception occurs merging a service with its explicit parent
+   */
+  private void mergeServiceWithExplicitParent(
+      ServiceModule service, String parent, Map<String, StackModule> allStacks,
+      Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+    if(isCommonServiceParent(parent)) {
+      LOG.info("merging with common service: " + service.getModuleInfo().getName());
+      mergeServiceWithCommonServiceParent(service, parent, allStacks, commonServices, extensions);
+      LOG.info("display name: " + service.getModuleInfo().getDisplayName());
+    } else {
+      throw new AmbariException("The service '" + service.getModuleInfo().getName() + "' in extension '" + extensionInfo.getName() + ":"
+          + extensionInfo.getVersion() + "' extends an invalid parent: '" + parent + "'");
+    }
+  }
+
+  /**
+   * @param allStacks        all stacks specified in the stack definition
+   *
+   * @throws AmbariException if the extension name is the same as any of the stacks
+   */
+  private void checkExtensionName(Map<String, StackModule> allStacks)
+      throws AmbariException {
+
+    String name = extensionInfo.getName();
+    for (StackModule stack : allStacks.values()) {
+      String stackName = stack.getModuleInfo().getName();
+      if (name.equals(stackName)) {
+        throw new AmbariException("The extension '" + name + "' has a name which matches a stack name");
+      }
+    }
+  }
+
+  /**
+   * Check if parent is common service
+   * @param parent  Parent string
+   * @return true: if parent is common service, false otherwise
+   */
+  private boolean isCommonServiceParent(String parent) {
+    return parent != null
+        && !parent.isEmpty()
+        && parent.split(StackManager.PATH_DELIMITER)[0].equalsIgnoreCase(StackManager.COMMON_SERVICES);
+  }
+
+  /**
+   * Merge a service with its explicitly specified common service as parent.
+   * Parent: common-services/<serviceName>/<serviceVersion>
+   * Common Services Lookup Key: <serviceName>/<serviceVersion>
+   * Example:
+   *  Parent: common-services/HDFS/2.1.0.2.0
+   *  Key: HDFS/2.1.0.2.0
+   *
+   * @param service          the service to merge
+   * @param parent           the explicitly specified common service as parent
+   * @param allStacks        all stacks specified in the stack definition
+   * @param commonServices   all common services specified in the stack definition
+   * @throws AmbariException
+   */
+  private void mergeServiceWithCommonServiceParent(
+      ServiceModule service, String parent, Map<String, StackModule> allStacks,
+      Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+    ServiceInfo serviceInfo = service.getModuleInfo();
+    String[] parentToks = parent.split(StackManager.PATH_DELIMITER);
+    if(parentToks.length != 3 || !parentToks[0].equalsIgnoreCase(StackManager.COMMON_SERVICES)) {
+      throw new AmbariException("The service '" + serviceInfo.getName() + "' in extension '" + extensionInfo.getName() + ":"
+          + extensionInfo.getVersion() + "' extends an invalid parent: '" + parent + "'");
+    }
+
+    String baseServiceKey = parentToks[1] + StackManager.PATH_DELIMITER + parentToks[2];
+    ServiceModule baseService = commonServices.get(baseServiceKey);
+    if (baseService == null) {
+      setValid(false);
+      extensionInfo.setValid(false);
+      String error = "The service '" + serviceInfo.getName() + "' in extension '" + extensionInfo.getName() + ":"
+          + extensionInfo.getVersion() + "' extends a non-existent service: '" + parent + "'";
+      addError(error);
+      extensionInfo.addError(error);
+    } else {
+      if (baseService.isValid()) {
+        service.resolveExplicit(baseService, allStacks, commonServices, extensions);
+      } else {
+        setValid(false);
+        extensionInfo.setValid(false);
+        addErrors(baseService.getErrors());
+        extensionInfo.addErrors(baseService.getErrors());
+      }
+    }
+  }
+
+  /**
+   * Populate the extension module and info from the extension definition.
+   */
+  private void populateExtensionInfo() {
+    extensionInfo.setName(extensionDirectory.getExtensionDirName());
+    extensionInfo.setVersion(extensionDirectory.getName());
+
+    id = String.format("%s:%s", extensionInfo.getName(), extensionInfo.getVersion());
+
+    LOG.debug("Adding new extension to known extensions"
+        + ", extensionName = " + extensionInfo.getName()
+        + ", extensionVersion = " + extensionInfo.getVersion());
+
+
+    //todo: give additional thought on handling missing metainfo.xml
+    ExtensionMetainfoXml emx = extensionDirectory.getMetaInfoFile();
+    if (emx != null) {
+      if (!emx.isValid()) {
+        extensionInfo.setValid(false);
+        extensionInfo.addErrors(emx.getErrors());
+      }
+      extensionInfo.setParentExtensionVersion(emx.getExtends());
+      extensionInfo.setStacks(emx.getStacks());
+      extensionInfo.setExtensions(emx.getExtensions());
+    }
+
+    try {
+      // Read the service for this extension
+      populateServices();
+      if (!extensionInfo.isValid()) {
+        setValid(false);
+        addErrors(extensionInfo.getErrors());
+      }
+
+      //todo: shouldn't blindly catch Exception, re-evaluate this.
+    } catch (Exception e) {
+      String error = "Exception caught while populating services for extension: " +
+          extensionInfo.getName() + "-" + extensionInfo.getVersion();
+      setValid(false);
+      extensionInfo.setValid(false);
+      addError(error);
+      extensionInfo.addError(error);
+      LOG.error(error);
+    }
+  }
+
+  /**
+   * Populate the child services.
+   */
+  private void populateServices()throws AmbariException {
+    for (ServiceDirectory serviceDir : extensionDirectory.getServiceDirectories()) {
+      populateService(serviceDir);
+    }
+  }
+
+  /**
+   * Populate a child service.
+   *
+   * @param serviceDirectory the child service directory
+   */
+  private void populateService(ServiceDirectory serviceDirectory)  {
+    Collection<ServiceModule> serviceModules = new ArrayList<ServiceModule>();
+    // unfortunately, we allow multiple services to be specified in the same metainfo.xml,
+    // so we can't move the unmarshal logic into ServiceModule
+    ServiceMetainfoXml metaInfoXml = serviceDirectory.getMetaInfoFile();
+    if (!metaInfoXml.isValid()){
+      extensionInfo.setValid(metaInfoXml.isValid());
+      setValid(metaInfoXml.isValid());
+      extensionInfo.addErrors(metaInfoXml.getErrors());
+      addErrors(metaInfoXml.getErrors());
+      return;
+    }
+    List<ServiceInfo> serviceInfos = metaInfoXml.getServices();
+
+    for (ServiceInfo serviceInfo : serviceInfos) {
+      ServiceModule serviceModule = new ServiceModule(stackContext, serviceInfo, serviceDirectory);
+      serviceModules.add(serviceModule);
+      if (!serviceModule.isValid()){
+        extensionInfo.setValid(false);
+        setValid(false);
+        extensionInfo.addErrors(serviceModule.getErrors());
+        addErrors(serviceModule.getErrors());
+      }
+    }
+    addServices(serviceModules);
+  }
+
+  /**
+   * Resolve another extension module.
+   *
+   * @param parentExtension    extension module to be resolved
+   * @param allStacks          all stack modules in stack definition
+   * @param commonServices     all common services specified in the stack definition
+   * @param extensions         all extensions
+   * @throws AmbariException if unable to resolve the extension
+   */
+  private void resolveExtension(
+          ExtensionModule parentExtension, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+          throws AmbariException {
+    if (parentExtension.getModuleState() == ModuleState.INIT) {
+	  parentExtension.resolve(null, allStacks, commonServices, extensions);
+    } else if (parentExtension.getModuleState() == ModuleState.VISITED) {
+      //todo: provide more information to user about cycle
+      throw new AmbariException("Cycle detected while parsing extension definition");
+    }
+    if (!parentExtension.isValid() || (parentExtension.getModuleInfo() != null && !parentExtension.getModuleInfo().isValid())) {
+      setValid(parentExtension.isValid());
+      extensionInfo.setValid(parentExtension.extensionInfo.isValid());
+      addErrors(parentExtension.getErrors());
+      extensionInfo.addErrors(parentExtension.getErrors());
+    }
+  }
+
+  /**
+   * Add a child service module to the extension.
+   *
+   * @param service  service module to add
+   */
+  private void addService(ServiceModule service) {
+    ServiceInfo serviceInfo = service.getModuleInfo();
+    Object previousValue = serviceModules.put(service.getId(), service);
+    if (previousValue == null) {
+      extensionInfo.getServices().add(serviceInfo);
+    }
+  }
+
+  /**
+   * Add child service modules to the extension.
+   *
+   * @param services  collection of service modules to add
+   */
+  private void addServices(Collection<ServiceModule> services) {
+    for (ServiceModule service : services) {
+      addService(service);
+    }
+  }
+
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  @Override
+  public void setValid(boolean valid) {
+    this.valid = valid;
+  }
+
+  private Set<String> errorSet = new HashSet<String>();
+
+  @Override
+  public Collection getErrors() {
+    return errorSet;
+  }
+
+  @Override
+  public void addError(String error) {
+    errorSet.add(error);
+  }
+
+  @Override
+  public void addErrors(Collection<String> errors) {
+    this.errorSet.addAll(errors);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/ModuleFileUnmarshaller.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ModuleFileUnmarshaller.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ModuleFileUnmarshaller.java
index 9e2f997..7d47339 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ModuleFileUnmarshaller.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ModuleFileUnmarshaller.java
@@ -20,6 +20,7 @@ package org.apache.ambari.server.stack;
 
 import org.apache.ambari.server.state.stack.ConfigUpgradePack;
 import org.apache.ambari.server.state.stack.ConfigurationXml;
+import org.apache.ambari.server.state.stack.ExtensionMetainfoXml;
 import org.apache.ambari.server.state.stack.RepositoryXml;
 import org.apache.ambari.server.state.stack.ServiceMetainfoXml;
 import org.apache.ambari.server.state.stack.StackMetainfoXml;
@@ -27,7 +28,9 @@ import org.apache.ambari.server.state.stack.UpgradePack;
 
 import javax.xml.bind.JAXBContext;
 import javax.xml.bind.JAXBException;
+import javax.xml.bind.Marshaller;
 import javax.xml.bind.Unmarshaller;
+
 import java.io.File;
 import java.util.HashMap;
 import java.util.Map;
@@ -72,6 +75,7 @@ class ModuleFileUnmarshaller {
       jaxbContexts.put(UpgradePack.class, ctx);
       jaxbContexts.put(ConfigUpgradePack.class, ctx);
       jaxbContexts.put(ServiceMetainfoXml.class, JAXBContext.newInstance(ServiceMetainfoXml.class));
+      jaxbContexts.put(ExtensionMetainfoXml.class, JAXBContext.newInstance(ExtensionMetainfoXml.class));
     } catch (JAXBException e) {
       throw new RuntimeException (e);
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/QuickLinksConfigurationModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/QuickLinksConfigurationModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/QuickLinksConfigurationModule.java
index 84da70e..ed8e8cf 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/QuickLinksConfigurationModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/QuickLinksConfigurationModule.java
@@ -83,7 +83,7 @@ public class QuickLinksConfigurationModule extends BaseModule<QuickLinksConfigur
   }
 
   @Override
-  public void resolve(QuickLinksConfigurationModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices) throws AmbariException {
+  public void resolve(QuickLinksConfigurationModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> allExtensions) throws AmbariException {
     QuickLinksConfigurationInfo parentModuleInfo = parent.getModuleInfo();
 
     if (parent.getModuleInfo() != null && !moduleInfo.isDeleted()) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
index 9ed2c24..e938e68 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceDirectory.java
@@ -22,11 +22,17 @@ import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.stack.ServiceMetainfoXml;
+import org.apache.ambari.server.state.stack.StackRoleCommandOrder;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.codehaus.jackson.type.TypeReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import javax.xml.bind.JAXBException;
+
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
 import java.util.HashMap;
 import java.util.Map;
 
@@ -60,6 +66,16 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
   private File kerberosDescriptorFile;
 
   /**
+   * RCO file
+   */
+  private File rcoFile;
+
+  /**
+   * role command order
+   */
+  private StackRoleCommandOrder roleCommandOrder;
+
+  /**
    * widgets descriptor file
    */
   private Map<String, File> widgetsDescriptorFileMap = new HashMap<String, File>();
@@ -119,33 +135,6 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
   public ServiceDirectory(String servicePath) throws AmbariException {
     super(servicePath);
     parsePath();
-
-    File af = new File(directory.getAbsolutePath()
-        + File.separator + AmbariMetaInfo.SERVICE_ALERT_FILE_NAME);
-    alertsFile = af.exists() ? af : null;
-
-    File kdf = new File(directory.getAbsolutePath()
-        + File.separator + AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME);
-    kerberosDescriptorFile = kdf.exists() ? kdf : null;
-
-    if (metaInfoXml.getServices() != null) {
-      for (ServiceInfo serviceInfo : metaInfoXml.getServices()) {
-        File mf = new File(directory.getAbsolutePath()
-                + File.separator + serviceInfo.getMetricsFileName());
-        metricsFileMap.put(serviceInfo.getName(), mf.exists() ? mf : null);
-
-        File wdf = new File(directory.getAbsolutePath()
-                + File.separator + serviceInfo.getWidgetsFileName());
-        widgetsDescriptorFileMap.put(serviceInfo.getName(), wdf.exists() ? wdf : null);
-      }
-    }
-
-    File advFile = new File(directory.getAbsolutePath()
-        + File.separator + AmbariMetaInfo.SERVICE_ADVISOR_FILE_NAME);
-    advisorFile = advFile.exists() ? advFile : null;
-
-    File themeFile = new File(directory.getAbsolutePath() + File.separator + AmbariMetaInfo.SERVICE_THEME_FILE_NAME);
-    this.themeFile = themeFile.exists() ? themeFile : null;
   }
 
   /**
@@ -237,9 +226,54 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
   }
 
   /**
+   * Obtain the object representation of the service role_command_order.json file
+   *
+   * @return object representation of the service role_command_order.json file
+   */
+  public StackRoleCommandOrder getRoleCommandOrder() {
+    return roleCommandOrder;
+  }
+
+  /**
    * Parse the service directory.
    */
-  protected abstract void parsePath() throws AmbariException;
+  protected void parsePath() throws AmbariException {
+    calculateDirectories();
+    parseMetaInfoFile();
+
+    File af = new File(directory, AmbariMetaInfo.SERVICE_ALERT_FILE_NAME);
+    alertsFile = af.exists() ? af : null;
+
+    File kdf = new File(directory, AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME);
+    kerberosDescriptorFile = kdf.exists() ? kdf : null;
+
+    File rco = new File(directory, AmbariMetaInfo.RCO_FILE_NAME);
+    if (rco.exists()) {
+      rcoFile = rco;
+      parseRoleCommandOrder();
+    }
+
+    if (metaInfoXml.getServices() != null) {
+      for (ServiceInfo serviceInfo : metaInfoXml.getServices()) {
+        File mf = new File(directory, serviceInfo.getMetricsFileName());
+        metricsFileMap.put(serviceInfo.getName(), mf.exists() ? mf : null);
+
+        File wdf = new File(directory, serviceInfo.getWidgetsFileName());
+        widgetsDescriptorFileMap.put(serviceInfo.getName(), wdf.exists() ? wdf : null);
+      }
+    }
+
+    File advFile = new File(directory, AmbariMetaInfo.SERVICE_ADVISOR_FILE_NAME);
+    advisorFile = advFile.exists() ? advFile : null;
+
+    File themeFile = new File(directory, AmbariMetaInfo.SERVICE_THEME_FILE_NAME);
+    this.themeFile = themeFile.exists() ? themeFile : null;
+  }
+
+  /**
+   * Calculate the service specific directories.
+   */
+  protected abstract void calculateDirectories();
 
   /**
    * Unmarshal the metainfo file into its object representation.
@@ -266,4 +300,28 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
     }
   }
 
+  /**
+   * Parse role command order file
+   */
+  private void parseRoleCommandOrder() {
+    if (rcoFile == null)
+      return;
+
+    try {
+      ObjectMapper mapper = new ObjectMapper();
+      TypeReference<Map<String, Object>> rcoElementTypeReference = new TypeReference<Map<String, Object>>() {};
+      HashMap<String, Object> result = mapper.readValue(rcoFile, rcoElementTypeReference);
+      LOG.info("Role command order info was loaded from file: {}", rcoFile.getAbsolutePath());
+
+      roleCommandOrder = new StackRoleCommandOrder(result);
+
+      if (LOG.isDebugEnabled() && rcoFile != null) {
+        LOG.debug("Role Command Order for " + rcoFile.getAbsolutePath());
+        roleCommandOrder.printRoleCommandOrder(LOG);
+      }
+    } catch (IOException e) {
+      LOG.error(String.format("Can not read role command order info %s", rcoFile.getAbsolutePath()), e);
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
index 17a2a93..bc94104 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ServiceModule.java
@@ -36,6 +36,8 @@ import org.apache.ambari.server.state.ThemeInfo;
 import org.apache.commons.lang.StringUtils;
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import javax.annotation.Nullable;
 
@@ -102,6 +104,11 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
   protected boolean valid = true;
 
   /**
+   * Logger
+   */
+  private final static Logger LOG = LoggerFactory.getLogger(ServiceModule.class);
+
+  /**
    * Constructor.
    *
    * @param stackContext      stack context which provides module access to external functionality
@@ -131,6 +138,7 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
     serviceInfo.setAlertsFile(serviceDirectory.getAlertsFile());
     serviceInfo.setKerberosDescriptorFile(serviceDirectory.getKerberosDescriptorFile());
     serviceInfo.setWidgetsDescriptorFile(serviceDirectory.getWidgetsDescriptorFile(serviceInfo.getName()));
+    serviceInfo.setRoleCommandOrder(serviceDirectory.getRoleCommandOrder());
     serviceInfo.setSchemaVersion(AmbariMetaInfo.SCHEMA_VERSION_2);
     serviceInfo.setServicePackageFolder(serviceDirectory.getPackageDir());
     serviceInfo.setServiceUpgradesFolder(serviceDirectory.getUpgradesDir());
@@ -152,25 +160,27 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
 
   @Override
   public void resolve(
-      ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
-    resolveInternal(parentModule, allStacks, commonServices, false);
+    resolveInternal(parentModule, allStacks, commonServices, extensions, false);
   }
 
   public void resolveExplicit(
-      ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
-    resolveInternal(parentModule, allStacks, commonServices, true);
+    resolveInternal(parentModule, allStacks, commonServices, extensions, true);
   }
 
   public void resolveInternal(
       ServiceModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices,
-      boolean resolveExplicit)
+      Map<String, ExtensionModule> extensions, boolean resolveExplicit)
       throws AmbariException {
     if (!serviceInfo.isValid() || !parentModule.isValid()) {
       return;
     }
 
+    LOG.info("Resolve service");
+
     // If resolving against parent stack service module (stack inheritance), do not merge if an
     // explicit parent is specified
     if(!StringUtils.isBlank(serviceInfo.getParent()) && !resolveExplicit) {
@@ -182,6 +192,7 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
     if (serviceInfo.getComment() == null) {
       serviceInfo.setComment(parent.getComment());
     }
+    LOG.info("Display name service/parent: " + serviceInfo.getDisplayName() + "/" + parent.getDisplayName());
     if (serviceInfo.getDisplayName() == null) {
       serviceInfo.setDisplayName(parent.getDisplayName());
     }
@@ -239,17 +250,19 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
       serviceInfo.setAdvisorName(parent.getAdvisorName());
     }
 
+    if (serviceInfo.getRoleCommandOrder() == null) {
+      serviceInfo.setRoleCommandOrder(parent.getRoleCommandOrder());
+    }
+
     mergeCustomCommands(parent.getCustomCommands(), serviceInfo.getCustomCommands());
     mergeConfigDependencies(parent);
-    mergeComponents(parentModule, allStacks, commonServices);
-    mergeConfigurations(parentModule, allStacks, commonServices);
-    mergeThemes(parentModule, allStacks, commonServices);
-    mergeQuickLinksConfigurations(parentModule, allStacks, commonServices);
+    mergeComponents(parentModule, allStacks, commonServices, extensions);
+    mergeConfigurations(parentModule, allStacks, commonServices, extensions);
+    mergeThemes(parentModule, allStacks, commonServices, extensions);
+    mergeQuickLinksConfigurations(parentModule, allStacks, commonServices, extensions);
     mergeExcludedConfigTypes(parent);
 
-
     mergeServiceProperties(parent.getServicePropertyList());
-
   }
 
   /**
@@ -296,7 +309,7 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
    *
    * @throws AmbariException
    */
-  public void resolveCommonService(Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+  public void resolveCommonService(Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     if(!isCommonService) {
       throw new AmbariException("Not a common service");
@@ -314,12 +327,12 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
         ServiceModule baseService = commonServices.get(baseServiceKey);
         ModuleState baseModuleState = baseService.getModuleState();
         if (baseModuleState == ModuleState.INIT) {
-          baseService.resolveCommonService(allStacks, commonServices);
+          baseService.resolveCommonService(allStacks, commonServices, extensions);
         } else if (baseModuleState == ModuleState.VISITED) {
           //todo: provide more information to user about cycle
           throw new AmbariException("Cycle detected while parsing common service");
         }
-        resolveExplicit(baseService, allStacks, commonServices);
+        resolveExplicit(baseService, allStacks, commonServices, extensions);
       } else {
         throw new AmbariException("Common service cannot inherit from a non common service");
       }
@@ -414,8 +427,8 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
    * Merge theme modules.
    */
   private void mergeThemes(ServiceModule parent, Map<String, StackModule> allStacks,
-                           Map<String, ServiceModule> commonServices) throws AmbariException {
-    Collection<ThemeModule> mergedModules = mergeChildModules(allStacks, commonServices, themeModules, parent.themeModules);
+                           Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
+    Collection<ThemeModule> mergedModules = mergeChildModules(allStacks, commonServices, extensions, themeModules, parent.themeModules);
 
     for (ThemeModule mergedModule : mergedModules) {
       themeModules.put(mergedModule.getId(), mergedModule);
@@ -448,8 +461,8 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
    * Merge theme modules.
    */
   private void mergeQuickLinksConfigurations(ServiceModule parent, Map<String, StackModule> allStacks,
-                           Map<String, ServiceModule> commonServices) throws AmbariException {
-    Collection<QuickLinksConfigurationModule> mergedModules = mergeChildModules(allStacks, commonServices, quickLinksConfigurationModules, parent.quickLinksConfigurationModules);
+                           Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
+    Collection<QuickLinksConfigurationModule> mergedModules = mergeChildModules(allStacks, commonServices, extensions, quickLinksConfigurationModules, parent.quickLinksConfigurationModules);
 
     for (QuickLinksConfigurationModule mergedModule : mergedModules) {
       quickLinksConfigurationModules.put(mergedModule.getId(), mergedModule);
@@ -514,13 +527,13 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
    * @param commonServices  common service modules
    */
   private void mergeConfigurations(
-      ServiceModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      ServiceModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     serviceInfo.getProperties().clear();
     serviceInfo.setAllConfigAttributes(new HashMap<String, Map<String, Map<String, String>>>());
 
     Collection<ConfigurationModule> mergedModules = mergeChildModules(
-        allStacks, commonServices, configurationModules, parent.configurationModules);
+        allStacks, commonServices, extensions, configurationModules, parent.configurationModules);
 
     for (ConfigurationModule module : mergedModules) {
       configurationModules.put(module.getId(), module);
@@ -540,11 +553,11 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
    * @param commonServices  common service modules
    */
   private void mergeComponents(
-      ServiceModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      ServiceModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     serviceInfo.getComponents().clear();
     Collection<ComponentModule> mergedModules = mergeChildModules(
-        allStacks, commonServices, componentModules, parent.componentModules);
+        allStacks, commonServices, extensions, componentModules, parent.componentModules);
     componentModules.clear();
     for (ComponentModule module : mergedModules) {
       componentModules.put(module.getId(), module);

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDefinitionModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDefinitionModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDefinitionModule.java
index b35afb2..0e834b8 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDefinitionModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDefinitionModule.java
@@ -37,10 +37,11 @@ public interface StackDefinitionModule <T, I> {
    * @param parent          the parent that this module will be merged with
    * @param allStacks       collection of all stack modules in the tree
    * @param commonServices  collection of all common service modules in the tree
+   * @param extensions  collection of all extension modules in the tree
    *
    * @throws AmbariException if resolution fails
    */
-  public void resolve(T parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices) throws AmbariException;
+  public void resolve(T parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException;
 
   /**
    * Obtain the associated module information.

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDirectory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDirectory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDirectory.java
index ee9e383..bfba021 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDirectory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackDirectory.java
@@ -272,7 +272,6 @@ public class StackDirectory extends StackDefinitionDirectory {
    *
    * @return object representation of the stack role_command_order.json file
    */
-
   public StackRoleCommandOrder getRoleCommandOrder() {
     return roleCommandOrder;
   }
@@ -509,7 +508,6 @@ public class StackDirectory extends StackDefinitionDirectory {
         result = new HashMap<String, Object>();
       }
       roleCommandOrder = new StackRoleCommandOrder(result);
-      parseRoleCommandOrdersForServices();
       if (LOG.isDebugEnabled()) {
         LOG.debug("Role Command Order for " + rcoFilePath);
         roleCommandOrder.printRoleCommandOrder(LOG);
@@ -518,34 +516,4 @@ public class StackDirectory extends StackDefinitionDirectory {
       LOG.error(String.format("Can not read role command order info %s", rcoFilePath), e);
     }
   }
-
-  private void parseRoleCommandOrdersForServices() {
-    if (rcoFilePath != null) {
-      File stack = new File(rcoFilePath).getParentFile();
-      File servicesDir = new File(stack, "services");
-      File[] services = servicesDir.listFiles();
-      for (File service : services) {
-        if (service.isDirectory()) {
-          File rcoFile = new File(service, ROLE_COMMAND_ORDER_FILE);
-          if (rcoFile.exists())
-            parseRoleCommandOrdersForService(rcoFile);
-        }
-      }
-    }
-  }
-
-  private void parseRoleCommandOrdersForService(File rcoFile) {
-    HashMap<String, Object> result = null;
-    ObjectMapper mapper = new ObjectMapper();
-    TypeReference<Map<String, Object>> rcoElementTypeReference = new TypeReference<Map<String, Object>>() {};
-    try {
-      result = mapper.readValue(rcoFile, rcoElementTypeReference);
-      LOG.info("Role command order info was loaded from file: {}", rcoFile.getAbsolutePath());
-      StackRoleCommandOrder serviceRoleCommandOrder = new StackRoleCommandOrder(result);
-      roleCommandOrder.merge(serviceRoleCommandOrder, true);
-    } catch (IOException e) {
-      LOG.error(String.format("Can not read role command order info %s", rcoFile), e);
-    }
-  }
-
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManager.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManager.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManager.java
index c224b56..6c0d5e4 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManager.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManager.java
@@ -22,6 +22,7 @@ import java.io.File;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
 
 import javax.annotation.Nullable;
@@ -35,9 +36,14 @@ import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.metadata.ActionMetadata;
+import org.apache.ambari.server.orm.dao.ExtensionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionLinkDAO;
 import org.apache.ambari.server.orm.dao.MetainfoDAO;
 import org.apache.ambari.server.orm.dao.StackDAO;
+import org.apache.ambari.server.orm.entities.ExtensionEntity;
+import org.apache.ambari.server.orm.entities.ExtensionLinkEntity;
 import org.apache.ambari.server.orm.entities.StackEntity;
+import org.apache.ambari.server.state.ExtensionInfo;
 import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.state.stack.OsFamily;
@@ -72,10 +78,19 @@ public class StackManager {
   public static final String COMMON_SERVICES = "common-services";
 
   /**
+   * Prefix used for extension services parent path string
+   */
+  public static final String EXTENSIONS = "extensions";
+
+  public static final String METAINFO_FILE_NAME = "metainfo.xml";
+
+  /**
    * Provides access to non-stack server functionality
    */
   private StackContext stackContext;
 
+  private File stackRoot;
+
   /**
    * Logger
    */
@@ -87,12 +102,19 @@ public class StackManager {
   private Map<String, StackInfo> stackMap = new HashMap<String, StackInfo>();
 
   /**
+   * Map of extension id to extension info
+   */
+  private Map<String, ExtensionInfo> extensionMap = new HashMap<String, ExtensionInfo>();
+
+  /**
    * Constructor. Initialize stack manager.
    *
    * @param stackRoot
    *          stack root directory
    * @param commonServicesRoot
    *          common services root directory
+   * @param extensionRoot
+   *          extensions root directory
    * @param osFamily
    *          the OS family read from resources
    * @param metaInfoDAO
@@ -101,6 +123,10 @@ public class StackManager {
    *          action meta data automatically injected
    * @param stackDao
    *          stack DAO automatically injected
+   * @param extensionDao
+   *          extension DAO automatically injected
+   * @param linkDao
+   *          extension link DAO automatically injected
    *
    * @throws AmbariException
    *           if an exception occurs while processing the stacks
@@ -108,9 +134,10 @@ public class StackManager {
   @Inject
   public StackManager(@Assisted("stackRoot") File stackRoot,
       @Assisted("commonServicesRoot") @Nullable File commonServicesRoot,
+      @Assisted("extensionRoot") @Nullable File extensionRoot,
       @Assisted OsFamily osFamily, @Assisted boolean validate,
-                      MetainfoDAO metaInfoDAO,
-      ActionMetadata actionMetadata, StackDAO stackDao)
+      MetainfoDAO metaInfoDAO, ActionMetadata actionMetadata, StackDAO stackDao,
+      ExtensionDAO extensionDao, ExtensionLinkDAO linkDao)
       throws AmbariException {
 
     LOG.info("Initializing the stack manager...");
@@ -118,22 +145,50 @@ public class StackManager {
     if (validate) {
       validateStackDirectory(stackRoot);
       validateCommonServicesDirectory(commonServicesRoot);
+      validateExtensionDirectory(extensionRoot);
     }
 
     stackMap = new HashMap<String, StackInfo>();
     stackContext = new StackContext(metaInfoDAO, actionMetadata, osFamily);
+    extensionMap = new HashMap<String, ExtensionInfo>();
 
     Map<String, ServiceModule> commonServiceModules = parseCommonServicesDirectory(commonServicesRoot);
     Map<String, StackModule> stackModules = parseStackDirectory(stackRoot);
+    LOG.info("About to parse extension directories");
+    Map<String, ExtensionModule> extensionModules = null;
+    extensionModules = parseExtensionDirectory(extensionRoot);
+
+    //Read the extension links from the DB
+    for (StackModule module : stackModules.values()) {
+      StackInfo stack = module.getModuleInfo();
+      List<ExtensionLinkEntity> entities = linkDao.findByStack(stack.getName(), stack.getVersion());
+      for (ExtensionLinkEntity entity : entities) {
+        String name = entity.getExtension().getExtensionName();
+        String version = entity.getExtension().getExtensionVersion();
+        String key = name + StackManager.PATH_DELIMITER + version;
+        ExtensionModule extensionModule = extensionModules.get(key);
+        if (extensionModule != null) {
+          LOG.info("Adding extension to stack/version: " + stack.getName() + "/" + stack.getVersion() +
+                   " extension/version: " + name + "/" + version);
+          //Add the extension to the stack
+          module.getExtensionModules().put(key, extensionModule);
+        }
+      }
+    }
 
-    fullyResolveCommonServices(stackModules, commonServiceModules);
-    fullyResolveStacks(stackModules, commonServiceModules);
+    fullyResolveCommonServices(stackModules, commonServiceModules, extensionModules);
+    fullyResolveExtensions(stackModules, commonServiceModules, extensionModules);
+    fullyResolveStacks(stackModules, commonServiceModules, extensionModules);
 
+    populateDB(stackDao, extensionDao);
+  }
+
+  private void populateDB(StackDAO stackDao, ExtensionDAO extensionDao) throws AmbariException {
     // for every stack read in, ensure that we have a database entry for it;
     // don't put try/catch logic around this since a failure here will
     // cause other things to break down the road
     Collection<StackInfo> stacks = getStacks();
-    for( StackInfo stack : stacks ){
+    for(StackInfo stack : stacks){
       String stackName = stack.getName();
       String stackVersion = stack.getVersion();
 
@@ -147,6 +202,25 @@ public class StackManager {
         stackDao.create(stackEntity);
       }
     }
+
+    // for every extension read in, ensure that we have a database entry for it;
+    // don't put try/catch logic around this since a failure here will
+    // cause other things to break down the road
+    Collection<ExtensionInfo> extensions = getExtensions();
+    for(ExtensionInfo extension : extensions){
+      String extensionName = extension.getName();
+      String extensionVersion = extension.getVersion();
+
+      if (extensionDao.find(extensionName, extensionVersion) == null) {
+        LOG.info("Adding extension {}-{} to the database", extensionName, extensionVersion);
+
+        ExtensionEntity extensionEntity = new ExtensionEntity();
+        extensionEntity.setExtensionName(extensionName);
+        extensionEntity.setExtensionVersion(extensionVersion);
+
+        extensionDao.create(extensionEntity);
+      }
+    }
   }
 
   /**
@@ -188,6 +262,44 @@ public class StackManager {
   }
 
   /**
+   * Obtain the extension info specified by name and version.
+   *
+   * @param name     name of the extension
+   * @param version  version of the extension
+   * @return The extension corresponding to the specified name and version.
+   *         If no matching stack exists, null is returned.
+   */
+  public ExtensionInfo getExtension(String name, String version) {
+    return extensionMap.get(name + StackManager.PATH_DELIMITER + version);
+  }
+
+  /**
+   * Obtain all extensions for the given name.
+   *
+   * @param name  extension name
+   * @return A collection of all extensions with the given name.
+   *         If no extensions match the specified name, an empty collection is returned.
+   */
+  public Collection<ExtensionInfo> getExtensions(String name) {
+    Collection<ExtensionInfo> extensions = new HashSet<ExtensionInfo>();
+    for (ExtensionInfo extension: extensionMap.values()) {
+      if (extension.getName().equals(name)) {
+	  extensions.add(extension);
+      }
+    }
+    return extensions;
+  }
+
+  /**
+   * Obtain all extensions.
+   *
+   * @return collection of all extensions
+   */
+  public Collection<ExtensionInfo> getExtensions() {
+    return extensionMap.values();
+  }
+
+  /**
    * Determine if all tasks which update stack repo urls have completed.
    *
    * @return true if all of the repo update tasks have completed; false otherwise
@@ -204,12 +316,12 @@ public class StackManager {
    * @throws AmbariException if unable to resolve all stacks
    */
   private void fullyResolveStacks(
-      Map<String, StackModule> stackModules, Map<String, ServiceModule> commonServiceModules)
+      Map<String, StackModule> stackModules, Map<String, ServiceModule> commonServiceModules, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     // Resolve all stacks without finalizing the stacks.
     for (StackModule stack : stackModules.values()) {
       if (stack.getModuleState() == ModuleState.INIT) {
-        stack.resolve(null, stackModules, commonServiceModules);
+        stack.resolve(null, stackModules, commonServiceModules, extensions);
       }
     }
     // Finalize the common services and stacks to remove sub-modules marked for deletion.
@@ -219,6 +331,9 @@ public class StackManager {
     for(ServiceModule commonService : commonServiceModules.values()) {
       commonService.finalizeModule();
     }
+    for (ExtensionModule extension : extensions.values()) {
+      extension.finalizeModule();
+    }
     for (StackModule stack : stackModules.values()) {
       stack.finalizeModule();
     }
@@ -234,11 +349,29 @@ public class StackManager {
    * @throws AmbariException if unable to resolve all common services
    */
   private void fullyResolveCommonServices(
-      Map<String, StackModule> stackModules, Map<String, ServiceModule> commonServiceModules)
+      Map<String, StackModule> stackModules, Map<String, ServiceModule> commonServiceModules, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     for(ServiceModule commonService : commonServiceModules.values()) {
       if (commonService.getModuleState() == ModuleState.INIT) {
-        commonService.resolveCommonService(stackModules, commonServiceModules);
+        commonService.resolveCommonService(stackModules, commonServiceModules, extensions);
+      }
+    }
+  }
+
+  /**
+   * Fully resolve extensions.
+   *
+   * @param extensionModules      map of extension id which contains name and version to extension module.
+   * @param stackModules          map of stack id which contains name and version to stack module.
+   * @param commonServiceModules  map of common service id which contains name and version to common service module.
+   * @throws AmbariException if unable to resolve all extensions
+   */
+  private void fullyResolveExtensions(Map<String, StackModule> stackModules, Map<String, ServiceModule> commonServiceModules,
+      Map<String, ExtensionModule> extensionModules)
+      throws AmbariException {
+    for(ExtensionModule extensionModule : extensionModules.values()) {
+      if (extensionModule.getModuleState() == ModuleState.INIT) {
+        extensionModule.resolve(null, stackModules, commonServiceModules, extensionModules);
       }
     }
   }
@@ -321,6 +454,34 @@ public class StackManager {
     }
   }
 
+
+
+  /**
+   * Validate that the specified extension root is a valid directory.
+   *
+   * @param extensionRoot  the extension root directory to validate
+   * @throws AmbariException if the specified extension root directory is invalid
+   */
+  private void validateExtensionDirectory(File extensionRoot) throws AmbariException {
+    LOG.info("Validating extension directory {} ...", extensionRoot);
+
+    if (extensionRoot == null)
+	return;
+
+    String extensionRootAbsPath = extensionRoot.getAbsolutePath();
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Loading extension information"
+          + ", extensionRoot = " + extensionRootAbsPath);
+    }
+
+    //For backwards compatibility extension directory may not exist
+    if (extensionRoot.exists() && !extensionRoot.isDirectory()) {
+      throw new AmbariException("" + Configuration.METADATA_DIR_PATH
+          + " should be a directory"
+          + ", extensionRoot = " + extensionRootAbsPath);
+    }
+  }
+
   /**
    * Parse the specified common services root directory
    *
@@ -399,4 +560,48 @@ public class StackManager {
     }
     return stackModules;
   }
+
+  public void linkStackToExtension(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+  }
+
+  public void unlinkStackAndExtension(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+  }
+
+  /**
+   * Parse the specified extension root directory
+   *
+   * @param extensionRoot  the extension root directory to parse
+   * @return map of extension id which contains name and version to extension module.
+   * @throws AmbariException if unable to parse all extensions
+   */
+  private Map<String, ExtensionModule> parseExtensionDirectory(File extensionRoot) throws AmbariException {
+    Map<String, ExtensionModule> extensionModules = new HashMap<String, ExtensionModule>();
+    if (extensionRoot == null || !extensionRoot.exists())
+      return extensionModules;
+
+    File[] extensionFiles = extensionRoot.listFiles(AmbariMetaInfo.FILENAME_FILTER);
+    for (File extensionNameFolder : extensionFiles) {
+      if (extensionNameFolder.isFile()) {
+        continue;
+      }
+      for (File extensionVersionFolder : extensionNameFolder.listFiles(AmbariMetaInfo.FILENAME_FILTER)) {
+        if (extensionVersionFolder.isFile()) {
+          continue;
+        }
+        String extensionName = extensionNameFolder.getName();
+        String extensionVersion = extensionVersionFolder.getName();
+
+        ExtensionModule extensionModule = new ExtensionModule(new ExtensionDirectory(extensionVersionFolder.getPath()), stackContext);
+        String extensionKey = extensionName + StackManager.PATH_DELIMITER + extensionVersion;
+        extensionModules.put(extensionKey, extensionModule);
+        extensionMap.put(extensionKey, extensionModule.getModuleInfo());
+      }
+    }
+
+    if (stackMap.isEmpty()) {
+      throw new AmbariException("Unable to find extension definitions under " +
+          "extensionRoot = " + extensionRoot.getAbsolutePath());
+    }
+    return extensionModules;
+  }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManagerFactory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManagerFactory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManagerFactory.java
index 105b715..84e7e0b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManagerFactory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackManagerFactory.java
@@ -38,11 +38,14 @@ public interface StackManagerFactory {
    * @param commonServicesRoot
    *          the root of the common services from which other stack services
    *          are extended (not {@code null}).
+   * @param extensionRoot
+   *          the root of the extensions (not {@code null}).
    * @param osFamily
    *          the list of all parsed OS families (not {@code null}).
    * @return a stack manager instance which contains all parsed stacks.
    */
   StackManager create(@Assisted("stackRoot") File stackRoot,
       @Nullable @Assisted("commonServicesRoot") File commonServicesRoot,
+      @Assisted("extensionRoot") @Nullable File extensionRoot,
       OsFamily osFamily, boolean validate);
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/StackModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackModule.java
index d819a52..0606f2a 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackModule.java
@@ -36,6 +36,7 @@ import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.stack.StackDefinitionDirectory;
 import org.apache.ambari.server.state.ConfigHelper;
+import org.apache.ambari.server.state.ExtensionInfo;
 import org.apache.ambari.server.state.PropertyDependencyInfo;
 import org.apache.ambari.server.state.PropertyInfo;
 import org.apache.ambari.server.state.RepositoryInfo;
@@ -107,6 +108,11 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
   private Map<String, ServiceModule> serviceModules = new HashMap<String, ServiceModule>();
 
   /**
+   * Map of linked extension modules keyed by extension name + version
+   */
+  private Map<String, ExtensionModule> extensionModules = new HashMap<String, ExtensionModule>();
+
+  /**
    * Corresponding StackInfo instance
    */
   private StackInfo stackInfo;
@@ -148,6 +154,14 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
     populateStackInfo();
   }
 
+  public Map<String, ServiceModule> getServiceModules() {
+	  return serviceModules;
+  }
+
+  public Map<String, ExtensionModule> getExtensionModules() {
+	  return extensionModules;
+  }
+
   /**
    * Fully resolve the stack. See stack resolution description in the class documentation.
    * If the stack has a parent, this stack will be merged against its fully resolved parent
@@ -160,20 +174,34 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    *                       have containing modules
    * @param allStacks      all stacks modules contained in the stack definition
    * @param commonServices all common services specified in the stack definition
+   * @param extensions     all extension modules contained in the stack definition
    *
    * @throws AmbariException if an exception occurs during stack resolution
    */
   @Override
   public void resolve(
-      StackModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      StackModule parentModule, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     moduleState = ModuleState.VISITED;
+    LOG.info("Resolve: " + stackInfo.getName() + ":" + stackInfo.getVersion());
     String parentVersion = stackInfo.getParentStackVersion();
-    mergeServicesWithExplicitParent(allStacks, commonServices);
+    mergeServicesWithExplicitParent(allStacks, commonServices, extensions);
+    addExtensionServices();
+
     // merge with parent version of same stack definition
     if (parentVersion != null) {
-      mergeStackWithParent(parentVersion, allStacks, commonServices);
+      mergeStackWithParent(parentVersion, allStacks, commonServices, extensions);
     }
+    for (ExtensionInfo extension : stackInfo.getExtensions()) {
+      String extensionKey = extension.getName() + StackManager.PATH_DELIMITER + extension.getVersion();
+      ExtensionModule extensionModule = extensions.get(extensionKey);
+      if (extensionModule == null) {
+        throw new AmbariException("Extension '" + stackInfo.getName() + ":" + stackInfo.getVersion() +
+                        "' specifies an extension " + extensionKey + " that doesn't exist");
+      }
+      mergeStackWithExtension(extensionModule, allStacks, commonServices, extensions);
+    }
+
     processUpgradePacks();
     processRepositories();
     processPropertyDependencies();
@@ -199,6 +227,12 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
   public void finalizeModule() {
     finalizeChildModules(serviceModules.values());
     finalizeChildModules(configurationModules.values());
+
+    // This needs to be merged during the finalize to avoid the RCO from services being inherited by the children stacks
+    // The RCOs from a service should only be inherited through the service.
+    for (ServiceModule module : serviceModules.values()) {
+      mergeRoleCommandOrder(module);
+    }
   }
 
   /**
@@ -220,7 +254,7 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    * @throws AmbariException if an exception occurs merging with the parent
    */
   private void mergeStackWithParent(
-      String parentVersion, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      String parentVersion, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
 
     String parentStackKey = stackInfo.getName() + StackManager.PATH_DELIMITER + parentVersion;
@@ -231,8 +265,8 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
           "' specifies a parent that doesn't exist");
     }
 
-    resolveStack(parentStack, allStacks, commonServices);
-    mergeConfigurations(parentStack, allStacks, commonServices);
+    resolveStack(parentStack, allStacks, commonServices, extensions);
+    mergeConfigurations(parentStack, allStacks, commonServices, extensions);
     mergeRoleCommandOrder(parentStack);
 
     if (stackInfo.getStackHooksFolder() == null) {
@@ -248,7 +282,22 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
       stackInfo.setWidgetsDescriptorFileLocation(parentStack.getModuleInfo().getWidgetsDescriptorFileLocation());
     }
 
-    mergeServicesWithParent(parentStack, allStacks, commonServices);
+    mergeServicesWithParent(parentStack, allStacks, commonServices, extensions);
+  }
+
+  /**
+   * Merge the stack with one of its linked extensions.
+   *
+   * @param allStacks      all stacks in stack definition
+   * @param commonServices all common services specified in the stack definition
+   * @param parentVersion  version of the stacks parent
+   *
+   * @throws AmbariException if an exception occurs merging with the parent
+   */
+  private void mergeStackWithExtension(
+		  ExtensionModule extension, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+
   }
 
   /**
@@ -261,11 +310,11 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    * @throws AmbariException if an exception occurs merging the child services with the parent stack
    */
   private void mergeServicesWithParent(
-      StackModule parentStack, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      StackModule parentStack, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     stackInfo.getServices().clear();
     Collection<ServiceModule> mergedModules = mergeChildModules(
-        allStacks, commonServices, serviceModules, parentStack.serviceModules);
+        allStacks, commonServices, extensions, serviceModules, parentStack.serviceModules);
     for (ServiceModule module : mergedModules) {
       serviceModules.put(module.getId(), module);
       stackInfo.getServices().add(module.getModuleInfo());
@@ -280,12 +329,12 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    * @throws AmbariException if an exception occurs while merging child services with their explicit parents
    */
   private void mergeServicesWithExplicitParent(
-      Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices) throws AmbariException {
+      Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
     for (ServiceModule service : serviceModules.values()) {
       ServiceInfo serviceInfo = service.getModuleInfo();
       String parent = serviceInfo.getParent();
       if (parent != null) {
-        mergeServiceWithExplicitParent(service, parent, allStacks, commonServices);
+        mergeServiceWithExplicitParent(service, parent, allStacks, commonServices, extensions);
       }
     }
   }
@@ -301,12 +350,16 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    */
   private void mergeServiceWithExplicitParent(
       ServiceModule service, String parent, Map<String, StackModule> allStacks,
-      Map<String, ServiceModule> commonServices)
+      Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
+
+    LOG.info("mergeServiceWithExplicitParent" + parent);
     if(isCommonServiceParent(parent)) {
-      mergeServiceWithCommonServiceParent(service, parent, allStacks,commonServices);
+      mergeServiceWithCommonServiceParent(service, parent, allStacks, commonServices, extensions);
+    } else if(isExtensionServiceParent(parent)) {
+      mergeServiceWithExtensionServiceParent(service, parent, allStacks, commonServices, extensions);
     } else {
-      mergeServiceWithStackServiceParent(service, parent, allStacks, commonServices);
+      mergeServiceWithStackServiceParent(service, parent, allStacks, commonServices, extensions);
     }
   }
 
@@ -322,6 +375,25 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
   }
 
   /**
+   * Check if parent is extension service
+   * @param parent  Parent string
+   * @return true: if parent is extension service, false otherwise
+   */
+  private boolean isExtensionServiceParent(String parent) {
+    return parent != null
+        && !parent.isEmpty()
+        && parent.split(StackManager.PATH_DELIMITER)[0].equalsIgnoreCase(StackManager.EXTENSIONS);
+  }
+
+  private void addExtensionServices() throws AmbariException {
+    for (ExtensionModule extension : extensionModules.values()) {
+      stackInfo.getExtensions().add(extension.getModuleInfo());
+      Collection<ServiceModule> services = extension.getServiceModules().values();
+      addServices(services);
+    }
+  }
+
+  /**
    * Merge a service with its explicitly specified common service as parent.
    * Parent: common-services/<serviceName>/<serviceVersion>
    * Common Services Lookup Key: <serviceName>/<serviceVersion>
@@ -337,7 +409,7 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    */
   private void mergeServiceWithCommonServiceParent(
       ServiceModule service, String parent, Map<String, StackModule> allStacks,
-      Map<String, ServiceModule> commonServices)
+      Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     ServiceInfo serviceInfo = service.getModuleInfo();
     String[] parentToks = parent.split(StackManager.PATH_DELIMITER);
@@ -357,7 +429,7 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
       stackInfo.addError(error);
     } else {
       if (baseService.isValid()) {
-        service.resolveExplicit(baseService, allStacks, commonServices);
+        service.resolveExplicit(baseService, allStacks, commonServices, extensions);
       } else {
         setValid(false);
         stackInfo.setValid(false);
@@ -368,6 +440,50 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
   }
 
   /**
+   * Merge a service with its explicitly specified extension service as parent.
+   * Parent: extensions/<extensionName>/<extensionVersion>/<serviceName>
+   * Example:
+   *  Parent: extensions/EXT_TEST/1.0/CUSTOM_SERVICE
+   *
+   * @param service          the service to merge
+   * @param parent           the explicitly specified extension as parent
+   * @param allStacks        all stacks specified in the stack definition
+   * @param commonServices   all common services
+   * @param extensions       all extensions
+   * @throws AmbariException
+   */
+  private void mergeServiceWithExtensionServiceParent(
+      ServiceModule service, String parent, Map<String, StackModule> allStacks,
+      Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+    ServiceInfo serviceInfo = service.getModuleInfo();
+    String[] parentToks = parent.split(StackManager.PATH_DELIMITER);
+    if(parentToks.length != 4 || !parentToks[0].equalsIgnoreCase(StackManager.EXTENSIONS)) {
+      throw new AmbariException("The service '" + serviceInfo.getName() + "' in stack '" + stackInfo.getName() + ":"
+          + stackInfo.getVersion() + "' extends an invalid parent: '" + parent + "'");
+    }
+
+    String extensionKey = parentToks[1] + StackManager.PATH_DELIMITER + parentToks[2];
+    ExtensionModule extension = extensions.get(extensionKey);
+
+    if (extension == null || !extension.isValid()) {
+      setValid(false);
+      addError("The service '" + serviceInfo.getName() + "' in stack '" + stackInfo.getName() + ":"
+          + stackInfo.getVersion() + "' extends a non-existent service: '" + parent + "'");
+    } else {
+      resolveExtension(extension, allStacks, commonServices, extensions);
+      ServiceModule parentService = extension.getServiceModules().get(parentToks[3]);
+      if (parentService == null || !parentService.isValid()) {
+        setValid(false);
+        addError("The service '" + serviceInfo.getName() + "' in stack '" + stackInfo.getName() + ":"
+            + stackInfo.getVersion() + "' extends a non-existent service: '" + parent + "'");
+      }
+      else
+        service.resolve(parentService, allStacks, commonServices, extensions);
+    }
+  }
+
+  /**
    * Merge a service with its explicitly specified stack service as parent.
    * Parent: <stackName>/<stackVersion>/<serviceName>
    * Stack Lookup Key: <stackName>/<stackVersion>
@@ -378,16 +494,17 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    * @param service          the service to merge
    * @param parent           the explicitly specified stack service as parent
    * @param allStacks        all stacks specified in the stack definition
-   * @param commonServices   all common services specified in the stack definition
+   * @param commonServices   all common services
+   * @param extensions       all extensions
    * @throws AmbariException
    */
   private void mergeServiceWithStackServiceParent(
       ServiceModule service, String parent, Map<String, StackModule> allStacks,
-      Map<String, ServiceModule> commonServices)
+      Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     ServiceInfo serviceInfo = service.getModuleInfo();
     String[] parentToks = parent.split(StackManager.PATH_DELIMITER);
-    if(parentToks.length != 3 || parentToks[0].equalsIgnoreCase(StackManager.COMMON_SERVICES)) {
+    if(parentToks.length != 3 || parentToks[0].equalsIgnoreCase(StackManager.EXTENSIONS) || parentToks[0].equalsIgnoreCase(StackManager.COMMON_SERVICES)) {
       throw new AmbariException("The service '" + serviceInfo.getName() + "' in stack '" + stackInfo.getName() + ":"
           + stackInfo.getVersion() + "' extends an invalid parent: '" + parent + "'");
     }
@@ -399,14 +516,14 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
           + stackInfo.getVersion() + "' extends a service in a non-existent stack: '" + baseStackKey + "'");
     }
 
-    resolveStack(baseStack, allStacks, commonServices);
+    resolveStack(baseStack, allStacks, commonServices, extensions);
 
     ServiceModule baseService = baseStack.serviceModules.get(parentToks[2]);
     if (baseService == null) {
       throw new AmbariException("The service '" + serviceInfo.getName() + "' in stack '" + stackInfo.getName() + ":"
           + stackInfo.getVersion() + "' extends a non-existent service: '" + parent + "'");
       }
-    service.resolveExplicit(baseService, allStacks, commonServices);
+    service.resolveExplicit(baseService, allStacks, commonServices, extensions);
   }
 
   /**
@@ -422,8 +539,7 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
         + ", stackName = " + stackInfo.getName()
         + ", stackVersion = " + stackInfo.getVersion());
 
-
-    //odo: give additional thought on handling missing metainfo.xml
+    //todo: give additional thought on handling missing metainfo.xml
     StackMetainfoXml smx = stackDirectory.getMetaInfoFile();
     if (smx != null) {
       if (!smx.isValid()) {
@@ -542,13 +658,13 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    * @param commonServices all common services specified in the stack definition
    */
   private void mergeConfigurations(
-      StackModule parent, Map<String,StackModule> allStacks, Map<String, ServiceModule> commonServices)
+      StackModule parent, Map<String,StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
       throws AmbariException {
     stackInfo.getProperties().clear();
     stackInfo.setAllConfigAttributes(new HashMap<String, Map<String, Map<String, String>>>());
 
     Collection<ConfigurationModule> mergedModules = mergeChildModules(
-        allStacks, commonServices, configurationModules, parent.configurationModules);
+        allStacks, commonServices, extensions, configurationModules, parent.configurationModules);
     for (ConfigurationModule module : mergedModules) {
       configurationModules.put(module.getId(), module);
       stackInfo.getProperties().addAll(module.getModuleInfo().getProperties());
@@ -565,10 +681,10 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    * @throws AmbariException if unable to resolve the stack
    */
   private void resolveStack(
-          StackModule stackToBeResolved, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices)
+          StackModule stackToBeResolved, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
           throws AmbariException {
     if (stackToBeResolved.getModuleState() == ModuleState.INIT) {
-      stackToBeResolved.resolve(null, allStacks, commonServices);
+      stackToBeResolved.resolve(null, allStacks, commonServices, extensions);
     } else if (stackToBeResolved.getModuleState() == ModuleState.VISITED) {
       //todo: provide more information to user about cycle
       throw new AmbariException("Cycle detected while parsing stack definition");
@@ -582,6 +698,30 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
   }
 
   /**
+   * Resolve an extension module.
+   *
+   * @param extension              extension module to be resolved
+   * @param allStacks              all stack modules in stack definition
+   * @param commonServices         all common services
+   * @param extensions             all extensions
+   * @throws AmbariException if unable to resolve the stack
+   */
+  private void resolveExtension(
+          ExtensionModule extension, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+          throws AmbariException {
+    if (extension.getModuleState() == ModuleState.INIT) {
+	  extension.resolve(null, allStacks, commonServices, extensions);
+    } else if (extension.getModuleState() == ModuleState.VISITED) {
+      //todo: provide more information to user about cycle
+      throw new AmbariException("Cycle detected while parsing extension definition");
+    }
+    if (!extension.isValid() || (extension.getModuleInfo() != null && !extension.getModuleInfo().isValid())) {
+      setValid(false);
+      addError("Stack includes an invalid extension: " + extension.getModuleInfo().getName());
+    }
+  }
+
+  /**
    * Add a child service module to the stack.
    *
    * @param service  service module to add
@@ -943,6 +1083,23 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
     stackInfo.getRoleCommandOrder().merge(parentStack.stackInfo.getRoleCommandOrder());
   }
 
+  /**
+   * Merge role command order with the service
+   *
+   * @param service    service
+   */
+  private void mergeRoleCommandOrder(ServiceModule service) {
+    if (service.getModuleInfo().getRoleCommandOrder() == null)
+      return;
+
+    stackInfo.getRoleCommandOrder().merge(service.getModuleInfo().getRoleCommandOrder(), true);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Role Command Order for " + stackInfo.getName() + "-" + stackInfo.getVersion() +
+        " service " + service.getModuleInfo().getName());
+      stackInfo.getRoleCommandOrder().printRoleCommandOrder(LOG);
+    }
+  }
+
   @Override
   public boolean isValid() {
     return valid;

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/StackServiceDirectory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackServiceDirectory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackServiceDirectory.java
index 68c1dd6..7bcd08b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackServiceDirectory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackServiceDirectory.java
@@ -67,14 +67,13 @@ public class StackServiceDirectory extends ServiceDirectory {
 
   @Override
   /**
-   * Parse stack service directory.
+   * Calculate the stack service directories.
    * packageDir Format: stacks/<stackName>/<stackVersion>/services/<serviceName>/package
    * Example:
    *  directory: "/var/lib/ambari-server/resources/stacks/HDP/2.0.6/services/HDFS"
    *  packageDir: "stacks/HDP/2.0.6/services/HDFS/package"
-   * @throws AmbariException if unable to parse the service directory
    */
-  protected void parsePath() throws AmbariException {
+  protected void calculateDirectories() {
     File serviceDir = new File(getAbsolutePath());
     File stackVersionDir = serviceDir.getParentFile().getParentFile();
     File stackDir = stackVersionDir.getParentFile();
@@ -116,6 +115,5 @@ public class StackServiceDirectory extends ServiceDirectory {
       LOG.debug("Service upgrades folder %s for service %s for stack %s does not exist.",
               absUpgradesDir, serviceDir.getName(), stackId);
     }
-    parseMetaInfoFile();
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/300a7e21/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java
index d8f50c6..30c1f1b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ThemeModule.java
@@ -82,7 +82,8 @@ public class ThemeModule extends BaseModule<ThemeModule, ThemeInfo> implements V
   }
 
   @Override
-  public void resolve(ThemeModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices) throws AmbariException {
+  public void resolve(ThemeModule parent, Map<String, StackModule> allStacks,
+		  Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
     ThemeInfo parentModuleInfo = parent.getModuleInfo();
 
     if (parent.getModuleInfo() != null && !moduleInfo.isDeleted()) {