You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by di...@apache.org on 2016/01/11 20:15:41 UTC

[05/13] ambari git commit: AMBARI-12885: Dynamic stack extensions - install and upgrade support for custom services (tthorpe via dili)

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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 9cfeffe..0403f46 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
@@ -20,6 +20,7 @@ package org.apache.ambari.server.stack;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.state.stack.ExtensionsXml;
 import org.apache.ambari.server.state.stack.RepositoryXml;
 import org.apache.ambari.server.state.stack.StackMetainfoXml;
 import org.apache.ambari.server.state.stack.StackRoleCommandOrder;
@@ -36,6 +37,7 @@ import javax.xml.bind.JAXBException;
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.HashMap;
@@ -137,6 +139,11 @@ public class StackDirectory extends StackDefinitionDirectory {
   private static final String STACK_METAINFO_FILE_NAME = "metainfo.xml";
 
   /**
+   * extensions file name
+   */
+  private static final String EXTENSIONS_XML_FILE_NAME = "extensions.xml";
+
+  /**
    * upgrades directory name
    */
   private static final String UPGRADE_PACK_FOLDER_NAME = "upgrades";
@@ -274,7 +281,6 @@ public class StackDirectory extends StackDefinitionDirectory {
    *
    * @return object representation of the stack role_command_order.json file
    */
-
   public StackRoleCommandOrder getRoleCommandOrder() {
     return roleCommandOrder;
   }
@@ -493,7 +499,7 @@ public class StackDirectory extends StackDefinitionDirectory {
             ClassLoader.getSystemResource(ROLE_COMMAND_ORDER_FILE));
       }
       roleCommandOrder = new StackRoleCommandOrder(result);
-      parseRoleCommandOrdersForServices();
+      //parseRoleCommandOrdersForServices();
       if (LOG.isDebugEnabled()) {
         LOG.debug("Role Command Order for " + rcoFilePath);
         roleCommandOrder.printRoleCommandOrder(LOG);
@@ -503,7 +509,7 @@ public class StackDirectory extends StackDefinitionDirectory {
     }
   }
 
-  private void parseRoleCommandOrdersForServices() {
+  /*private void parseRoleCommandOrdersForServices() {
     if (rcoFilePath != null) {
       File stack = new File(rcoFilePath).getParentFile();
       File servicesDir = new File(stack, "services");
@@ -530,6 +536,6 @@ public class StackDirectory extends StackDefinitionDirectory {
     } 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/647929db/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 2a5e75b..77a37df 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;
@@ -30,9 +31,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;
@@ -64,10 +70,24 @@ 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";
+
+  /**
+   * extensions file name
+   */
+  public static final String EXTENSIONS_XML_FILE_NAME = "extensions.xml";
+
+  public static final String METAINFO_FILE_NAME = "metainfo.xml";
+
+  /**
    * Provides access to non-stack server functionality
    */
   private StackContext stackContext;
 
+  private File stackRoot;
+
   /**
    * Logger
    */
@@ -79,12 +99,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
@@ -93,6 +120,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
@@ -100,29 +131,60 @@ public class StackManager {
   @Inject
   public StackManager(@Assisted("stackRoot") File stackRoot,
       @Assisted("commonServicesRoot") @Nullable File commonServicesRoot,
+      @Assisted("extensionRoot") @Nullable File extensionRoot,
       @Assisted OsFamily osFamily, MetainfoDAO metaInfoDAO,
-      ActionMetadata actionMetadata, StackDAO stackDao)
+      ActionMetadata actionMetadata, StackDAO stackDao, ExtensionDAO extensionDao,
+      ExtensionLinkDAO linkDao)
       throws AmbariException {
 
     LOG.info("Initializing the stack manager...");
 
     validateStackDirectory(stackRoot);
     validateCommonServicesDirectory(commonServicesRoot);
+    validateExtensionDirectory(extensionRoot);
 
+    this.stackRoot = stackRoot;
     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);
+    Map<String, ExtensionModule> 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) {
+          //Add the extension to the stack
+          module.getExtensionModules().put(key, extensionModule);
+          //linkStackToExtension(stack, extension);
+        }
+      }
+    }
+
+    fullyResolveCommonServices(stackModules, commonServiceModules, extensionModules);
+    fullyResolveExtensions(stackModules, commonServiceModules, extensionModules);
+    fullyResolveStacks(stackModules, commonServiceModules, extensionModules);
+
+    populateDB(stackDao, extensionDao);
+
+  }
 
-    fullyResolveCommonServices(stackModules, commonServiceModules);
-    fullyResolveStacks(stackModules, commonServiceModules);
+  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();
 
