ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d...@apache.org
Subject [06/13] ambari git commit: AMBARI-12885: Dynamic stack extensions - install and upgrade support for custom services (tthorpe via dili)
Date Mon, 11 Jan 2016 19:15:42 GMT
http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostExtensionVersionEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostExtensionVersionEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostExtensionVersionEntity.java
new file mode 100644
index 0000000..ffb319d
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/HostExtensionVersionEntity.java
@@ -0,0 +1,183 @@
+/**
+ * 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.orm.entities;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+import org.apache.ambari.server.state.RepositoryVersionState;
+
+@Table(name = "host_extension_version")
+@Entity
+@TableGenerator(name = "host_extension_version_id_generator",
+    table = "ambari_sequences", pkColumnName = "sequence_name", valueColumnName = "sequence_value"
+    , pkColumnValue = "host_extension_version_id_seq"
+    , initialValue = 0
+)
+@NamedQueries({
+    @NamedQuery(name = "hostExtensionVersionByClusterAndExtensionAndVersion", query =
+        "SELECT hostExtensionVersion FROM HostExtensionVersionEntity hostExtensionVersion JOIN hostExtensionVersion.hostEntity host JOIN host.clusterEntities clusters " +
+            "WHERE clusters.clusterName=:clusterName AND hostExtensionVersion.repositoryVersion.extension.extensionName=:extensionName " +
+            "AND hostExtensionVersion.repositoryVersion.extension.extensionVersion=:extensionVersion AND hostExtensionVersion.repositoryVersion.version=:version"),
+
+    @NamedQuery(name = "hostExtensionVersionByClusterAndHostname", query =
+        "SELECT hostExtensionVersion FROM HostExtensionVersionEntity hostExtensionVersion JOIN hostExtensionVersion.hostEntity host JOIN host.clusterEntities clusters " +
+            "WHERE clusters.clusterName=:clusterName AND hostExtensionVersion.hostEntity.hostName=:hostName"),
+
+    @NamedQuery(name = "hostExtensionVersionByHostname", query =
+        "SELECT hostExtensionVersion FROM HostExtensionVersionEntity hostExtensionVersion JOIN hostExtensionVersion.hostEntity host " +
+            "WHERE hostExtensionVersion.hostEntity.hostName=:hostName"),
+
+    @NamedQuery(name = "hostExtensionVersionByClusterHostnameAndState", query =
+        "SELECT hostExtensionVersion FROM HostExtensionVersionEntity hostExtensionVersion JOIN hostExtensionVersion.hostEntity host JOIN host.clusterEntities clusters " +
+            "WHERE clusters.clusterName=:clusterName AND hostExtensionVersion.hostEntity.hostName=:hostName AND hostExtensionVersion.state=:state"),
+
+    @NamedQuery(name = "hostExtensionVersionByClusterExtensionVersionAndHostname", query =
+        "SELECT hostExtensionVersion FROM HostExtensionVersionEntity hostExtensionVersion JOIN hostExtensionVersion.hostEntity host JOIN host.clusterEntities clusters " +
+            "WHERE clusters.clusterName=:clusterName AND hostExtensionVersion.repositoryVersion.extension.extensionName=:extensionName AND hostExtensionVersion.repositoryVersion.extension.extensionVersion=:extensionVersion " +
+            "AND hostExtensionVersion.repositoryVersion.version=:version AND hostExtensionVersion.hostEntity.hostName=:hostName"),
+})
+public class HostExtensionVersionEntity {
+
+  @Id
+  @Column(name = "id", nullable = false, insertable = true, updatable = false)
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = "host_extension_version_id_generator")
+  private Long id;
+
+  @ManyToOne
+  @JoinColumn(name = "repo_version_id", referencedColumnName = "repo_version_id", nullable = false)
+  private ExtensionRepositoryVersionEntity repositoryVersion;
+
+  @Column(name = "host_id", nullable=false, insertable = false, updatable = false)
+  private Long hostId;
+
+  @ManyToOne
+  @JoinColumn(name = "host_id", referencedColumnName = "host_id", nullable = false)
+  private HostEntity hostEntity;
+
+  @Column(name = "state", nullable = false, insertable = true, updatable = true)
+  @Enumerated(value = EnumType.STRING)
+  private RepositoryVersionState state;
+
+  /**
+   * Empty constructor is needed by unit tests.
+   */
+  public HostExtensionVersionEntity() {
+  }
+
+  /**
+   * When using this constructor, you should also call setHostEntity(). Otherwise
+   * you will have persistence errors when persisting the instance.
+   */
+  public HostExtensionVersionEntity(HostEntity hostEntity, ExtensionRepositoryVersionEntity repositoryVersion, RepositoryVersionState state) {
+    this.hostEntity = hostEntity;
+    this.repositoryVersion = repositoryVersion;
+    this.state = state;
+  }
+
+  /**
+   * This constructor is mainly used by the unit tests in order to construct an object without the id.
+   */
+  public HostExtensionVersionEntity(HostExtensionVersionEntity other) {
+    this.hostEntity = other.hostEntity;
+    this.repositoryVersion = other.repositoryVersion;
+    this.state = other.state;
+  }
+
+  public Long getId() {
+    return id;
+  }
+
+  public void setId(Long id) {
+    this.id = id;
+  }
+
+  public String getHostName() {
+    return hostEntity != null ? hostEntity.getHostName() : null;
+  }
+
+  public HostEntity getHostEntity() {
+    return hostEntity;
+  }
+
+  public void setHostEntity(HostEntity hostEntity) {
+    this.hostEntity = hostEntity;
+  }
+
+  public RepositoryVersionState getState() {
+    return state;
+  }
+
+  public void setState(RepositoryVersionState state) {
+    this.state = state;
+  }
+
+  public ExtensionRepositoryVersionEntity getRepositoryVersion() {
+    return repositoryVersion;
+  }
+
+  public void setRepositoryVersion(ExtensionRepositoryVersionEntity repositoryVersion) {
+    this.repositoryVersion = repositoryVersion;
+  }
+
+  @Override
+  public int hashCode() {
+    final int prime = 31;
+    int result = 1;
+    result = prime * result + ((hostEntity == null) ? 0 : hostEntity.hashCode());
+    result = prime * result + ((hostEntity == null) ? 0 : hostEntity.hashCode());
+    result = prime * result + ((id == null) ? 0 : id.hashCode());
+    result = prime * result + ((repositoryVersion == null) ? 0 : repositoryVersion.hashCode());
+    result = prime * result + ((state == null) ? 0 : state.hashCode());
+    return result;
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) return true;
+    if (obj == null) return false;
+    if (getClass() != obj.getClass()) return false;
+
+    HostExtensionVersionEntity other = (HostExtensionVersionEntity) obj;
+    if (id != null ? id != other.id : other.id != null) return false;
+    if (hostEntity != null ? !hostEntity.equals(other.hostEntity) : other.hostEntity != null) return false;
+    if (repositoryVersion != null ? !repositoryVersion.equals(other.repositoryVersion) : other.repositoryVersion != null) return false;
+    if (state != other.state) return false;
+    return true;
+  }
+
+  public Long getHostId() {
+    return hostId;
+  }
+
+  public void setHostId(Long hostId) {
+    this.hostId = hostId;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RoleSuccessCriteriaEntity.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RoleSuccessCriteriaEntity.java b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RoleSuccessCriteriaEntity.java
index 3386c24..13bf608 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RoleSuccessCriteriaEntity.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/RoleSuccessCriteriaEntity.java
@@ -32,6 +32,7 @@ import org.apache.ambari.server.Role;
 
 @IdClass(org.apache.ambari.server.orm.entities.RoleSuccessCriteriaEntityPK.class)
 @Table(name = "role_success_criteria")
+
 @Entity
 public class RoleSuccessCriteriaEntity {
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/stack/BaseModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/BaseModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/BaseModule.java
index ef2438f..0e1116f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/BaseModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/BaseModule.java
@@ -60,7 +60,7 @@ public abstract class BaseModule<T, I> implements StackDefinitionModule<T, I> {
    * @return collection of the merged modules
    */
   protected <T extends StackDefinitionModule<T, ?>> Collection<T> mergeChildModules(
-      Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, T> modules, Map<String, T> parentModules)
+      Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions, Map<String, T> modules, Map<String, T> parentModules)
         throws AmbariException {
     Set<String> addedModules = new HashSet<String>();
     Collection<T> mergedModules = new HashSet<T>();
@@ -70,7 +70,7 @@ public abstract class BaseModule<T, I> implements StackDefinitionModule<T, I> {
       addedModules.add(id);
       if (!module.isDeleted()) {
         if (parentModules.containsKey(id)) {
-          module.resolve(parentModules.get(id), allStacks, commonServices);
+          module.resolve(parentModules.get(id), allStacks, commonServices, extensions);
         }
         mergedModules.add(module);
       }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/stack/CommonServiceDirectory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/CommonServiceDirectory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/CommonServiceDirectory.java
index 636de37..c9a16f0 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/CommonServiceDirectory.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/CommonServiceDirectory.java
@@ -45,15 +45,13 @@ public class CommonServiceDirectory extends ServiceDirectory {
 
   @Override
   /**
-   * Parse common service directory
+   * Calculate the package directory for the common service
    * packageDir Format: common-services/<serviceName>/<serviceVersion>/package
    * Example:
    *  directory: "/var/lib/ambari-server/resources/common-services/HDFS/1.0"
    *  packageDir: "common-services/HDFS/1.0/package"
-   *
-   * @throws AmbariException
    */
-  protected void parsePath() throws AmbariException {
+  protected void calculatePackageDir() {
     File serviceVersionDir = new File(getAbsolutePath());
     File serviceDir = serviceVersionDir.getParentFile();
 
@@ -68,6 +66,5 @@ public class CommonServiceDirectory extends ServiceDirectory {
       LOG.debug(String.format("Service package folder %s for common service %s does not exist.",
           absPackageDir, serviceId ));
     }
-    parseMetaInfoFile();
   }
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/stack/ComponentModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ComponentModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ComponentModule.java
index 18c6ef3..8106c6b 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ComponentModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ComponentModule.java
@@ -54,7 +54,8 @@ public class ComponentModule extends BaseModule<ComponentModule, ComponentInfo>
   }
 
   @Override
-  public void resolve(ComponentModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices) {
+  public void resolve(ComponentModule parent, Map<String, StackModule> allStacks,
+	    Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) {
     if (parent != null) {
       ComponentInfo parentInfo = parent.getModuleInfo();
       if (!parent.isValid()) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/stack/ConfigurationModule.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ConfigurationModule.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ConfigurationModule.java
index 70f2547..a9e3df6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/ConfigurationModule.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ConfigurationModule.java
@@ -82,7 +82,8 @@ public class ConfigurationModule extends BaseModule<ConfigurationModule, Configu
   }
 
   @Override
-  public void resolve(ConfigurationModule parent, Map<String, StackModule> allStacks, Map<String, ServiceModule> commonServices) throws AmbariException {
+  public void resolve(ConfigurationModule parent, Map<String, StackModule> allStacks,
+	    Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions) throws AmbariException {
     // merge properties also removes deleted props so should be called even if extension is disabled
     if (parent != null) {
       if (parent.info != null) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionDirectory.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionDirectory.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionDirectory.java
new file mode 100644
index 0000000..46a4730
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionDirectory.java
@@ -0,0 +1,494 @@
+/**
+ * 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 org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.state.stack.ExtensionMetainfoXml;
+import org.apache.ambari.server.state.stack.RepositoryXml;
+import org.apache.ambari.server.state.stack.StackRoleCommandOrder;
+import org.apache.ambari.server.state.stack.UpgradePack;
+import org.apache.commons.io.FilenameUtils;
+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.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+
+/**
+ * Encapsulates IO operations on a extension definition extension directory.
+ */
+//todo: Normalize all path return values.
+//todo: Currently some are relative and some are absolute.
+//todo: Current values were dictated by the ExtensionInfo expectations.
+public class ExtensionDirectory extends StackDefinitionDirectory {
+
+  /**
+   * upgrades directory path
+   */
+  private String upgradesDir;
+
+  /**
+   * rco file path
+   */
+  private String rcoFilePath;
+
+  /**
+   * kerberos descriptor file path
+   */
+  //private String kerberosDescriptorFilePath;
+
+  /**
+   * kerberos descriptor file path
+   */
+  //private String widgetsDescriptorFilePath;
+
+  /**
+   * repository file
+   */
+  private RepositoryXml repoFile;
+
+  /**
+   * role command order
+   */
+  private StackRoleCommandOrder roleCommandOrder;
+
+  /**
+   * repository directory
+   */
+  private String repoDir;
+
+  /**
+   * collection of service directories
+   */
+  private Collection<ServiceDirectory> serviceDirectories;
+
+  /**
+   * map of upgrade pack name to upgrade pack
+   */
+  //todo: should be a collection but upgrade pack doesn't have a name attribute
+  private Map<String, UpgradePack> upgradePacks;
+
+  /**
+   * metainfo file representation
+   */
+  private ExtensionMetainfoXml metaInfoXml;
+
+  /**
+   * file unmarshaller
+   */
+  ModuleFileUnmarshaller unmarshaller = new ModuleFileUnmarshaller();
+
+  /**
+   * extensions directory name
+   */
+  public final static String EXTENSIONS_FOLDER_NAME = "extensions";
+
+  /**
+   * name of the hooks directory
+   */
+  //public static final String HOOKS_FOLDER_NAME = "hooks";
+
+  /**
+   * repository directory name
+   */
+  private final static String REPOSITORY_FOLDER_NAME = "repos";
+
+  /**
+   * repository file name
+   */
+  private final static String REPOSITORY_FILE_NAME = "repoinfo.xml";
+
+  /**
+   * metainfo file name
+   */
+  private static final String STACK_METAINFO_FILE_NAME = "metainfo.xml";
+
+  /**
+   * upgrades directory name
+   */
+  private static final String UPGRADE_PACK_FOLDER_NAME = "upgrades";
+
+  /**
+   * role command order file name
+   */
+  private static final String ROLE_COMMAND_ORDER_FILE = "role_command_order.json";
+
+  /**
+   * logger instance
+   */
+  private final static Logger LOG = LoggerFactory.getLogger(ExtensionDirectory.class);
+
+
+  /**
+   * Constructor.
+   *
+   * @param directory  extension directory
+   * @throws AmbariException if unable to parse the stack directory
+   */
+  public ExtensionDirectory(String directory) throws AmbariException {
+    super(directory);
+    parsePath();
+  }
+
+  /**
+   * Obtain the extension directory name.
+   *
+   * @return extension directory name
+   */
+  public String getExtensionDirName() {
+    return getDirectory().getParentFile().getName();
+  }
+
+  /**
+   * Obtain the hooks directory path.
+   *
+   * @return hooks directory path
+   */
+  /*public String getHooksDir() {
+    return hooksDir;
+  }*/
+
+  /**
+   * Obtain the upgrades directory path.
+   *
+   * @return upgrades directory path
+   */
+  public String getUpgradesDir() {
+    return upgradesDir;
+  }
+
+  /**
+   * Obtain the rco file path.
+   *
+   * @return rco file path
+   */
+  public String getRcoFilePath() {
+    return rcoFilePath;
+  }
+
+  /**
+   * Obtain the path to the (extension-level) Kerberos descriptor file
+   *
+   * @return the path to the (extension-level) Kerberos descriptor file
+   */
+  /*public String getKerberosDescriptorFilePath() {
+    return kerberosDescriptorFilePath;
+  }*/
+
+  /**
+   * Obtain the path to the (extension-level) widgets descriptor file
+   *
+   * @return the path to the (extension-level) widgets descriptor file
+   */
+  /*public String getWidgetsDescriptorFilePath() {
+    return widgetsDescriptorFilePath;
+  }*/
+
+  /**
+   * Obtain the repository directory path.
+   *
+   * @return repository directory path
+   */
+  public String getRepoDir() {
+    return repoDir;
+  }
+
+  /**
+   * Obtain the repository file object representation.
+   *
+   * @return repository file object representation
+   */
+  public RepositoryXml getRepoFile() {
+    return repoFile;
+  }
+
+  /**
+   * Obtain the object representation of the extension metainfo.xml file.
+   *
+   * @return object representation of the extension metainfo.xml file
+   */
+  public ExtensionMetainfoXml getMetaInfoFile() {
+    return metaInfoXml;
+  }
+
+  /**
+   * Obtain a collection of all service directories.
+   *
+   * @return collection of all service directories
+   */
+  public Collection<ServiceDirectory> getServiceDirectories() {
+    return serviceDirectories;
+  }
+
+  /**
+   * Obtain a map of all upgrade packs.
+   *
+   * @return map of upgrade pack name to upgrade pack or null if no packs available
+   */
+  public Map<String, UpgradePack> getUpgradePacks() {
+    return upgradePacks;
+  }
+
+  /**
+   * Obtain the object representation of the extension role_command_order.json file
+   *
+   * @return object representation of the extension role_command_order.json file
+   */
+
+  public StackRoleCommandOrder getRoleCommandOrder() {
+    return roleCommandOrder;
+  }
+
+  /**
+   * Parse the extension directory.
+   *
+   * @throws AmbariException if unable to parse the directory
+   */
+  private void parsePath() throws AmbariException {
+    Collection<String> subDirs = Arrays.asList(directory.list());
+    /*if (subDirs.contains(HOOKS_FOLDER_NAME)) {
+      // hooksDir is expected to be relative to stack root
+      hooksDir = getStackDirName() + File.separator + getName() +
+          File.separator + HOOKS_FOLDER_NAME;
+    } else {
+      LOG.debug("Hooks folder " + getAbsolutePath() + File.separator +
+          HOOKS_FOLDER_NAME + " does not exist");
+    }*/
+
+    if (subDirs.contains(AmbariMetaInfo.RCO_FILE_NAME)) {
+      // rcoFile is expected to be absolute
+      rcoFilePath = getAbsolutePath() + File.separator + AmbariMetaInfo.RCO_FILE_NAME;
+    }
+
+    /*if (subDirs.contains(AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME)) {
+      // kerberosDescriptorFilePath is expected to be absolute
+      kerberosDescriptorFilePath = getAbsolutePath() + File.separator + AmbariMetaInfo.KERBEROS_DESCRIPTOR_FILE_NAME;
+    }
+
+    if (subDirs.contains(AmbariMetaInfo.WIDGETS_DESCRIPTOR_FILE_NAME)) {
+      widgetsDescriptorFilePath = getAbsolutePath() + File.separator + AmbariMetaInfo.WIDGETS_DESCRIPTOR_FILE_NAME;
+    }*/
+
+    parseUpgradePacks(subDirs);
+    parseServiceDirectories(subDirs);
+    parseRepoFile(subDirs);
+    parseMetaInfoFile();
+    parseRoleCommandOrder();
+  }
+
+  /**
+   * Parse the repository file.
+   *
+   * @param subDirs stack directory sub directories
+   * @throws AmbariException if unable to parse the repository file
+   */
+  private void parseRepoFile(Collection<String> subDirs) throws AmbariException {
+    File repositoryFile;
+
+    if (subDirs.contains(REPOSITORY_FOLDER_NAME)) {
+      repoDir = getAbsolutePath() + File.separator + REPOSITORY_FOLDER_NAME;
+      repositoryFile = new File(getPath()+ File.separator +
+          REPOSITORY_FOLDER_NAME + File.separator + REPOSITORY_FILE_NAME);
+
+      if (repositoryFile.exists()) {
+        try {
+          repoFile = unmarshaller.unmarshal(RepositoryXml.class, repositoryFile);
+        } catch (JAXBException e) {
+          repoFile = new RepositoryXml();
+          repoFile.setValid(false);
+          repoFile.setErrors("Unable to parse repo file at location: " +
+              repositoryFile.getAbsolutePath());
+        }
+      }
+    }
+
+    if (repoFile == null || !repoFile.isValid()) {
+      LOG.warn("No repository information defined for "
+          + ", extensionName=" + getExtensionDirName()
+          + ", extensionVersion=" + getPath()
+          + ", repoFolder=" + getPath() + File.separator + REPOSITORY_FOLDER_NAME);
+    }
+  }
+
+  /**
+   * Parse the stack metainfo file.
+   *
+   * @throws AmbariException if unable to parse the stack metainfo file
+   */
+  private void parseMetaInfoFile() throws AmbariException {
+    File extensionMetaInfoFile = new File(getAbsolutePath()
+        + File.separator + STACK_METAINFO_FILE_NAME);
+
+    //todo: is it ok for this file not to exist?
+    if (extensionMetaInfoFile.exists()) {
+      if (LOG.isDebugEnabled()) {
+        LOG.debug("Reading extension version metainfo from file " + extensionMetaInfoFile.getAbsolutePath());
+      }
+
+      try {
+        metaInfoXml = unmarshaller.unmarshal(ExtensionMetainfoXml.class, extensionMetaInfoFile);
+      } catch (JAXBException e) {
+        metaInfoXml = new ExtensionMetainfoXml();
+        metaInfoXml.setValid(false);
+        metaInfoXml.setErrors("Unable to parse extension metainfo.xml file at location: " +
+            extensionMetaInfoFile.getAbsolutePath());
+      }
+    }
+  }
+
+  /**
+   * Parse the extension's service directories.
+   *
+   * @param subDirs  extension sub directories
+   * @throws AmbariException  if unable to parse the service directories
+   */
+  private void parseServiceDirectories(Collection<String> subDirs) throws AmbariException {
+    Collection<ServiceDirectory> dirs = new HashSet<ServiceDirectory>();
+
+    if (subDirs.contains(ServiceDirectory.SERVICES_FOLDER_NAME)) {
+      String servicesDir = getAbsolutePath() + File.separator + ServiceDirectory.SERVICES_FOLDER_NAME;
+      File baseServiceDir = new File(servicesDir);
+      File[] serviceFolders = baseServiceDir.listFiles(AmbariMetaInfo.FILENAME_FILTER);
+      if (serviceFolders != null) {
+        for (File d : serviceFolders) {
+          if (d.isDirectory()) {
+            try {
+              dirs.add(new StackServiceDirectory(d.getAbsolutePath()));
+            } catch (AmbariException e) {
+              //todo: this seems as though we should propagate this exception
+              //todo: eating it now to keep backwards compatibility
+              LOG.warn(String.format("Unable to parse extension definition service at '%s'.  Ignoring service. : %s",
+                  d.getAbsolutePath(), e.toString()));
+            }
+          }
+        }
+      }
+    }
+
+    if (dirs.isEmpty()) {
+      //todo: what does it mean for a extension to have no services?
+      LOG.info("The extension defined at '" + getAbsolutePath() + "' contains no services");
+    }
+    serviceDirectories = dirs;
+  }
+
+  /**
+   * Parse all stack upgrade files for the stack.
+   *
+   * @param subDirs  stack sub directories
+   * @throws AmbariException if unable to parse stack upgrade file
+   */
+  private void parseUpgradePacks(Collection<String> subDirs) throws AmbariException {
+    Map<String, UpgradePack> upgradeMap = new HashMap<String, UpgradePack>();
+    if (subDirs.contains(UPGRADE_PACK_FOLDER_NAME)) {
+      File f = new File(getAbsolutePath() + File.separator + UPGRADE_PACK_FOLDER_NAME);
+      if (f.isDirectory()) {
+        upgradesDir = f.getAbsolutePath();
+        for (File upgradeFile : f.listFiles(XML_FILENAME_FILTER)) {
+          try {
+            upgradeMap.put(FilenameUtils.removeExtension(upgradeFile.getName()),
+                unmarshaller.unmarshal(UpgradePack.class, upgradeFile));
+          } catch (JAXBException e) {
+            throw new AmbariException("Unable to parse stack upgrade file at location: " +
+                upgradeFile.getAbsolutePath(), e);
+          }
+        }
+      }
+    }
+
+    if (upgradesDir == null) {
+      LOG.info("Extension '{}' doesn't contain an upgrade directory ", getPath());
+    }
+
+    if (! upgradeMap.isEmpty()) {
+      upgradePacks = upgradeMap;
+    }
+  }
+
+  /**
+   * Parse role command order file
+   */
+  private void parseRoleCommandOrder() {
+    HashMap<String, Object> result = null;
+    ObjectMapper mapper = new ObjectMapper();
+    try {
+      TypeReference<Map<String, Object>> rcoElementTypeReference = new TypeReference<Map<String, Object>>() {};
+      if (rcoFilePath != null) {
+        File file = new File(rcoFilePath);
+        result = mapper.readValue(file, rcoElementTypeReference);
+        LOG.info("Role command order info was loaded from file: {}", file.getAbsolutePath());
+      } else {
+        result = new HashMap<String, Object>();
+        /*InputStream rcoInputStream = ClassLoader.getSystemResourceAsStream(ROLE_COMMAND_ORDER_FILE);
+        result = mapper.readValue(rcoInputStream, rcoElementTypeReference);
+        LOG.info("Role command order info was loaded from classpath: " +
+            ClassLoader.getSystemResource(ROLE_COMMAND_ORDER_FILE));*/
+      }
+      roleCommandOrder = new StackRoleCommandOrder(result);
+      //parseRoleCommandOrdersForServices();
+      /*if (LOG.isDebugEnabled()) {
+        LOG.debug("Role Command Order for " + rcoFilePath);
+        roleCommandOrder.printRoleCommandOrder(LOG);
+      }*/
+    } catch (IOException e) {
+      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/647929db/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionHelper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionHelper.java
new file mode 100644
index 0000000..7c3277c
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionHelper.java
@@ -0,0 +1,168 @@
+/**
+ * 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 org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.ServiceNotFoundException;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+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.ExtensionMetainfoXml;
+
+public class ExtensionHelper {
+
+  public static void validateDeleteLink(Clusters clusters, StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    validateNotRequiredExtension(stack, extension);
+    validateServicesNotInstalled(clusters, stack, extension);
+  }
+
+  private static void validateServicesNotInstalled(Clusters clusters, StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    for (Cluster cluster : clusters.getClusters().values()) {
+      for (ServiceInfo service : extension.getServices()) {
+        try {
+          if (service != null && cluster.getService(service.getName()) != null) {
+            String message = "Extension service is still installed"
+                        + ", stackName=" + stack.getName()
+                        + ", stackVersion=" + stack.getVersion()
+                        + ", service=" + service.getName()
+                        + ", extensionName=" + extension.getName()
+                        + ", extensionVersion=" + extension.getVersion();
+
+            throw new AmbariException(message);
+          }
+        }
+        catch (ServiceNotFoundException e) {
+          //Eat the exception
+        }
+      }
+    }
+  }
+
+  public static void validateCreateLink(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    validateSupportedStackVersion(stack, extension);
+    validateServiceDuplication(stack, extension);
+    validateRequiredExtensions(stack, extension);
+  }
+
+  private static void validateSupportedStackVersion(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    for (ExtensionMetainfoXml.Stack validStack : extension.getStacks()) {
+      if (validStack.getName().equals(stack.getName())) {
+        String version = validStack.getVersion();
+        if (version.endsWith("*")) {
+          version = version.substring(0, version.length() - 1);
+          if (stack.getVersion().startsWith(version)) {
+            //Found a supported stack version
+            return;
+          }
+        }
+        else if (stack.getVersion().equals(version)) {
+          //Found a supported stack version
+          return;
+        }
+      }
+    }
+
+    String message = "Stack is not supported by extension"
+		+ ", stackName=" + stack.getName()
+		+ ", stackVersion=" + stack.getVersion()
+		+ ", extensionName=" + extension.getName()
+		+ ", extensionVersion=" + extension.getVersion();
+
+    throw new AmbariException(message);
+  }
+
+  private static void validateServiceDuplication(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    for (ServiceInfo service : extension.getServices()) {
+      if (service != null) {
+        ServiceInfo stackService = null;
+        try {
+          stackService = stack.getService(service.getName());
+        }
+        catch (Exception e) {
+          //Eat the exception
+        }
+        if (stackService != null) {
+          String message = "Existing service is included in extension"
+                      + ", stackName=" + stack.getName()
+                      + ", stackVersion=" + stack.getVersion()
+                      + ", service=" + service.getName()
+                      + ", extensionName=" + extension.getName()
+                      + ", extensionVersion=" + extension.getVersion();
+
+          throw new AmbariException(message);
+        }
+      }
+    }
+  }
+
+  private static void validateRequiredExtensions(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    for (ExtensionMetainfoXml.Extension requiredExtension : extension.getExtensions()) {
+      if (requiredExtension != null) {
+        String message = "Stack has not linked required extension"
+                    + ", stackName=" + stack.getName()
+                    + ", stackVersion=" + stack.getVersion()
+                    + ", extensionName=" + extension.getName()
+                    + ", extensionVersion=" + extension.getVersion()
+                    + ", requiredExtensionName=" + requiredExtension.getName()
+                    + ", requiredExtensionVersion=" + requiredExtension.getVersion();
+        try {
+          ExtensionInfo stackExtension = stack.getExtension(requiredExtension.getName());
+          if (stackExtension != null) {
+            String version = requiredExtension.getVersion();
+            if (version.endsWith("*")) {
+              version = version.substring(0, version.length() - 1);
+              if (!stackExtension.getVersion().startsWith(version)) {
+                throw new AmbariException(message);
+              }
+            }
+            else if (!stackExtension.getVersion().equals(version)) {
+              throw new AmbariException(message);
+            }
+          }
+        }
+        catch (Exception e) {
+          throw new AmbariException(message, e);
+        }
+      }
+    }
+  }
+
+  private static void validateNotRequiredExtension(StackInfo stack, ExtensionInfo extension) throws AmbariException {
+    for (ExtensionInfo stackExtension : stack.getExtensions()) {
+      if (stackExtension != null) {
+        for (ExtensionMetainfoXml.Extension requiredExtension : stackExtension.getExtensions()) {
+          if (requiredExtension != null && requiredExtension.getName().equals(extension.getName())) {
+            String message = "Stack extension is required by extension"
+                        + ", stackName=" + stack.getName()
+                        + ", stackVersion=" + stack.getVersion()
+                        + ", extensionName=" + extension.getName()
+                        + ", extensionVersion=" + extension.getVersion()
+                        + ", dependentExtensionName=" + stackExtension.getName()
+                        + ", dependentExtensionVersion=" + stackExtension.getVersion();
+
+            throw new AmbariException(message);
+          }
+        }
+      }
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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..c4124c5
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/ExtensionModule.java
@@ -0,0 +1,714 @@
+/**
+ * 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.
+ *
+ * <p>
+ * Each extension node is identified by name and version, contains service and configuration
+ * 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 configuration and services children of the extension as well as merging of other extension
+ * state with the fully resolved parent.
+ * </p>
+ *
+ * <p>
+ * Configuration child node resolution involves merging configuration types, properties and attributes
+ * 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);
+    }
+    processRepositories();
+    //processPropertyDependencies();
+    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)) {
+      mergeServiceWithCommonServiceParent(service, parent, allStacks, commonServices, extensions);
+    } 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 + "'";
+      setErrors(error);
+      extensionInfo.setErrors(error);
+    } else {
+      if (baseService.isValid()) {
+        service.resolve(baseService, allStacks, commonServices, extensions);
+      } else {
+        setValid(false);
+        extensionInfo.setValid(false);
+        setErrors(baseService.getErrors());
+        extensionInfo.setErrors(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.setErrors(emx.getErrors());
+      }
+      /*extensionInfo.setMinUpgradeVersion(smx.getVersion().getUpgrade());
+      extensionInfo.setActive(smx.getVersion().isActive());*/
+      extensionInfo.setParentExtensionVersion(emx.getExtends());
+      extensionInfo.setRcoFileLocation(extensionDirectory.getRcoFilePath());
+      /*extensionInfo.setKerberosDescriptorFileLocation(extensionDirectory.getKerberosDescriptorFilePath());
+      extensionInfo.setWidgetsDescriptorFileLocation(extensionDirectory.getWidgetsDescriptorFilePath());*/
+      extensionInfo.setUpgradesFolder(extensionDirectory.getUpgradesDir());
+      extensionInfo.setUpgradePacks(extensionDirectory.getUpgradePacks());
+      extensionInfo.setRoleCommandOrder(extensionDirectory.getRoleCommandOrder());
+      extensionInfo.setStacks(emx.getStacks());
+      extensionInfo.setExtensions(emx.getExtensions());
+      //populateConfigurationModules();
+    }
+
+    try {
+      //configurationModules
+      RepositoryXml rxml = extensionDirectory.getRepoFile();
+      if (rxml != null && !rxml.isValid()) {
+        extensionInfo.setValid(false);
+        extensionInfo.setErrors(rxml.getErrors());
+      }
+      // Read the service and available configs for this extension
+      populateServices();
+      if (!extensionInfo.isValid()) {
+        setValid(false);
+        setErrors(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);
+      setErrors(error);
+      extensionInfo.setErrors(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.setErrors(metaInfoXml.getErrors());
+      setErrors(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.setErrors(serviceModule.getErrors());
+        setErrors(serviceModule.getErrors());
+      }
+    }
+    addServices(serviceModules);
+  }
+
+  /**
+   * Populate the child configurations.
+   */
+  /*private void populateConfigurationModules() {
+    //todo: can't exclude types in extension config
+    ConfigurationDirectory configDirectory = extensionDirectory.getConfigurationDirectory(
+        AmbariMetaInfo.SERVICE_CONFIG_FOLDER_NAME);
+
+    if (configDirectory != null) {
+      for (ConfigurationModule config : configDirectory.getConfigurationModules()) {
+        if (extensionInfo.isValid()){
+          extensionInfo.setValid(config.isValid());
+          extensionInfo.setErrors(config.getErrors());
+        }
+        extensionInfo.getProperties().addAll(config.getModuleInfo().getProperties());
+        extensionInfo.setConfigTypeAttributes(config.getConfigType(), config.getModuleInfo().getAttributes());
+        configurationModules.put(config.getConfigType(), config);
+      }
+    }
+  }*/
+
+  /**
+   * Merge configurations with the parent configurations.
+   *
+   * @param parent          parent extension module
+   * @param allStacks       all stacks in stack definition
+   * @param commonServices  all common services
+   * @param extensions      all extensions
+   */
+  /*private void mergeConfigurations(
+      ExtensionModule parent, Map<String,ExtensionModule> allStacks, Map<String, ServiceModule> commonServices, Map<String, ExtensionModule> extensions)
+      throws AmbariException {
+    extensionInfo.getProperties().clear();
+    extensionInfo.setAllConfigAttributes(new HashMap<String, Map<String, Map<String, String>>>());
+
+    Collection<ConfigurationModule> mergedModules = mergeChildModules(
+        allStacks, commonServices, configurationModules, parent.configurationModules);
+    for (ConfigurationModule module : mergedModules) {
+      configurationModules.put(module.getId(), module);
+      extensionInfo.getProperties().addAll(module.getModuleInfo().getProperties());
+      extensionInfo.setConfigTypeAttributes(module.getConfigType(), module.getModuleInfo().getAttributes());
+    }
+  }*/
+
+  /**
+   * 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());
+      setErrors(parentExtension.getErrors());
+      extensionInfo.setErrors(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) {
+      serviceInfo.setExtensionService(true);
+      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);
+    }
+  }
+
+  /**
+   * Process <depends-on></depends-on> properties
+   */
+  private void processPropertyDependencies() {
+
+    // Extension-definition has 'depends-on' relationship specified.
+    // We have a map to construct the 'depended-by' relationship.
+    Map<PropertyDependencyInfo, Set<PropertyDependencyInfo>> dependedByMap = new HashMap<PropertyDependencyInfo, Set<PropertyDependencyInfo>>();
+
+    // Go through all service-configs and gather the reversed 'depended-by'
+    // relationship into map. Since we do not have the reverse {@link PropertyInfo},
+    // we have to loop through service-configs again later.
+    for (ServiceModule serviceModule : serviceModules.values()) {
+      for (PropertyInfo pi : serviceModule.getModuleInfo().getProperties()) {
+        for (PropertyDependencyInfo pdi : pi.getDependsOnProperties()) {
+          String type = ConfigHelper.fileNameToConfigType(pi.getFilename());
+          String name = pi.getName();
+          PropertyDependencyInfo propertyDependency =
+            new PropertyDependencyInfo(type, name);
+          if (dependedByMap.keySet().contains(pdi)) {
+            dependedByMap.get(pdi).add(propertyDependency);
+          } else {
+            Set<PropertyDependencyInfo> newDependenciesSet =
+              new HashSet<PropertyDependencyInfo>();
+            newDependenciesSet.add(propertyDependency);
+            dependedByMap.put(pdi, newDependenciesSet);
+          }
+        }
+      }
+    }
+
+    // Go through all service-configs again and set their 'depended-by' if necessary.
+    for (ServiceModule serviceModule : serviceModules.values()) {
+      for (PropertyInfo pi : serviceModule.getModuleInfo().getProperties()) {
+        String type = ConfigHelper.fileNameToConfigType(pi.getFilename());
+        String name = pi.getName();
+        Set<PropertyDependencyInfo> set =
+          dependedByMap.remove(new PropertyDependencyInfo(type, name));
+        if (set != null) {
+          pi.getDependedByProperties().addAll(set);
+        }
+      }
+    }
+  }
+
+  /**
+   * Process repositories associated with the extension.
+   * @throws AmbariException if unable to fully process the extension repositories
+   */
+  private void processRepositories() throws AmbariException {
+    RepositoryXml rxml = extensionDirectory.getRepoFile();
+    if (rxml == null) {
+      return;
+    }
+
+    LOG.debug("Adding repositories to extension" +
+        ", extensionName=" + extensionInfo.getName() +
+        ", extensionVersion=" + extensionInfo.getVersion() +
+        ", repoFolder=" + extensionDirectory.getRepoDir());
+
+    //extensionInfo.setMainRepositoryId(rxml.getMainRepoId());
+
+    List<RepositoryInfo> repos = new ArrayList<RepositoryInfo>();
+
+    for (RepositoryXml.Os o : rxml.getOses()) {
+      String osFamily = o.getFamily();
+      for (String os : osFamily.split(",")) {
+        for (RepositoryXml.Repo r : o.getRepos()) {
+          repos.add(processRepository(osFamily, os, r));
+        }
+      }
+    }
+
+    extensionInfo.getRepositories().addAll(repos);
+
+    if (null != rxml.getLatestURI() && repos.size() > 0) {
+      stackContext.registerRepoUpdateTask(rxml.getLatestURI(), this);
+    }
+  }
+
+  /**
+   * Process a repository associated with the extension.
+   *
+   * @param osFamily  OS family
+   * @param osType    OS type
+   * @param r         repo
+   */
+  private RepositoryInfo processRepository(String osFamily, String osType, RepositoryXml.Repo r) {
+    RepositoryInfo ri = new RepositoryInfo();
+    ri.setBaseUrl(r.getBaseUrl());
+    ri.setDefaultBaseUrl(r.getBaseUrl());
+    ri.setMirrorsList(r.getMirrorsList());
+    ri.setOsType(osType.trim());
+    ri.setRepoId(r.getRepoId());
+    ri.setRepoName(r.getRepoName());
+    ri.setLatestBaseUrl(r.getBaseUrl());
+
+    LOG.debug("Checking for override for base_url");
+    String updatedUrl = stackContext.getUpdatedRepoUrl(extensionInfo.getName(), extensionInfo.getVersion(),
+        osFamily, r.getRepoId());
+
+    if (null != updatedUrl) {
+      ri.setBaseUrl(updatedUrl);
+      ri.setBaseUrlFromSaved(true);
+    }
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Adding repo to extension"
+          + ", repoInfo=" + ri.toString());
+    }
+    return ri;
+  }
+
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  @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);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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..617c9f0 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,8 @@ 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.ExtensionsXml;
 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 +29,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;
@@ -57,6 +61,12 @@ class ModuleFileUnmarshaller {
     return clz.cast(u.unmarshal(file));
   }
 
+  public <T> void marshal(T object, File file) throws JAXBException {
+    Marshaller m = jaxbContexts.get(object.getClass()).createMarshaller();
+
+    m.marshal(object, file);
+  }
+
   /**
    * statically register the JAXB contexts
    */
@@ -72,6 +82,8 @@ 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));
+      jaxbContexts.put(ExtensionsXml.class, JAXBContext.newInstance(ExtensionsXml.class));
     } catch (JAXBException e) {
       throw new RuntimeException (e);
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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 d574d60..930fa5e 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;
 
@@ -55,6 +61,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>();
@@ -104,29 +120,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 themeFile = new File(directory.getAbsolutePath() + File.separator + AmbariMetaInfo.SERVICE_THEME_FILE_NAME);
-    this.themeFile = themeFile.exists() ? themeFile : null;
   }
 
   /**
@@ -193,9 +186,53 @@ 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 {
+    calculatePackageDir();
+    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.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 themeFile = new File(directory.getAbsolutePath() + File.separator + AmbariMetaInfo.SERVICE_THEME_FILE_NAME);
+    this.themeFile = themeFile.exists() ? themeFile : null;
+  }
+
+  /**
+   * Parse the service directory.
+   */
+  protected abstract void calculatePackageDir();
 
   /**
    * Unmarshal the metainfo file into its object representation.
@@ -203,7 +240,7 @@ public abstract class ServiceDirectory extends StackDefinitionDirectory {
    * @throws AmbariException if the metainfo file doesn't exist or
    *                         unable to unmarshal the metainfo file
    */
-  protected void parseMetaInfoFile() throws AmbariException {
+  private void parseMetaInfoFile() throws AmbariException {
     File f = new File(getAbsolutePath() + File.separator + SERVICE_METAINFO_FILE_NAME);
     if (! f.exists()) {
       throw new AmbariException(String.format("Stack Definition Service at '%s' doesn't contain a metainfo.xml file",
@@ -222,4 +259,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/647929db/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 c2a2a0c..703f70a 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
@@ -123,6 +123,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());
 
@@ -140,7 +141,7 @@ 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 {
 
     if (!serviceInfo.isValid() || !parentModule.isValid())
@@ -198,15 +199,17 @@ public class ServiceModule extends BaseModule<ServiceModule, ServiceInfo> implem
     if (serviceInfo.getWidgetsDescriptorFile() == null) {
       serviceInfo.setWidgetsDescriptorFile(parent.getWidgetsDescriptorFile());
     }
+    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);
+    mergeComponents(parentModule, allStacks, commonServices, extensions);
+    mergeConfigurations(parentModule, allStacks, commonServices, extensions);
+    mergeThemes(parentModule, allStacks, commonServices, extensions);
     mergeExcludedConfigTypes(parent);
 
-
     mergeServiceProperties(parent.getServicePropertyList());
 
   }
@@ -255,7 +258,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");
@@ -273,12 +276,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");
         }
-        resolve(baseService, allStacks, commonServices);
+        resolve(baseService, allStacks, commonServices, extensions);
       } else {
         throw new AmbariException("Common service cannot inherit from a non common service");
       }
@@ -373,8 +376,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);
@@ -441,13 +444,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);
@@ -467,11 +470,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/647929db/ambari-server/src/main/java/org/apache/ambari/server/stack/StackContext.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackContext.java b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackContext.java
index da7f021..df2db3f 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackContext.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackContext.java
@@ -22,6 +22,8 @@ import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.metadata.ActionMetadata;
 import org.apache.ambari.server.orm.dao.MetainfoDAO;
 import org.apache.ambari.server.orm.entities.MetainfoEntity;
+import org.apache.ambari.server.state.stack.AbstractLatestRepoCallable;
+import org.apache.ambari.server.state.stack.LatestExtensionRepoCallable;
 import org.apache.ambari.server.state.stack.LatestRepoCallable;
 import org.apache.ambari.server.state.stack.OsFamily;
 
@@ -111,7 +113,18 @@ public class StackContext {
    */
   public void registerRepoUpdateTask(String url, StackModule stack) {
     repoUpdateExecutor.addTask(new LatestRepoCallable(url,
-        new File(stack.getStackDirectory().getRepoDir()), stack.getModuleInfo(), osFamily));
+        new File(stack.getStackDirectory().getRepoDir()), osFamily, stack.getModuleInfo()));
+  }
+
+  /**
+   * Register a task to obtain the latest repo url from an external location.
+   *
+   * @param url        external repo information URL
+   * @param extension  extension module
+   */
+  public void registerRepoUpdateTask(String url, ExtensionModule extension) {
+    repoUpdateExecutor.addTask(new LatestExtensionRepoCallable(url,
+        new File(extension.getExtensionDirectory().getRepoDir()), osFamily, extension.getModuleInfo()));
   }
 
   /**
@@ -139,7 +152,7 @@ public class StackContext {
     /**
      * Registered tasks
      */
-    private Collection<LatestRepoCallable> tasks = new ArrayList<LatestRepoCallable>();
+    private Collection<AbstractLatestRepoCallable> tasks = new ArrayList<AbstractLatestRepoCallable>();
 
     /**
      * Task futures
@@ -161,7 +174,7 @@ public class StackContext {
      *
      * @param task task to be added
      */
-    public void addTask(LatestRepoCallable task) {
+    public void addTask(AbstractLatestRepoCallable task) {
       tasks.add(task);
     }
 
@@ -169,7 +182,7 @@ public class StackContext {
      * Execute all tasks.
      */
     public void execute() {
-      for (LatestRepoCallable task : tasks) {
+      for (AbstractLatestRepoCallable task : tasks) {
         futures.add(executor.submit(task));
       }
       executor.shutdown();

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/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.


Mime
View raw message