@@ -136,6 +198,101 @@ 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);
+      }
+    }
+
+  }
+
+  public void unlinkStackFromExtension(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    /*File stackVersionDir = new File(new File(stackRoot, stack.getName()), stack.getVersion());
+    File servicesDir = new File(stackVersionDir, "services");
+
+    for (ServiceInfo service : extension.getServices()) {
+      String name = service.getName();
+      File serviceDir = new File(servicesDir, name);
+      if (serviceDir.exists() && !serviceDir.isFile()) {
+        File xmlFile = new File(serviceDir, METAINFO_FILE_NAME);
+        if (xmlFile.exists())
+          xmlFile.delete();
+        serviceDir.delete();
+      }
+    }*/
+
+  }
+
+  /*private void writeServiceMetainfoXmlFile(ServiceMetainfoXml serviceXml, File file) throws AmbariException {
+    PrintWriter writer = null;
+    try {
+      writer = new PrintWriter(file);
+      writer.println("<?xml version=\"1.0\"?>");
+      writer.println("<metainfo>");
+      writer.println("  <schemaVersion>2.0</schemaVersion>");
+      writer.println("  <services>");
+      for (ServiceInfo service : serviceXml.getServices()) {
+        writer.println("    <service>");
+        writer.println("      <name>" + service.getName() + "</name>");
+        writer.println("      <extends>" + service.getParent() + "</extends>");
+        writer.println("    </service>");
+      }
+      writer.println("  </services>");
+      writer.println("</metainfo>");
+
+      writer.flush();
+    }
+    catch (Exception e) {
+      throw new AmbariException("Unable to create service metainfo file for extension service: " + file.getAbsolutePath(), e);
+    }
+    finally {
+      writer.close();
+    }
+  }*/
+
+  public void linkStackToExtension(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    /*File stackVersionDir = new File(new File(stackRoot, stack.getName()), stack.getVersion());
+    File servicesDir = new File(stackVersionDir, "services");
+
+    for (ServiceInfo service : extension.getServices()) {
+      String name = service.getName();
+      File serviceDir = new File(servicesDir, name);
+      if (serviceDir.exists() && serviceDir.isFile())
+        throw new AmbariException("Unable to create service directory for extension service: " + serviceDir.getAbsolutePath());
+      if (!serviceDir.exists())
+        serviceDir.mkdir();
+      if (!serviceDir.exists())
+        throw new AmbariException("Unable to create service directory for extension service: " + serviceDir.getAbsolutePath());
+
+      ServiceInfo stackService = new ServiceInfo();
+      stackService.setName(name);
+      String parent = StackManager.EXTENSIONS + PATH_DELIMITER + extension.getName()
+		  + PATH_DELIMITER + extension.getVersion() + PATH_DELIMITER + name;
+      stackService.setParent(parent);
+
+      ServiceMetainfoXml serviceXML = new ServiceMetainfoXml();
+      List<ServiceInfo> services = new ArrayList<ServiceInfo>();
+      services.add(stackService);
+      serviceXML.setServices(services);
+
+      File xmlFile = new File(serviceDir, METAINFO_FILE_NAME);
+      writeServiceMetainfoXmlFile(serviceXML, xmlFile);
+    }*/
+
   }
 
   /**
@@ -177,6 +334,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
@@ -193,12 +388,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.
@@ -208,6 +403,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();
     }
@@ -223,11 +421,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);
       }
     }
   }
@@ -279,6 +495,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
    *
@@ -357,4 +601,42 @@ public class StackManager {
     }
     return stackModules;
   }
+
+  /**
+   * 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/647929db/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 5aa2b5f..d672546 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);
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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 def33f0..e54867b 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
@@ -29,6 +29,7 @@ import java.util.Set;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 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;
@@ -93,6 +94,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;
@@ -129,6 +135,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
@@ -146,14 +160,25 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    */
   @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;
     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);
     }
     processRepositories();
     processPropertyDependencies();
@@ -179,6 +204,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);
+    }
   }
 
   /**
@@ -200,7 +231,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;
@@ -211,8 +242,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) {
@@ -228,7 +259,33 @@ 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 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 {
+
+    //mergeConfigurations(extension, allStacks, commonServices, extensions);
+    //mergeRoleCommandOrder(extension);
+
+    // grab stack level kerberos.json from parent stack
+    /*if (stackInfo.getKerberosDescriptorFileLocation() == null) {
+      stackInfo.setKerberosDescriptorFileLocation(extension.getModuleInfo().getKerberosDescriptorFileLocation());
+    }
+
+    if (stackInfo.getWidgetsDescriptorFileLocation() == null) {
+      stackInfo.setWidgetsDescriptorFileLocation(parentStack.getModuleInfo().getWidgetsDescriptorFileLocation());
+    }*/
   }
 
   /**
@@ -241,11 +298,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());
@@ -260,12 +317,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);
       }
     }
   }
@@ -281,12 +338,14 @@ 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 {
     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);
     }
   }
 
@@ -302,6 +361,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>
@@ -317,7 +395,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);
@@ -337,7 +415,7 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
       stackInfo.setErrors(error);
     } else {
       if (baseService.isValid()) {
-        service.resolve(baseService, allStacks, commonServices);
+        service.resolve(baseService, allStacks, commonServices, extensions);
       } else {
         setValid(false);
         stackInfo.setValid(false);
@@ -348,6 +426,62 @@ 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()) {
+      setInvalid(extension, "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()) {
+        setInvalid(parentService, "The service '" + serviceInfo.getName() + "' in stack '" + stackInfo.getName() + ":"
+            + stackInfo.getVersion() + "' extends a non-existent service: '" + parent + "'");
+      }
+      else
+        service.resolve(parentService, allStacks, commonServices, extensions);
+    }
+  }
+
+  private void setInvalid(Validable module, String error) {
+    setValid(false);
+    stackInfo.setValid(false);
+    if (module == null) {
+      setErrors(error);
+      stackInfo.setErrors(error);
+    }
+    else {
+      setErrors(module.getErrors());
+      stackInfo.setErrors(module.getErrors());
+    }
+  }
+
+
+  /**
    * Merge a service with its explicitly specified stack service as parent.
    * Parent: <stackName>/<stackVersion>/<serviceName>
    * Stack Lookup Key: <stackName>/<stackVersion>
@@ -358,16 +492,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 + "'");
     }
@@ -376,17 +511,17 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
     StackModule baseStack = allStacks.get(baseStackKey);
     if (baseStack == null) {
       throw new AmbariException("The service '" + serviceInfo.getName() + "' in stack '" + stackInfo.getName() + ":"
-          + stackInfo.getVersion() + "' extends a service in a non-existent stack: '" + baseStackKey + "'");
+          + stackInfo.getVersion() + "' extends a service in a non-existent extension: '" + 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.resolve(baseService, allStacks, commonServices);
+    service.resolve(baseService, allStacks, commonServices, extensions);
   }
 
   /**
@@ -402,8 +537,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()) {
@@ -522,13 +656,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());
@@ -545,10 +679,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");
@@ -562,6 +696,29 @@ 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())) {
+      setInvalid(extension, null);
+    }
+  }
+
+  /**
    * Add a child service module to the stack.
    *
    * @param service  service module to add
@@ -714,11 +871,47 @@ public class StackModule extends BaseModule<StackModule, StackInfo> implements V
    *
    * @param parentStack parent stack
    */
-
   private void mergeRoleCommandOrder(StackModule parentStack) {
-
     stackInfo.getRoleCommandOrder().merge(parentStack.stackInfo.getRoleCommandOrder());
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Role Command Order for " + stackInfo.getName() + "-" + stackInfo.getVersion() +
+        " parent " + stackInfo.getParentStackVersion());
+      stackInfo.getRoleCommandOrder().printRoleCommandOrder(LOG);
+    }
+  }
 
+  /**
+   * Merge role command order with the extension
+   *
+   * @param extension    linked extension
+   */
+  private void mergeRoleCommandOrder(ExtensionModule extension) {
+    if (extension.getModuleInfo().getRoleCommandOrder() == null)
+      return;
+
+    stackInfo.getRoleCommandOrder().merge(extension.getModuleInfo().getRoleCommandOrder(), true);
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Role Command Order for " + stackInfo.getName() + "-" + stackInfo.getVersion() +
+        " extension " + extension.getId());
+      stackInfo.getRoleCommandOrder().printRoleCommandOrder(LOG);
+    }
+  }
+
+  /**
+   * 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

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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 d27e52a..e27d556 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
@@ -47,14 +47,13 @@ public class StackServiceDirectory extends ServiceDirectory {
 
   @Override
   /**
-   * Parse stack service directory.
+   * Calculate the package directory for the stack service.
    * 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 calculatePackageDir() {
     File serviceDir = new File(getAbsolutePath());
     File stackVersionDir = serviceDir.getParentFile().getParentFile();
     File stackDir = stackVersionDir.getParentFile();
@@ -78,6 +77,6 @@ public class StackServiceDirectory extends ServiceDirectory {
       LOG.debug(String.format("Service package folder %s for service %s for stack %s does not exist.",
               absPackageDir, serviceDir.getName(), stackId));
     }
-    parseMetaInfoFile();
+    //parseMetaInfoFile();
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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 85ce252..b0089c1 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()) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java b/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
index 2c5c5af..92fc152 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
@@ -27,8 +27,11 @@ import java.util.concurrent.locks.ReadWriteLock;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.ClusterResponse;
 import org.apache.ambari.server.controller.ServiceConfigVersionResponse;
+import org.apache.ambari.server.orm.entities.ClusterExtensionVersionEntity;
 import org.apache.ambari.server.orm.entities.ClusterVersionEntity;
+import org.apache.ambari.server.orm.entities.ExtensionRepositoryVersionEntity;
 import org.apache.ambari.server.orm.entities.HostEntity;
+import org.apache.ambari.server.orm.entities.HostExtensionVersionEntity;
 import org.apache.ambari.server.orm.entities.HostVersionEntity;
 import org.apache.ambari.server.orm.entities.PrivilegeEntity;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
@@ -136,6 +139,12 @@ public interface Cluster {
   Collection<ClusterVersionEntity> getAllClusterVersions();
 
   /**
+   * Get all of the ClusterExtensionVersionEntity objects for the cluster and extension.
+   * @return
+   */
+  Collection<ClusterExtensionVersionEntity> getAllClusterExtensionVersions(String extensionName);
+
+  /**
    * Get desired stack version
    * @return
    */
@@ -216,6 +225,20 @@ public interface Cluster {
       throws AmbariException;
 
   /**
+   * For a given host, will either either update an existing Extension Host Version Entity for the given version, or create
+   * one if it doesn't exist
+   *
+   * @param host Host Entity object
+   * @param repositoryVersion Extension Repository Version that the host is transitioning to
+   * @param extensionId extension information with the version
+   * @return Returns either the newly created or the updated Extension Host Version Entity.
+   * @throws AmbariException
+   */
+  HostExtensionVersionEntity transitionHostExtensionVersionState(HostEntity host,
+      final ExtensionRepositoryVersionEntity repositoryVersion, final ExtensionId extensionId)
+      throws AmbariException;
+
+  /**
    * Update state of a cluster stack version for cluster based on states of host versions and stackids.
    * @param repositoryVersion the repository version entity whose version is a value like 2.2.1.0-100)
    * @throws AmbariException
@@ -223,6 +246,13 @@ public interface Cluster {
   void recalculateClusterVersionState(RepositoryVersionEntity repositoryVersion) throws AmbariException;
 
   /**
+   * Update state of a cluster extension version for cluster based on states of host extension versions and extension ids.
+   * @param repositoryVersion the extension repository version entity whose version is a value like 2.2.1.0-1000)
+   * @throws AmbariException
+   */
+  void recalculateClusterExtensionVersionState(ExtensionRepositoryVersionEntity repositoryVersion) throws AmbariException;
+
+  /**
    * Update state of all cluster stack versions for cluster based on states of host versions.
    * @throws AmbariException
    */
@@ -262,6 +292,20 @@ public interface Cluster {
       RepositoryVersionState state) throws AmbariException;
 
   /**
+   * Transition an existing cluster version from one state to another.
+   *
+   * @param extensionId
+   *          Extension ID
+   * @param version
+   *          Extension version
+   * @param state
+   *          Desired state
+   * @throws AmbariException
+   */
+  void transitionClusterExtensionVersion(ExtensionId extensionId, String version,
+      RepositoryVersionState state) throws AmbariException;
+
+  /**
    * Gets whether the cluster is still initializing or has finished with its
    * deployment requests.
    *

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/ComponentInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ComponentInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ComponentInfo.java
index fd68060..18f456d 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/ComponentInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ComponentInfo.java
@@ -46,6 +46,19 @@ public class ComponentInfo {
   @XmlElements(@XmlElement(name = "versionAdvertised"))
   private boolean versionAdvertised = false;
 
+  @XmlElements(@XmlElement(name = "notPreferableOnServerComponents"))
+  private boolean notPreferableOnServerComponents = false;
+
+  @XmlElements(@XmlElement(name = "notValuable"))
+  private boolean notValuable = false;
+
+  @XmlElements(@XmlElement(name = "useCardinalityForLayout"))
+  private boolean useCardinalityForLayout = false;
+
+  private String layout = null;
+
+  private int idealMinimum = 1;
+
   /**
   * Added at schema ver 2
   */
@@ -104,6 +117,11 @@ public class ComponentInfo {
     deleted = prototype.deleted;
     cardinality = prototype.cardinality;
     versionAdvertised = prototype.versionAdvertised;
+    notPreferableOnServerComponents = prototype.notPreferableOnServerComponents;
+    notValuable = prototype.notValuable;
+    useCardinalityForLayout = prototype.useCardinalityForLayout;
+    layout = prototype.layout;
+    idealMinimum = prototype.idealMinimum;
     clientsToUpdateConfigs = prototype.clientsToUpdateConfigs;
     commandScript = prototype.commandScript;
     customCommands = prototype.customCommands;
@@ -247,6 +265,46 @@ public class ComponentInfo {
     return versionAdvertised;
   }
 
+  public boolean isNotPreferableOnServerComponents() {
+    return notPreferableOnServerComponents;
+  }
+
+  public void setNotPreferableOnServerComponents(boolean notPreferableOnServerComponents) {
+    this.notPreferableOnServerComponents = notPreferableOnServerComponents;
+  }
+
+  public boolean isNotValuable() {
+    return notValuable;
+  }
+
+  public void setNotValuable(boolean notValuable) {
+    this.notValuable = notValuable;
+  }
+
+  public boolean isUseCardinalityForLayout() {
+    return useCardinalityForLayout;
+  }
+
+  public void setUseCardinalityForLayout(boolean useCardinalityForLayout) {
+    this.useCardinalityForLayout = useCardinalityForLayout;
+  }
+
+  public String getLayout() {
+    return layout;
+  }
+
+  public void setLayout(String layout) {
+    this.layout = layout;
+  }
+
+  public int getIdealMinimum() {
+    return idealMinimum;
+  }
+
+  public void setIdealMinimum(int idealMinimum) {
+    this.idealMinimum = idealMinimum;
+  }
+
   public List<String> getClientsToUpdateConfigs() {
     return clientsToUpdateConfigs;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionId.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionId.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionId.java
new file mode 100644
index 0000000..401244a
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionId.java
@@ -0,0 +1,155 @@
+/**
+ * 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.state;
+
+import org.apache.ambari.server.orm.entities.ExtensionEntity;
+import org.apache.ambari.server.utils.VersionUtils;
+
+public class ExtensionId implements Comparable<ExtensionId> {
+
+  private static final String NAME_SEPARATOR = "-";
+
+  private String extensionName;
+  private String extensionVersion;
+
+  public ExtensionId() {
+    extensionName = "";
+    extensionVersion = "";
+  }
+
+  public ExtensionId(String extensionId) {
+    parseExtensionIdHelper(this, extensionId);
+  }
+
+  public ExtensionId(ExtensionInfo extension) {
+    extensionName = extension.getName();
+    extensionVersion = extension.getVersion();
+  }
+
+  public ExtensionId(String extensionName, String extensionVersion) {
+    this(extensionName + NAME_SEPARATOR + extensionVersion);
+  }
+
+  public ExtensionId(ExtensionEntity entity) {
+    this(entity.getExtensionName(), entity.getExtensionVersion());
+  }
+
+  /**
+   * @return the extensionName
+   */
+  public String getExtensionName() {
+    return extensionName;
+  }
+
+  /**
+   * @return the extensionVersion
+   */
+  public String getExtensionVersion() {
+    return extensionVersion;
+  }
+
+  /**
+   * @return the extensionVersion
+   */
+  public String getExtensionId() {
+    if (extensionName.isEmpty()
+        && extensionVersion.isEmpty()) {
+      return "";
+    }
+    return extensionName + NAME_SEPARATOR + extensionVersion;
+  }
+
+  /**
+   * @param extensionId the extensionVersion to set
+   */
+  public void setExtensionId(String extensionId) {
+    parseExtensionIdHelper(this, extensionId);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public boolean equals(Object object) {
+    if (!(object instanceof ExtensionId)) {
+      return false;
+    }
+    if (this == object) {
+      return true;
+    }
+    ExtensionId s = (ExtensionId) object;
+    return extensionName.equals(s.extensionName) && extensionVersion.equals(s.extensionVersion);
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int hashCode() {
+    int result = extensionName != null ? extensionName.hashCode() : 0;
+    result = 31 * result + (extensionVersion != null ? extensionVersion.hashCode() : 0);
+    return result;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public int compareTo(ExtensionId other) {
+    if (this == other) {
+      return 0;
+    }
+
+    if (other == null) {
+      throw new RuntimeException("Cannot compare with a null value.");
+    }
+
+    int returnValue = getExtensionName().compareTo(other.getExtensionName());
+    if (returnValue == 0) {
+      returnValue = VersionUtils.compareVersions(getExtensionVersion(), other.getExtensionVersion());
+    } else {
+      throw new RuntimeException("ExtensionId with different names cannot be compared.");
+    }
+    return returnValue;
+  }
+
+  /**
+   * {@inheritDoc}
+   */
+  @Override
+  public String toString() {
+    return getExtensionId();
+  }
+
+  private void parseExtensionIdHelper(ExtensionId extensionVersion, String extensionId) {
+    if (extensionId == null || extensionId.isEmpty()) {
+      extensionVersion.extensionName = "";
+      extensionVersion.extensionVersion = "";
+      return;
+    }
+
+    int pos = extensionId.indexOf('-');
+    if (pos == -1 || (extensionId.length() <= (pos + 1))) {
+      throw new RuntimeException("Could not parse invalid Extension Id" + ", extensionId=" + extensionId);
+    }
+
+    extensionVersion.extensionName = extensionId.substring(0, pos);
+    extensionVersion.extensionVersion = extensionId.substring(pos + 1);
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionInfo.java
new file mode 100644
index 0000000..b07fb2f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ExtensionInfo.java
@@ -0,0 +1,414 @@
+/**
+ * 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.state;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.controller.ExtensionVersionResponse;
+import org.apache.ambari.server.stack.Validable;
+import org.apache.ambari.server.state.stack.ExtensionMetainfoXml;
+import org.apache.ambari.server.state.stack.StackRoleCommandOrder;
+import org.apache.ambari.server.state.stack.UpgradePack;
+
+public class ExtensionInfo implements Comparable<ExtensionInfo>, Validable{
+  private String name;
+  private String version;
+  private String rcoFileLocation;
+  private String kerberosDescriptorFileLocation;
+  private String widgetsDescriptorFileLocation;
+  private String mainRepoId;
+  private List<RepositoryInfo> repositories;
+  private Collection<ServiceInfo> services;
+  private String parentExtensionVersion;
+
+  private List<ExtensionMetainfoXml.Stack> stacks;
+  private List<ExtensionMetainfoXml.Extension> extensions;
+  private List<PropertyInfo> properties;
+  private Map<String, Map<String, Map<String, String>>> configTypes;
+  private Map<String, UpgradePack> upgradePacks;
+  private StackRoleCommandOrder roleCommandOrder;
+  private boolean valid = true;
+
+  /**
+   *
+   * @return valid xml flag
+   */
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  /**
+   *
+   * @param valid set validity flag
+   */
+  @Override
+  public void setValid(boolean valid) {
+    this.valid = valid;
+  }
+
+  private Set<String> errorSet = new HashSet<String>();
+
+  @Override
+  public void setErrors(String error) {
+    errorSet.add(error);
+  }
+
+  @Override
+  public Collection getErrors() {
+    return errorSet;
+  }
+
+  @Override
+  public void setErrors(Collection error) {
+    this.errorSet.addAll(error);
+  }
+
+  //private String stackHooksFolder;
+
+  private String upgradesFolder = null;
+
+  private volatile Map<String, PropertyInfo> requiredProperties;
+
+  public String getName() {
+    return name;
+  }
+
+  public void setName(String name) {
+    this.name = name;
+  }
+
+  public String getVersion() {
+    return version;
+  }
+
+  public void setVersion(String version) {
+    this.version = version;
+  }
+
+  public String getMainRepositoryId() {
+    if (mainRepoId != null)
+      return mainRepoId;
+    else
+      // if main repo id is not defined in repository then
+      // try main repo as a combination of extension name and version
+      return (name + "-" + version);
+  }
+
+  public void setMainRepositoryId(String repoid) {
+    this.mainRepoId = repoid;
+  }
+
+  public List<RepositoryInfo> getRepositories() {
+    if( repositories == null ) repositories = new ArrayList<RepositoryInfo>();
+    return repositories;
+  }
+
+  public void setRepositories(List<RepositoryInfo> repositories) {
+    this.repositories = repositories;
+  }
+
+  public synchronized Collection<ServiceInfo> getServices() {
+    if (services == null) services = new ArrayList<ServiceInfo>();
+    return services;
+  }
+
+  public ServiceInfo getService(String name) {
+    Collection<ServiceInfo> services = getServices();
+    for (ServiceInfo service : services) {
+      if (service.getName().equals(name)) {
+        return service;
+      }
+    }
+    //todo: exception?
+    return null;
+  }
+
+  public synchronized void setServices(Collection<ServiceInfo> services) {
+    this.services = services;
+  }
+
+  public List<PropertyInfo> getProperties() {
+    if (properties == null) properties = new ArrayList<PropertyInfo>();
+    return properties;
+  }
+
+  public void setProperties(List<PropertyInfo> properties) {
+    this.properties = properties;
+  }
+
+  /**
+   * Obtain the config types associated with this stack.
+   * The returned map is an unmodifiable view.
+   * @return copy of the map of config types associated with this stack
+   */
+  public synchronized Map<String, Map<String, Map<String, String>>> getConfigTypeAttributes() {
+    return configTypes == null ?
+        Collections.<String, Map<String, Map<String, String>>>emptyMap() :
+        Collections.unmodifiableMap(configTypes);
+  }
+
+
+  /**
+   * Add the given type and set it's attributes.
+   *
+   * @param type            configuration type
+   * @param typeAttributes  attributes associated with the type
+   */
+  /*public synchronized void setConfigTypeAttributes(String type, Map<String, Map<String, String>> typeAttributes) {
+    if (this.configTypes == null) {
+      configTypes = new HashMap<String, Map<String, Map<String, String>>>();
+    }
+    // todo: no exclusion mechanism for stack config types
+    configTypes.put(type, typeAttributes);
+  }*/
+
+  /**
+   * Set all types and associated attributes.  Any previously existing types and
+   * attributes are removed prior to setting the new values.
+   *
+   * @param types map of type attributes
+   */
+  /*public synchronized void setAllConfigAttributes(Map<String, Map<String, Map<String, String>>> types) {
+    configTypes = new HashMap<String, Map<String, Map<String, String>>>();
+    for (Map.Entry<String, Map<String, Map<String, String>>> entry : types.entrySet()) {
+      setConfigTypeAttributes(entry.getKey(), entry.getValue());
+    }
+  }*/
+
+  @Override
+  public String toString() {
+    StringBuilder sb = new StringBuilder("Stack name:" + name + "\nversion:" +
+      version + " \nvalid:" + isValid());
+    if (services != null) {
+      sb.append("\n\t\tService:");
+      for (ServiceInfo service : services) {
+        sb.append("\t\t");
+        sb.append(service);
+      }
+    }
+
+    if (repositories != null) {
+      sb.append("\n\t\tRepositories:");
+      for (RepositoryInfo repository : repositories) {
+        sb.append("\t\t");
+        sb.append(repository.toString());
+      }
+    }
+
+    return sb.toString();
+  }
+
+
+  @Override
+  public int hashCode() {
+    return 31  + name.hashCode() + version.hashCode();
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (!(obj instanceof ExtensionInfo)) {
+      return false;
+    }
+    if (this == obj) {
+      return true;
+    }
+    ExtensionInfo stackInfo = (ExtensionInfo) obj;
+    return getName().equals(stackInfo.getName()) && getVersion().equals(stackInfo.getVersion());
+  }
+
+  public ExtensionVersionResponse convertToResponse() {
+
+    // Get the stack-level Kerberos descriptor file path
+    String stackDescriptorFileFilePath = getKerberosDescriptorFileLocation();
+
+    String widgetDescriptorFilePath = getWidgetsDescriptorFileLocation();
+
+    // Collect the services' Kerberos descriptor files
+    Collection<ServiceInfo> serviceInfos = getServices();
+    // The collection of service descriptor files. A Set is being used because some Kerberos descriptor
+    // files contain multiple services, therefore the same File may be encountered more than once.
+    // For example the YARN directory may contain YARN and MAPREDUCE2 services.
+    Collection<File> serviceDescriptorFiles = new HashSet<File>();
+    if (serviceInfos != null) {
+      for (ServiceInfo serviceInfo : serviceInfos) {
+        File file = serviceInfo.getKerberosDescriptorFile();
+        if (file != null) {
+          serviceDescriptorFiles.add(file);
+        }
+      }
+    }
+
+    /*return new ExtensionVersionResponse(getVersion(), getMinUpgradeVersion(),
+            isActive(), getParentStackVersion(), getConfigTypeAttributes(),
+            (stackDescriptorFileFilePath == null) ? null : new File(stackDescriptorFileFilePath),
+            serviceDescriptorFiles,
+            null == upgradePacks ? Collections.<String>emptySet() : upgradePacks.keySet(),
+            isValid(), getErrors());*/
+
+    return new ExtensionVersionResponse(getVersion(), getParentExtensionVersion(), getConfigTypeAttributes(),
+            (stackDescriptorFileFilePath == null) ? null : new File(stackDescriptorFileFilePath),
+            serviceDescriptorFiles,
+        null == upgradePacks ? Collections.<String>emptySet() : upgradePacks.keySet(),
+        isValid(), getErrors());
+  }
+
+  /*public String getMinUpgradeVersion() {
+    return minUpgradeVersion;
+  }
+
+  public void setMinUpgradeVersion(String minUpgradeVersion) {
+    this.minUpgradeVersion = minUpgradeVersion;
+  }
+
+  public boolean isActive() {
+    return active;
+  }
+
+  public void setActive(boolean active) {
+    this.active = active;
+  }*/
+
+  public String getParentExtensionVersion() {
+    return parentExtensionVersion;
+  }
+
+  public void setParentExtensionVersion(String parentExtensionVersion) {
+    this.parentExtensionVersion = parentExtensionVersion;
+  }
+
+  public StackRoleCommandOrder getRoleCommandOrder() {
+    return roleCommandOrder;
+  }
+
+  public void setRoleCommandOrder(StackRoleCommandOrder roleCommandOrder) {
+    this.roleCommandOrder = roleCommandOrder;
+  }
+
+  public String getRcoFileLocation() {
+    return rcoFileLocation;
+  }
+
+  public void setRcoFileLocation(String rcoFileLocation) {
+    this.rcoFileLocation = rcoFileLocation;
+  }
+
+  public String getKerberosDescriptorFileLocation() {
+    return kerberosDescriptorFileLocation;
+  }
+
+  public void setKerberosDescriptorFileLocation(String kerberosDescriptorFileLocation) {
+    this.kerberosDescriptorFileLocation = kerberosDescriptorFileLocation;
+  }
+
+  public String getWidgetsDescriptorFileLocation() {
+    return widgetsDescriptorFileLocation;
+  }
+
+  public void setWidgetsDescriptorFileLocation(String widgetsDescriptorFileLocation) {
+    this.widgetsDescriptorFileLocation = widgetsDescriptorFileLocation;
+  }
+
+  /**
+   * Set the path of the stack upgrade directory.
+   *
+   * @param path the path to the upgrades directory
+   */
+  public void setUpgradesFolder(String path) {
+    upgradesFolder = path;
+  }
+
+  /**
+   * Obtain the path of the upgrades folder or null if directory doesn't exist.
+   *
+   * @return the upgrades folder, or {@code null} if not set
+   */
+  public String getUpgradesFolder() {
+    return upgradesFolder;
+  }
+
+  /**
+   * Set upgrade packs.
+   *
+   * @param upgradePacks  map of upgrade packs
+   */
+  public void setUpgradePacks(Map<String, UpgradePack> upgradePacks) {
+    this.upgradePacks = upgradePacks;
+  }
+
+  /**
+   * Obtain all stack upgrade packs.
+   *
+   * @return map of upgrade pack name to upgrade pack or {@code null} of no packs
+   */
+  public Map<String, UpgradePack> getUpgradePacks() {
+    return upgradePacks;
+  }
+
+
+  @Override
+  public int compareTo(ExtensionInfo o) {
+    String myId = name + "-" + version;
+    String oId = o.name + "-" + o.version;
+    return myId.compareTo(oId);
+  }
+
+  public List<ExtensionMetainfoXml.Stack> getStacks() {
+    return stacks;
+  }
+
+  public void setStacks(List<ExtensionMetainfoXml.Stack> stacks) {
+    this.stacks = stacks;
+  }
+
+  public List<ExtensionMetainfoXml.Extension> getExtensions() {
+    return extensions;
+  }
+
+  public void setExtensions(List<ExtensionMetainfoXml.Extension> extensions) {
+    this.extensions = extensions;
+  }
+
+  //todo: ensure that required properties are never modified...
+  /*public Map<String, PropertyInfo> getRequiredProperties() {
+    Map<String, PropertyInfo> result = requiredProperties;
+    if (result == null) {
+      synchronized(this) {
+        result = requiredProperties;
+        if (result == null) {
+          requiredProperties = result = new HashMap<String, PropertyInfo>();
+          List<PropertyInfo> properties = getProperties();
+          for (PropertyInfo propertyInfo : properties) {
+            if (propertyInfo.isRequireInput()) {
+              result.put(propertyInfo.getName(), propertyInfo);
+            }
+          }
+        }
+      }
+    }
+    return result;
+  }*/
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyDependencyInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyDependencyInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyDependencyInfo.java
index a801311..f6759d1 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyDependencyInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyDependencyInfo.java
@@ -82,9 +82,5 @@ public class PropertyDependencyInfo {
         '}';
   }
 
-  public StackConfigurationDependencyResponse convertToResponse() {
-    return new StackConfigurationDependencyResponse(getName(), getType());
-  }
-
 }
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceComponentHost.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceComponentHost.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceComponentHost.java
index f1e8d62..1296aad 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceComponentHost.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceComponentHost.java
@@ -23,6 +23,7 @@ import java.util.Map;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.ServiceComponentHostResponse;
+import org.apache.ambari.server.orm.entities.ExtensionRepositoryVersionEntity;
 import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
 import org.apache.ambari.server.state.fsm.InvalidStateTransitionException;
 
@@ -40,6 +41,11 @@ public interface ServiceComponentHost {
   public String getClusterName();
 
   /**
+   * Returns whether or not the service to which this object maps comes from an extension
+   */
+  public boolean isExtensionService();
+
+  /**
    * Get the Service this object maps to
    * @return Name of the Service
    */
@@ -220,4 +226,11 @@ public interface ServiceComponentHost {
    */
   public RepositoryVersionEntity recalculateHostVersionState() throws AmbariException;
 
+  /**
+   * Changes extension host version state according to state of the components installed on the host.
+   * @return The Extension Repository Version Entity with that component in the host
+   * @throws AmbariException if host is detached from the cluster
+   */
+  public ExtensionRepositoryVersionEntity recalculateHostExtensionVersionState() throws AmbariException;
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
index a58cda3..ab09905 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/ServiceInfo.java
@@ -27,6 +27,7 @@ import com.google.common.collect.Multimaps;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.stack.Validable;
 import org.apache.ambari.server.state.stack.MetricDefinition;
+import org.apache.ambari.server.state.stack.StackRoleCommandOrder;
 import org.codehaus.jackson.annotate.JsonIgnore;
 import org.codehaus.jackson.map.annotate.JsonFilter;
 
@@ -67,6 +68,7 @@ public class ServiceInfo implements Validable{
   private String version;
   private String comment;
   private String serviceType;
+  boolean extensionService = false;
 
   @XmlTransient
   private List<PropertyInfo> properties;
@@ -138,6 +140,8 @@ public class ServiceInfo implements Validable{
 
   @XmlTransient
   private File widgetsDescriptorFile = null;
+
+  private StackRoleCommandOrder roleCommandOrder;
   
   @XmlTransient
   private boolean valid = true;
@@ -683,6 +687,14 @@ public String getVersion() {
     this.widgetsDescriptorFile = widgetsDescriptorFile;
   }
 
+  public StackRoleCommandOrder getRoleCommandOrder() {
+    return roleCommandOrder;
+  }
+
+  public void setRoleCommandOrder(StackRoleCommandOrder roleCommandOrder) {
+    this.roleCommandOrder = roleCommandOrder;
+  }
+
   /**
    * @return config types this service contains configuration for, but which are primarily related to another service
    */
@@ -769,6 +781,13 @@ public String getVersion() {
     this.themesMap = themesMap;
   }
 
+  public boolean isExtensionService() {
+    return extensionService;
+  }
+
+  public void setExtensionService(boolean extensionService) {
+    this.extensionService = extensionService;
+  }
 
   public List<ServicePropertyInfo> getServicePropertyList() {
     return servicePropertyList;

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java b/ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java
index 0d87b68..42ac622 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/StackInfo.java
@@ -63,6 +63,7 @@ public class StackInfo implements Comparable<StackInfo>, Validable{
   private String widgetsDescriptorFileLocation;
   private List<RepositoryInfo> repositories;
   private Collection<ServiceInfo> services;
+  private Collection<ExtensionInfo> extensions;
   private String parentStackVersion;
   // stack-level properties
   private List<PropertyInfo> properties;
@@ -164,6 +165,34 @@ public class StackInfo implements Comparable<StackInfo>, Validable{
     this.services = services;
   }
 
+  public synchronized Collection<ExtensionInfo> getExtensions() {
+    if (extensions == null) extensions = new ArrayList<ExtensionInfo>();
+    return extensions;
+  }
+
+  public ExtensionInfo getExtension(String name) {
+    Collection<ExtensionInfo> extensions = getExtensions();
+    for (ExtensionInfo extension : extensions) {
+      if (extension.getName().equals(name)) {
+        return extension;
+      }
+    }
+    //todo: exception?
+    return null;
+  }
+
+  public ExtensionInfo getExtensionByService(String serviceName) {
+    Collection<ExtensionInfo> extensions = getExtensions();
+    for (ExtensionInfo extension : extensions) {
+      for (ServiceInfo service : services) {
+        if (service.getName().equals(serviceName))
+          return extension;
+      }
+    }
+    //todo: exception?
+    return null;
+  }
+
   public List<PropertyInfo> getProperties() {
     if (properties == null) properties = new ArrayList<PropertyInfo>();
     return properties;