This is an automated email from the ASF dual-hosted git repository.

jluniya pushed a commit to branch branch-2.7
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/branch-2.7 by this push:
     new cd98499  AMBARI-24764: StackMerger utility tool for flattening stack 
definitons (#2444)
cd98499 is described below

commit cd9849914accd4b696df028df18238cb66d4c93f
Author: jayush <[email protected]>
AuthorDate: Fri Oct 26 10:32:30 2018 -0700

    AMBARI-24764: StackMerger utility tool for flattening stack definitons 
(#2444)
    
    * AMBARI-24764: StackMerger utility tool for flattening stack definitions 
(jluniya)
    
    * AMBARI-24764: StackMerger utility tool - update usage and address CR 
feedback (jluniya)
    
    * AMBARI-24764: StackMerger utility tool - Update MSFT-R service advisor 
(jluniya)
    
    * AMBARI-24764: StackMerger utility tool - console logging (jluniya)
---
 ambari-server/conf/unix/log4j.properties           |  11 +
 ambari-server/conf/windows/log4j.properties        |  11 +
 ambari-server/src/main/conf/log4j.properties       |  11 +
 .../ambari/server/api/services/AmbariMetaInfo.java |   4 +
 .../server/stack/CommonServiceDirectory.java       |   3 +-
 .../ambari/server/stack/ServiceDirectory.java      |   4 +-
 .../apache/ambari/server/stack/StackDirectory.java |   2 +-
 .../apache/ambari/server/stack/StackManager.java   |   2 +-
 .../apache/ambari/server/stack/StackMerger.java    | 606 +++++++++++++++++++++
 .../apache/ambari/server/state/AutoDeployInfo.java |   2 +-
 .../apache/ambari/server/state/ComponentInfo.java  |   2 +
 .../server/state/DependencyConditionInfo.java      |   1 +
 .../apache/ambari/server/state/DependencyInfo.java |   2 +-
 .../apache/ambari/server/state/PropertyInfo.java   |   3 +
 .../apache/ambari/server/state/ServiceInfo.java    |  54 +-
 .../server/state/stack/ConfigurationXml.java       |  11 +
 .../server/state/stack/StackMetainfoXml.java       |  31 +-
 .../AMBARI_INFRA_SOLR/0.1.0/service_advisor.py     |   2 +
 .../AMBARI_METRICS/0.1.0/service_advisor.py        |   2 +
 .../common-services/HAWQ/2.0.0/service_advisor.py  |   2 +
 .../LOGSEARCH/0.5.0/service_advisor.py             |   2 +
 .../common-services/PXF/3.0.0/service_advisor.py   |   2 +
 .../ZEPPELIN/0.7.0/service_advisor.py              |   2 +
 .../ZOOKEEPER/3.4.9/service_advisor.py             |   2 +
 .../src/main/resources/scripts/stack_advisor.py    |   6 +
 .../stacks/HDP/2.1/services/stack_advisor.py       |   5 +
 .../stacks/HDP/2.2/services/stack_advisor.py       |   5 +
 .../stacks/HDP/2.3/services/stack_advisor.py       |   5 +
 .../stacks/HDP/2.4/services/stack_advisor.py       |   5 +
 .../stacks/HDP/2.5/services/stack_advisor.py       |   5 +
 .../src/main/resources/stacks/HDP/2.6/metainfo.xml |   2 +
 .../stacks/HDP/2.6/services/stack_advisor.py       |   5 +
 .../MICROSOFT_R_SERVER/8.0.5/service_advisor.py    |   2 +
 33 files changed, 803 insertions(+), 11 deletions(-)

diff --git a/ambari-server/conf/unix/log4j.properties 
b/ambari-server/conf/unix/log4j.properties
index 06968e4..78d4171 100644
--- a/ambari-server/conf/unix/log4j.properties
+++ b/ambari-server/conf/unix/log4j.properties
@@ -26,6 +26,7 @@ ambari.alerts.file=ambari-alerts.log
 ambari.eclipselink.file=ambari-eclipselink.log
 ambari.audit.file=ambari-audit.log
 ambari.dbcheck.file=ambari-server-check-database.log
+ambari.stackmerger.file=ambari-stack-merger.log
 
 log4j.rootLogger=INFO,file
 
@@ -68,6 +69,16 @@ 
log4j.appender.dbcheckhelper.File=${ambari.log.dir}/${ambari.dbcheck.file}
 log4j.appender.dbcheckhelper.layout=org.apache.log4j.PatternLayout
 log4j.appender.dbcheckhelper.layout.ConversionPattern=%d{ISO8601} %5p - %m%n
 
+# Log stack merger
+log4j.logger.org.apache.ambari.server.stack.StackMerger=INFO,stackmerger,console-stackmerger
+log4j.additivity.org.apache.ambari.server.stack.StackMerger=false
+log4j.appender.console-stackmerger=org.apache.log4j.ConsoleAppender
+log4j.appender.console-stackmerger.layout=org.apache.log4j.PatternLayout
+log4j.appender.stackmerger=org.apache.log4j.FileAppender
+log4j.appender.stackmerger.File=${ambari.log.dir}/${ambari.stackmerger.file}
+log4j.appender.stackmerger.layout=org.apache.log4j.PatternLayout
+log4j.appender.stackmerger.layout.ConversionPattern=%d{ISO8601} %5p - %m%n
+
 # EclipsLink -> slf4j bridge
 log4j.logger.eclipselink=TRACE,eclipselink
 log4j.additivity.eclipselink=false
diff --git a/ambari-server/conf/windows/log4j.properties 
b/ambari-server/conf/windows/log4j.properties
index 4b768f3..13a3180 100644
--- a/ambari-server/conf/windows/log4j.properties
+++ b/ambari-server/conf/windows/log4j.properties
@@ -25,6 +25,7 @@ ambari.alerts.file=ambari-alerts.log
 ambari.eclipselink.file=ambari-eclipselink.log
 ambari.audit.file=ambari-audit.log
 ambari.dbcheck.file=ambari-server-check-database.log
+ambari.stackmerger.file=ambari-stack-merger.log
 
 # Define the root logger to the system property "ambari.root.logger".
 log4j.rootLogger=${ambari.root.logger}
@@ -93,6 +94,16 @@ 
log4j.appender.dbcheckhelper.File=${ambari.log.dir}/${ambari.dbcheck.file}
 log4j.appender.dbcheckhelper.layout=org.apache.log4j.PatternLayout
 log4j.appender.dbcheckhelper.layout.ConversionPattern=%d{ISO8601} %5p - %m%n
 
+# Log stack merger
+log4j.logger.org.apache.ambari.server.stack.StackMerger=INFO,stackmerger,console-stackmerger
+log4j.additivity.org.apache.ambari.server.stack.StackMerger=false
+log4j.appender.console-stackmerger=org.apache.log4j.ConsoleAppender
+log4j.appender.console-stackmerger.layout=org.apache.log4j.PatternLayout
+log4j.appender.stackmerger=org.apache.log4j.FileAppender
+log4j.appender.stackmerger.File=${ambari.log.dir}/${ambari.stackmerger.file}
+log4j.appender.stackmerger.layout=org.apache.log4j.PatternLayout
+log4j.appender.stackmerger.layout.ConversionPattern=%d{ISO8601} %5p - %m%n
+
 # EclipsLink -> slf4j bridge
 log4j.logger.eclipselink=TRACE,eclipselink
 log4j.additivity.eclipselink=false
diff --git a/ambari-server/src/main/conf/log4j.properties 
b/ambari-server/src/main/conf/log4j.properties
index 680b4c4..73fc92f 100644
--- a/ambari-server/src/main/conf/log4j.properties
+++ b/ambari-server/src/main/conf/log4j.properties
@@ -25,6 +25,7 @@ ambari.alerts.file=ambari-alerts.log
 ambari.eclipselink.file=ambari-eclipselink.log
 ambari.audit.file=ambari-audit.log
 ambari.dbcheck.file=ambari-server-check-database.log
+ambari.stackmerger.file=ambari-stack-merger.log
 
 # Define the root logger to the system property "ambari.root.logger".
 log4j.rootLogger=${ambari.root.logger}
@@ -93,6 +94,16 @@ 
log4j.appender.dbcheckhelper.File=${ambari.log.dir}/${ambari.dbcheck.file}
 log4j.appender.dbcheckhelper.layout=org.apache.log4j.PatternLayout
 log4j.appender.dbcheckhelper.layout.ConversionPattern=%d{ISO8601} %5p - %m%n
 
+# Log stack merger
+log4j.logger.org.apache.ambari.server.stack.StackMerger=INFO,stackmerger,console-stackmerger
+log4j.additivity.org.apache.ambari.server.stack.StackMerger=false
+log4j.appender.console-stackmerger=org.apache.log4j.ConsoleAppender
+log4j.appender.console-stackmerger.layout=org.apache.log4j.PatternLayout
+log4j.appender.stackmerger=org.apache.log4j.FileAppender
+log4j.appender.stackmerger.File=${ambari.log.dir}/${ambari.stackmerger.file}
+log4j.appender.stackmerger.layout=org.apache.log4j.PatternLayout
+log4j.appender.stackmerger.layout.ConversionPattern=%d{ISO8601} %5p - %m%n
+
 # EclipsLink -> slf4j bridge
 log4j.logger.eclipselink=TRACE,eclipselink
 log4j.additivity.eclipselink=false
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
index 9f0a43b..85e25f6 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/AmbariMetaInfo.java
@@ -860,6 +860,10 @@ public class AmbariMetaInfo {
     return stackRoot;
   }
 
+  public File getCommonServicesRoot() {
+    return commonServicesRoot;
+  }
+
   public File getExtensionsRoot() {
     return extensionsRoot;
   }
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 80b0b7f..da06de3 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
@@ -44,8 +44,9 @@ public class CommonServiceDirectory extends ServiceDirectory {
    */
   @Override
   public String getAdvisorName(String serviceName) {
-    if (getAdvisorFile() == null || serviceName == null)
+    if (getAdvisorFile() == null || serviceName == null) {
       return null;
+    }
 
     File serviceVersionDir = new File(getAbsolutePath());
     String serviceVersion = serviceVersionDir.getName().replaceAll("\\.", "");
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 7464e61..a303170 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
@@ -110,12 +110,12 @@ public abstract class ServiceDirectory extends 
StackDefinitionDirectory {
   /**
    * package directory name
    */
-  protected static final String PACKAGE_FOLDER_NAME = "package";
+  public static final String PACKAGE_FOLDER_NAME = "package";
 
   /**
    * upgrades directory name
    */
-  protected static final String UPGRADES_FOLDER_NAME = "upgrades";
+  public static final String UPGRADES_FOLDER_NAME = "upgrades";
 
   /**
    * checks directory name
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 daf8e7c..4ad9be2 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
@@ -145,7 +145,7 @@ public class StackDirectory extends 
StackDefinitionDirectory {
   /**
    * upgrades directory name
    */
-  private static final String UPGRADE_PACK_FOLDER_NAME = "upgrades";
+  public static final String UPGRADE_PACK_FOLDER_NAME = "upgrades";
 
   /**
    * role command order file name
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 3473fe8..6ff04d4 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
@@ -563,7 +563,7 @@ public class StackManager {
   private Map<String, ServiceModule> parseCommonServicesDirectory(File 
commonServicesRoot) throws AmbariException {
     Map<String, ServiceModule> commonServiceModules = new HashMap<>();
 
-    if(commonServicesRoot != null) {
+    if(commonServicesRoot != null && commonServicesRoot.exists()) {
       File[] commonServiceFiles = 
commonServicesRoot.listFiles(StackDirectory.FILENAME_FILTER);
       for (File commonService : commonServiceFiles) {
         if (commonService.isFile()) {
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/stack/StackMerger.java 
b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackMerger.java
new file mode 100644
index 0000000..96f211d
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/stack/StackMerger.java
@@ -0,0 +1,606 @@
+/**
+ * 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
+ * <p>
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * <p>
+ * 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.io.BufferedWriter;
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.StringWriter;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardCopyOption;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+
+import javax.xml.bind.JAXBContext;
+import javax.xml.bind.Marshaller;
+
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.audit.AuditLoggerModule;
+import org.apache.ambari.server.controller.ControllerModule;
+import org.apache.ambari.server.ldap.LdapModule;
+import org.apache.ambari.server.orm.DBAccessor;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.state.PropertyInfo;
+import org.apache.ambari.server.state.QuickLinksConfigurationInfo;
+import org.apache.ambari.server.state.ServiceInfo;
+import org.apache.ambari.server.state.StackId;
+import org.apache.ambari.server.state.StackInfo;
+import org.apache.ambari.server.state.ThemeInfo;
+import org.apache.ambari.server.state.quicklinks.QuickLinks;
+import org.apache.ambari.server.state.stack.ConfigurationXml;
+import org.apache.ambari.server.state.stack.RepositoryXml;
+import org.apache.ambari.server.state.stack.ServiceMetainfoXml;
+import org.apache.ambari.server.state.stack.StackMetainfoXml;
+import org.apache.ambari.server.state.stack.StackRoleCommandOrder;
+import org.apache.ambari.server.state.theme.Theme;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.StringUtils;
+import org.codehaus.jackson.map.ObjectMapper;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
+
+/**
+ * Utility tool for merging stack hierarchy and generate flattened stack 
definitions for legal stacks
+ */
+public class StackMerger {
+
+  private static final String MERGED_STACKS_ROOT = "mergedStacksRoot";
+  private static final String STACKS_ARG = "stacks";
+
+  private static final Logger LOG = LoggerFactory.getLogger
+    (StackMerger.class);
+
+  private PersistService persistService;
+  private DBAccessor dbAccessor;
+  private Injector injector;
+
+  private static final ObjectMapper mapper = new ObjectMapper();
+  private File commonServicesRoot;
+  private File stackRoot;
+  private StackManager stackManager;
+  private Gson gson = new GsonBuilder().setPrettyPrinting().create();
+
+  @Inject
+  public StackMerger(Injector injector) throws Exception {
+    this.injector = injector;
+    AmbariMetaInfo metaInfo = injector.getInstance(AmbariMetaInfo.class);
+    metaInfo.init();
+    this.stackRoot = metaInfo.getStackRoot();
+    this.commonServicesRoot = metaInfo.getCommonServicesRoot();
+    this.stackManager = metaInfo.getStackManager();
+  }
+
+  /**
+   * Extension of audit logger module
+   */
+  public static class StackMergerAuditModule extends AuditLoggerModule {
+
+    public StackMergerAuditModule() throws Exception {
+    }
+
+    @Override
+    protected void configure() {
+      super.configure();
+    }
+  }
+
+  /**
+   * Context object that encapsulates values passed in as arguments to the 
{@link StackMerger} class.
+   */
+  private static class StackMergerContext {
+    private String mergedStacksRoot;
+    private HashSet<StackId> stackIds;
+
+    public StackMergerContext(String mergedStacksRoot, HashSet<StackId> 
stackIds) {
+      this.stackIds = stackIds;
+      this.mergedStacksRoot = mergedStacksRoot;
+    }
+
+    public HashSet<StackId> getStackIds() {
+      return stackIds;
+    }
+
+    public String getMergedStacksRoot() {
+      return mergedStacksRoot;
+    }
+  }
+
+  private static Options getOptions() {
+    Options options = new Options();
+    options.addOption(Option.builder().longOpt(STACKS_ARG).desc(
+            "Comma-separated list of stacks to be merged and 
exported").required().type(String.class).hasArg().valueSeparator(' ').build());
+    options.addOption(Option.builder().longOpt(MERGED_STACKS_ROOT).desc(
+            "Root directory where the merged stacks should be 
exported").required().type(String.class).hasArg().valueSeparator(' ').build());
+    return options;
+  }
+
+  private static StackMergerContext processArguments(String... args) throws 
Exception {
+    CommandLineParser cmdLineParser = new DefaultParser();
+    HelpFormatter formatter = new HelpFormatter();
+
+    CommandLine line = cmdLineParser.parse(getOptions(), args);
+    String mergedStacksRoot = (String) 
line.getParsedOptionValue(MERGED_STACKS_ROOT);
+    String stacksStr = (String) line.getParsedOptionValue(STACKS_ARG);
+    HashSet<StackId> stackIds = new HashSet<>();
+    for (String s : stacksStr.split(",")) {
+      stackIds.add(new StackId(s));
+    }
+    return new StackMergerContext(mergedStacksRoot, stackIds);
+  }
+
+  public void mergeStacks(StackMergerContext ctx) throws Exception {
+    File mergeRoot = new File(ctx.mergedStacksRoot);
+    for(StackId stackId : ctx.stackIds) {
+      mergeStack(mergeRoot, stackId);
+    }
+  }
+
+  /**
+   * Merge stack hierarchy to create flattened stack definition
+   * @throws Exception
+   */
+  public void mergeStack(File mergeRoot, StackId srcStackId) throws Exception {
+    StackId destStackId = srcStackId;
+    String stackName = destStackId.getStackName();
+    String stackVersion = destStackId.getStackVersion();
+    File mergedStackDir = new File(mergeRoot.getAbsolutePath()
+            + File.separator + stackName + File.separator + stackVersion);
+
+    LOG.info("===========================================================");
+    LOG.info("Source Stacks Root: " + stackRoot);
+    LOG.info("Common Services Root: " + commonServicesRoot);
+    LOG.info("Merged Stacks Root: " + mergeRoot);
+    LOG.info("Source Stack Id: " + srcStackId);
+    LOG.info("Destination Stack Id: " + destStackId);
+    LOG.info("Merged Stack Path " + mergedStackDir);
+    LOG.info("===========================================================");
+
+    // Create merged stack directory
+    if (!mergeRoot.exists()) {
+      mergeRoot.mkdirs();
+    }
+    if (mergedStackDir.exists()) {
+      FileUtils.deleteDirectory(mergedStackDir);
+    }
+    mergedStackDir.mkdirs();
+
+    // Create services directory
+    File servicesDir = new File(mergedStackDir.getAbsolutePath() + 
File.separator + "services");
+    if (servicesDir.exists()) {
+      servicesDir.delete();
+    }
+    servicesDir.mkdir();
+
+    // Export role command order
+    StackInfo srcStackInfo = stackManager.getStack(srcStackId.getStackName(), 
srcStackId.getStackVersion());
+    StackRoleCommandOrder stackRoleCommandOrder = 
srcStackInfo.getRoleCommandOrder();
+    FileWriter stackRCOFile = new FileWriter(
+      mergedStackDir.getAbsolutePath() + File.separator + 
StackDirectory.RCO_FILE_NAME);
+    mapper.writerWithDefaultPrettyPrinter().writeValue(stackRCOFile, 
stackRoleCommandOrder.getContent());
+
+    // Export stack-level configs (example: cluster-env)
+    File stackConfigDir = new File(
+      mergedStackDir.getAbsolutePath() + File.separator + 
StackDirectory.SERVICE_CONFIG_FOLDER_NAME);
+    exportConfigs(srcStackInfo.getProperties(), stackConfigDir);
+
+    // Export stack metainfo.xml
+    StackMetainfoXml stackMetainfoXml = new StackMetainfoXml();
+    stackMetainfoXml.setMinJdk(srcStackInfo.getMinJdk());
+    stackMetainfoXml.setMaxJdk(srcStackInfo.getMaxJdk());
+    // Flattened stack, so set extends to null
+    stackMetainfoXml.setExtendsVersion(null);
+    StackMetainfoXml.Version version = new StackMetainfoXml.Version();
+    version.setActive(srcStackInfo.isActive());
+    version.setUpgrade(srcStackInfo.getMinUpgradeVersion());
+    stackMetainfoXml.setVersion(version);
+
+    JAXBContext ctx = JAXBContext.newInstance(StackMetainfoXml.class);
+    Marshaller marshaller = ctx.createMarshaller();
+    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+    FileOutputStream stackMetainfoFileStream = new FileOutputStream(
+            mergedStackDir.getAbsolutePath() + File.separator + 
"metainfo.xml");
+    marshaller.marshal(stackMetainfoXml, stackMetainfoFileStream);
+    stackMetainfoFileStream.flush();
+    stackMetainfoFileStream.close();
+
+    // Export repoinfo.xml
+    RepositoryXml repositoryXml =  srcStackInfo.getRepositoryXml();
+    ctx = JAXBContext.newInstance(RepositoryXml.class);
+    marshaller = ctx.createMarshaller();
+    marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+    File reposDir = new File(mergedStackDir.getAbsolutePath() + File.separator 
+ "repos");
+    if (!reposDir.exists()) {
+      reposDir.mkdir();
+    }
+    FileOutputStream repoXmlFileStream = new FileOutputStream(
+      reposDir.getAbsolutePath() + File.separator + "repoinfo.xml");
+    marshaller.marshal(repositoryXml, repoXmlFileStream);
+    repoXmlFileStream.flush();
+    repoXmlFileStream.close();
+
+    // Copy cluster property files (example: 
stacks/HDP/2.0.6/properties/stack_tools.json)
+    File destPropertiesDir = new File(mergedStackDir.getAbsoluteFile() + 
File.separator + "properties");
+    if(!destPropertiesDir.exists()) {
+      destPropertiesDir.mkdir();
+    }
+
+    String srcStackName = srcStackId.getStackName();
+    String currentStackVersion = srcStackId.getStackVersion();
+    while (!StringUtils.isEmpty(currentStackVersion)) {
+      StackInfo currentStackInfo = stackManager.getStack(srcStackName, 
currentStackVersion);
+      File srcPropertiesDir = new File(stackRoot.getAbsolutePath()
+              + File.separator + srcStackName + File.separator + 
currentStackVersion + File.separator + "properties");
+      if (srcPropertiesDir.exists() && srcPropertiesDir.isDirectory()) {
+        for (File srcPropertiesFile : srcPropertiesDir.listFiles()) {
+          File destPropertiesFile = new 
File(destPropertiesDir.getAbsolutePath()
+                  + File.separator + srcPropertiesFile.getName());
+          if (!destPropertiesFile.exists()) {
+            FileUtils.copyFile(srcPropertiesFile, destPropertiesFile);
+          }
+        }
+      }
+      currentStackVersion = currentStackInfo.getParentStackVersion();
+    }
+
+    // Copy Kerberos pre-configuration file (example: 
stacks/HDP/2.6/kerberos_preconfigure.json
+    if(srcStackInfo.getKerberosDescriptorPreConfigurationFileLocation() != 
null) {
+      File srcKerberosPreConfigFile = new 
File(srcStackInfo.getKerberosDescriptorPreConfigurationFileLocation());
+      File destKerberosPreConfigFile = new 
File(mergedStackDir.getAbsoluteFile()
+              + File.separator + 
StackDirectory.KERBEROS_DESCRIPTOR_PRECONFIGURE_FILE_NAME);
+      if (!destKerberosPreConfigFile.exists()) {
+        FileUtils.copyFile(srcKerberosPreConfigFile, 
destKerberosPreConfigFile);
+      }
+    }
+
+    // Copy upgrade packs
+    if(srcStackInfo.getUpgradesFolder() != null) {
+      File srcUpgradesFile = new File(srcStackInfo.getUpgradesFolder());
+      File destUpgradesFile = new File(mergedStackDir.getAbsoluteFile()
+              + File.separator + StackDirectory.UPGRADE_PACK_FOLDER_NAME);
+      FileUtils.copyDirectory(srcUpgradesFile, destUpgradesFile);
+    }
+
+    // Export all stack advisors in the stack hierarchy
+    File stackAdvisorsDir = new File(mergedStackDir.getAbsolutePath() + 
File.separator + "stack-advisors");
+    if(!stackAdvisorsDir.exists()) {
+      stackAdvisorsDir.mkdir();
+    }
+
+    currentStackVersion = srcStackId.getStackVersion();
+    String baseStackAdvisor = null;
+    String baseStackAdvisorModule = null;
+    while (!StringUtils.isEmpty(currentStackVersion)) {
+      // Copy all inherited stack advisors from source stack to 
"stack-advisors" folder
+      StackInfo currentStackInfo = stackManager.getStack(srcStackName, 
currentStackVersion);
+      File srcStackAdvisor = new File(stackRoot.getAbsolutePath() + 
File.separator
+              + srcStackName + File.separator + currentStackVersion + 
File.separator + "services" + File.separator
+              + "stack_advisor.py");
+      if(srcStackAdvisor.exists()) {
+        if(baseStackAdvisor == null) {
+          baseStackAdvisor = srcStackName.toUpperCase() + 
currentStackVersion.replace(".", "") + "StackAdvisor";
+          baseStackAdvisorModule = "stack_advisor_" + 
srcStackName.toLowerCase() + currentStackVersion.replace(".", "");
+        }
+        File destStackAdvisor = new File(
+          stackAdvisorsDir.getAbsolutePath() + File.separator + 
"stack_advisor_" + srcStackName.toLowerCase()
+            + currentStackVersion.replace(".", "") + ".py");
+        FileUtils.copyFile(srcStackAdvisor, destStackAdvisor);
+      }
+      currentStackVersion = currentStackInfo.getParentStackVersion();
+    }
+
+    // Define top-level stack advisor for merged stack
+    if(baseStackAdvisor != null) {
+      String topLevelStackAdvisorName = 
destStackId.getStackName().toUpperCase() + 
destStackId.getStackVersion().replace(".", "") + "StackAdvisor";
+      if(baseStackAdvisor.equalsIgnoreCase(topLevelStackAdvisorName)) {
+        // Use top level stack advisor from source stack as top level stack 
advisor for merged stack
+        String srcPath = stackAdvisorsDir.getAbsolutePath() + File.separator + 
baseStackAdvisorModule + ".py";
+        String destPath = servicesDir.getAbsolutePath() + File.separator + 
"stack_advisor.py";
+        Files.move(Paths.get(srcPath), Paths.get(destPath));
+      } else {
+        // Create top level stack advisor for merged stack
+        FileWriter fileWriter = new FileWriter(
+          servicesDir.getAbsolutePath() + File.separator + "stack_advisor.py");
+        BufferedWriter bw = new BufferedWriter(fileWriter);
+        bw.write("from " + baseStackAdvisorModule + " import *");
+        bw.newLine();
+        bw.write("class " + topLevelStackAdvisorName + "(" + baseStackAdvisor 
+ ")");
+        bw.newLine();
+        bw.write("  pass");
+        bw.newLine();
+        bw.flush();
+        fileWriter.flush();
+        bw.close();
+        fileWriter.close();
+      }
+    }
+
+    // Export all service definitions
+    for (String serviceName : srcStackInfo.getServiceNames()) {
+
+      ServiceInfo serviceInfo = srcStackInfo.getService(serviceName);
+      ServiceInfo clonedServiceInfo =  (ServiceInfo) serviceInfo.clone();
+      // Flattening the stack, so set cloned service's parent to null
+      clonedServiceInfo.setParent(null);
+
+      // Create service root directory
+      File serviceDir = new File(
+        servicesDir.getAbsolutePath() + File.separator + serviceName);
+      if (!serviceDir.exists()) {
+        serviceDir.mkdir();
+      }
+
+      // Export service metainfo.xml
+      ServiceMetainfoXml serviceMetainfoXml = new ServiceMetainfoXml();
+      
serviceMetainfoXml.setSchemaVersion(clonedServiceInfo.getSchemaVersion());
+      List<ServiceInfo> serviceInfos = 
Collections.singletonList(clonedServiceInfo);
+      serviceMetainfoXml.setServices(serviceInfos);
+      ctx = JAXBContext.newInstance(ServiceMetainfoXml.class);
+      marshaller = ctx.createMarshaller();
+      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+      FileOutputStream serviceMetainfoFileStream = new FileOutputStream(
+              serviceDir.getAbsolutePath() + File.separator + "metainfo.xml");
+      StringWriter sw = new StringWriter();
+      marshaller.marshal(serviceMetainfoXml, serviceMetainfoFileStream);
+      marshaller.marshal(serviceMetainfoXml, sw);
+      serviceMetainfoFileStream.flush();
+      serviceMetainfoFileStream.close();
+
+      // Export mertrics.json
+      File srcMetricsFile = serviceInfo.getMetricsFile();
+      exportFile(srcMetricsFile, serviceDir);
+
+      // Export widgets.json
+      File srcWidgetsFile = serviceInfo.getWidgetsDescriptorFile();
+      exportFile(srcWidgetsFile, serviceDir);
+
+      // Export alerts.json
+      File srcAlertsFile = serviceInfo.getAlertsFile();
+      exportFile(srcAlertsFile, serviceDir);
+
+      // Export kerberos.json
+      File srcKerberosFile = serviceInfo.getKerberosDescriptorFile();
+      exportFile(srcKerberosFile, serviceDir);
+
+      // Export quicklinks
+      for (Map.Entry<String, QuickLinksConfigurationInfo> entry : 
serviceInfo.getQuickLinksConfigurationsMap()
+        .entrySet()) {
+        QuickLinksConfigurationInfo quickLinksConfigurationInfo = 
entry.getValue();
+        String quickLinksFileName = quickLinksConfigurationInfo.getFileName();
+        for (Map.Entry<String, QuickLinks> quickLinksEntry : 
quickLinksConfigurationInfo
+          .getQuickLinksConfigurationMap().entrySet()) {
+          File quickLinksDir = new File(
+                  serviceDir.getAbsolutePath() + File.separator + serviceInfo
+              .getQuickLinksConfigurationsDir());
+          if (!quickLinksDir.exists()) {
+            quickLinksDir.mkdir();
+          }
+          FileWriter quickLinksFileWriter = new FileWriter(
+            quickLinksDir.getAbsolutePath() + File.separator + 
quickLinksFileName, true);
+          mapper.writerWithDefaultPrettyPrinter()
+            .writeValue(quickLinksFileWriter, quickLinksEntry.getValue());
+        }
+      }
+
+      // Export themes
+      for (Map.Entry<String, ThemeInfo> entry : 
serviceInfo.getThemesMap().entrySet()) {
+        ThemeInfo themeInfo = entry.getValue();
+        String themeFileName = themeInfo.getFileName();
+        for (Map.Entry<String, Theme> themeEntry : 
themeInfo.getThemeMap().entrySet()) {
+          File themesDir = new File(
+                  serviceDir.getAbsolutePath() + File.separator + 
serviceInfo.getThemesDir());
+          if (!themesDir.exists()) {
+            themesDir.mkdir();
+          }
+          FileWriter themesFileWriter = new FileWriter(
+            themesDir.getAbsolutePath() + File.separator + themeFileName, 
true);
+          mapper.writerWithDefaultPrettyPrinter().writeValue(themesFileWriter, 
themeEntry.getValue());
+        }
+      }
+
+      // Export package folder (python scripts)
+      String srcPackageFolder = serviceInfo.getServicePackageFolder();
+      if (srcPackageFolder.startsWith("common-services")) {
+        srcPackageFolder = srcPackageFolder
+          .replace("common-services", commonServicesRoot.getAbsolutePath());
+      } else {
+        srcPackageFolder = srcPackageFolder.replace("stacks", 
stackRoot.getAbsolutePath());
+      }
+      File srcPackageFile = new File(srcPackageFolder);
+      if (srcPackageFile != null && srcPackageFile.exists()) {
+        File destPackageFile = new File(
+                serviceDir.getAbsolutePath() + File.separator
+            + ServiceDirectory.PACKAGE_FOLDER_NAME);
+        FileUtils.copyDirectory(srcPackageFile, destPackageFile);
+      }
+
+      // Export merged service-level configs
+      File configDir = new File(
+              serviceDir.getAbsolutePath() + File.separator + 
serviceInfo.getConfigDir());
+      exportConfigs(serviceInfo.getProperties(), configDir);
+
+      // Copy service property files (example: 
common-services/KERBEROS/1.10.3-10/properties/krb5_conf.j2)
+      File destServicePropertiesDir = new File(serviceDir.getAbsolutePath() + 
File.separator + "properties");
+      if(!destServicePropertiesDir.exists()) {
+        destServicePropertiesDir.mkdir();
+      }
+
+      srcStackName = srcStackId.getStackName();
+      currentStackVersion = srcStackId.getStackVersion();
+      boolean foundExplicitParent = false;
+      while (!StringUtils.isEmpty(currentStackVersion)) {
+        StackInfo currentStackInfo = stackManager.getStack(srcStackName, 
currentStackVersion);
+        ServiceInfo currentServiceInfo = 
currentStackInfo.getService(serviceName);
+        if(currentServiceInfo != null) {
+          File srcServicePropertiesDir = new File(stackRoot.getAbsolutePath() 
+ File.separator
+                  + srcStackName + File.separator + currentStackVersion + 
File.separator
+                  + "services" + File.separator + serviceName + File.separator 
+ "properties");
+          if (srcServicePropertiesDir.exists() && 
srcServicePropertiesDir.isDirectory()) {
+            for (File srcServicePropertiesFile : 
srcServicePropertiesDir.listFiles()) {
+              File destServicePropertiesFile = new 
File(destServicePropertiesDir.getAbsolutePath()
+                      + File.separator + srcServicePropertiesFile.getName());
+              if (!destServicePropertiesFile.exists()) {
+                FileUtils.copyFile(srcServicePropertiesFile, 
destServicePropertiesFile);
+              }
+            }
+          }
+          String currentServiceParent =  currentServiceInfo.getParent();
+          // Check for explicit service inheritance
+          if (StringUtils.isNotEmpty(currentServiceParent)) {
+            foundExplicitParent = true;
+          }
+        }
+        if(foundExplicitParent) {
+          // Stop traversing the stack hierarchy if explicit parent defined
+          break;
+        } else {
+          currentStackVersion = currentStackInfo.getParentStackVersion();
+        }
+      }
+
+      if(foundExplicitParent) {
+        StackInfo currentStackInfo = stackManager.getStack(srcStackName, 
currentStackVersion);
+        ServiceInfo currentServiceInfo = 
currentStackInfo.getService(serviceName);
+        String currentServiceParent =  currentServiceInfo.getParent();
+        
if(currentServiceParent.split(StackManager.PATH_DELIMITER)[0].equalsIgnoreCase(StackManager.COMMON_SERVICES))
 {
+          String[] parentToks = 
currentServiceParent.split(StackManager.PATH_DELIMITER);
+          File srcServicePropertiesDir = new 
File(commonServicesRoot.getAbsolutePath() + File.separator
+                  + parentToks[1] + File.separator + parentToks[2] + 
File.separator + "properties");
+          if (srcServicePropertiesDir.exists() && 
srcServicePropertiesDir.isDirectory()) {
+            for (File srcServicePropertiesFile : 
srcServicePropertiesDir.listFiles()) {
+              File destServicePropertiesFile = new 
File(destServicePropertiesDir.getAbsolutePath()
+                      + File.separator + srcServicePropertiesFile.getName());
+              if (!destServicePropertiesFile.exists()) {
+                FileUtils.copyFile(srcServicePropertiesFile, 
destServicePropertiesFile);
+              }
+            }
+          }
+          // TODO : Not traversing common-services hierarchy for now
+        }  else {
+          // TODO : Not handling explicit inheritance outside of 
common-services for now
+        }
+      }
+
+      // Copy service advisor
+      File srcServiceAdvisor = serviceInfo.getAdvisorFile();
+      File destServiceAdvisor = new File(serviceDir.getAbsolutePath() + 
File.separator + "service_advisor.py");
+      if(srcServiceAdvisor != null && srcServiceAdvisor.exists()) {
+        FileUtils.copyFile(srcServiceAdvisor, destServiceAdvisor);
+      }
+    }
+
+    // Delete *.pyc, *.pyo, archive.zip
+    FileUtils.listFiles(mergedStackDir, new String[]{"pyc", "pyo", "zip"}, 
true).forEach(File::delete);
+    LOG.info("Merged Stack " + destStackId + " has been successfully exported 
at " + mergedStackDir);
+  }
+
+  /**
+   * Export file
+   * @param srcFile     Source File Path
+   * @param destRootDir Target root directory
+   * @throws Exception
+   */
+  public static void exportFile(File srcFile, File destRootDir) throws 
Exception {
+    if (srcFile != null && srcFile.exists()) {
+      Path srcPath = Paths.get(srcFile.getAbsolutePath());
+      Path destPath = Paths.get(
+        destRootDir.getAbsolutePath() + File.separator + srcFile.getName());
+      Files.copy(srcPath, destPath, StandardCopyOption.COPY_ATTRIBUTES,
+        StandardCopyOption.REPLACE_EXISTING);
+    }
+  }
+
+  /**
+   * Export configs
+   * @param properties List of config properties
+   * @param configDir Configuration directory
+   * @throws Exception
+   */
+  public static void exportConfigs(List<PropertyInfo> properties, File 
configDir) throws Exception {
+    if (!configDir.exists()) {
+      configDir.mkdir();
+    }
+
+    Map<String, List<PropertyInfo>> configFilesMap = new HashMap<>();
+    for (PropertyInfo propertyInfo : properties) {
+      String fileName = propertyInfo.getFilename();
+      if (!configFilesMap.containsKey(fileName)) {
+        configFilesMap.put(fileName, new ArrayList<PropertyInfo>());
+      }
+      configFilesMap.get(fileName).add(propertyInfo);
+    }
+
+    for (Map.Entry<String, List<PropertyInfo>> entry : 
configFilesMap.entrySet()) {
+      String fileName = entry.getKey();
+      ConfigurationXml configXml = new ConfigurationXml();
+      configXml.setProperties(entry.getValue());
+      JAXBContext ctx = JAXBContext.newInstance(ConfigurationXml.class);
+      Marshaller marshaller = ctx.createMarshaller();
+      marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);
+      FileOutputStream configFileStream = new FileOutputStream(
+        configDir.getAbsolutePath() + File.separator + fileName);
+      marshaller.marshal(configXml, configFileStream);
+      configFileStream.flush();
+      configFileStream.close();
+    }
+  }
+
+  /**
+   * Main method for merging stack definitions
+   *
+   * Usage:
+   *   java -cp 
/etc/ambari-server/conf:/usr/lib/ambari-server/*:/usr/share/java/postgresql-jdbc.jar
+   *        org.apache.ambari.server.stack.StackMerger 
--mergedStacksRoot=/tmp/merged-root --stacks=HDP-2.5,HDP-2.6
+   * @param args
+   * @throws Exception
+   */
+  public static void main(String[] args) throws Exception {
+    StackMergerContext ctx = processArguments(args);
+
+    LOG.info("********* Initializing Stack Merger *********");
+    Injector injector = Guice.createInjector(new ControllerModule(), new 
StackMergerAuditModule(), new LdapModule());
+    GuiceJpaInitializer jpaInitializer = 
injector.getInstance(GuiceJpaInitializer.class);
+    jpaInitializer.setInitialized();
+    StackMerger stackMerger = injector.getInstance(StackMerger.class);
+    LOG.info("********* Stack Merger Initialized *********");
+    stackMerger.mergeStacks(ctx);
+    LOG.info("********* Stack Merger Finished *********");
+
+    System.exit(0);
+  }
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/AutoDeployInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/AutoDeployInfo.java
index c2d4d8b..74bc0b3 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/AutoDeployInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/AutoDeployInfo.java
@@ -34,7 +34,6 @@ public class AutoDeployInfo {
    * Optional component name to co-locate with.
    * Specified in the form serviceName/componentName.
    */
-  @XmlElement(name="co-locate")
   private String m_coLocate;
 
   /**
@@ -69,6 +68,7 @@ public class AutoDeployInfo {
    *
    * @return a component name in the form serviceName/componentName
    */
+  @XmlElement(name="co-locate")
   public String getCoLocate() {
     return m_coLocate;
   }
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 a4bac56..351f865 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
@@ -26,6 +26,7 @@ import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlTransient;
 
 import org.apache.commons.lang.builder.ToStringBuilder;
 import org.apache.commons.lang.builder.ToStringStyle;
@@ -53,6 +54,7 @@ public class ComponentInfo {
    * This is the translation of the xml element ["true", "false", null] (note 
that if a value is not specified,
    * it will inherit from the parent) into a boolean after actually resolving 
it.
    */
+  @XmlTransient
   private boolean versionAdvertisedInternal = false;
 
   /**
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyConditionInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyConditionInfo.java
index 392dec6..de39af4 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyConditionInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyConditionInfo.java
@@ -60,6 +60,7 @@ class PropertyExists implements DependencyConditionInfo {
    */
   protected String type = this.getClass().getSimpleName();
 
+  @XmlTransient
   public String getType() {
     return type;
   }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
index 2c4ddcc..56c90b7 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/DependencyInfo.java
@@ -58,7 +58,6 @@ public class DependencyInfo {
    * If auto-deployment is enabled for the dependency, the dependency is
    * automatically deployed if it is not specified in the provided topology.
    */
-  @XmlElement(name="auto-deploy")
   private AutoDeployInfo m_autoDeploy;
 
   /**
@@ -124,6 +123,7 @@ public class DependencyInfo {
    *
    * @return auto-deploy information
    */
+  @XmlElement(name="auto-deploy")
   public AutoDeployInfo getAutoDeploy() {
     return m_autoDeploy;
   }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyInfo.java 
b/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyInfo.java
index 31fcb9d..5d5db52 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyInfo.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/PropertyInfo.java
@@ -34,6 +34,7 @@ import javax.xml.bind.annotation.XmlAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlElementWrapper;
 import javax.xml.bind.annotation.XmlList;
+import javax.xml.bind.annotation.XmlTransient;
 
 import org.apache.ambari.server.controller.StackConfigurationResponse;
 import org.w3c.dom.Element;
@@ -47,7 +48,9 @@ public class PropertyInfo {
   @XmlElement(name = "display-name")
   private String displayName;
 
+  @XmlTransient
   private String filename;
+
   private boolean deleted;
 
   @XmlElement(name="on-ambari-upgrade", required = true)
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 59cee41..674637f 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
@@ -59,7 +59,7 @@ import com.google.common.collect.Multimaps;
 
 @XmlAccessorType(XmlAccessType.FIELD)
 @JsonFilter("propertiesfilter")
-public class ServiceInfo implements Validable {
+public class ServiceInfo implements Validable, Cloneable {
 
   public static final AbstractMap.SimpleEntry<String, String> 
DEFAULT_SERVICE_INSTALLABLE_PROPERTY = new 
AbstractMap.SimpleEntry<>("installable", "true");
   public static final AbstractMap.SimpleEntry<String, String> 
DEFAULT_SERVICE_MANAGED_PROPERTY = new AbstractMap.SimpleEntry<>("managed", 
"true");
@@ -195,6 +195,7 @@ public class ServiceInfo implements Validable {
   @XmlTransient
   private File widgetsDescriptorFile = null;
 
+  @XmlTransient
   private StackRoleCommandOrder roleCommandOrder;
 
   @XmlTransient
@@ -282,6 +283,7 @@ public class ServiceInfo implements Validable {
    * at getter.
    * Added at schema ver 2
    */
+  @XmlTransient
   private volatile Map<String, ServiceOsSpecific> serviceOsSpecificsMap;
 
   /**
@@ -469,6 +471,56 @@ public class ServiceInfo implements Validable {
     this.properties = properties;
   }
 
+  @Override
+  public Object clone() throws CloneNotSupportedException {
+    ServiceInfo clone = (ServiceInfo) super.clone();
+    clone.setSchemaVersion(schemaVersion);
+    clone.setName(name);
+    clone.setDisplayName(displayName);
+    clone.setVersion(version);
+    clone.setComment(comment);
+    clone.setServiceType(serviceType);
+    clone.setSelection(selection);
+    clone.components = components;
+    clone.setDeleted(isDeleted);
+    clone.setConfigDependencies(configDependencies);
+    clone.setExcludedConfigTypes(excludedConfigTypes);
+    clone.setMonitoringService(monitoringService);
+    clone.setRestartRequiredAfterChange(restartRequiredAfterChange);
+    clone.setRestartRequiredAfterRackChange(restartRequiredAfterRackChange);
+    clone.setParent(parent);
+    if(metricsFile != null) {
+      clone.metricsFileName = metricsFile.getName();
+    }
+    if(widgetsDescriptorFile != null) {
+      clone.widgetsFileName = widgetsDescriptorFile.getName();
+    }
+    clone.setCredentialStoreInfo(credentialStoreInfo);
+    clone.setServicePropertyList(servicePropertyList);
+    clone.configDir = configDir;
+
+    clone.themesDir = themesDir;
+    clone.setThemesMap(themesMap);
+    if(this.themesMap != null) {
+      clone.themes = new ArrayList(this.themesMap.values());
+    }
+
+    clone.quickLinksConfigurationsDir = quickLinksConfigurationsDir;
+    clone.setQuickLinksConfigurationsMap(quickLinksConfigurationsMap);
+    if(this.quickLinksConfigurationsMap != null) {
+      clone.quickLinksConfigurations = new 
ArrayList(this.quickLinksConfigurationsMap.values());
+    }
+
+    clone.serviceOsSpecificsMap =  serviceOsSpecificsMap;
+    if(this.serviceOsSpecificsMap != null) {
+      clone.serviceOsSpecifics = new 
ArrayList<>(serviceOsSpecificsMap.values());
+    }
+
+    clone.setCommandScript(commandScript);
+    clone.setRequiredServices(requiredServices);
+    return clone;
+  }
+
   public List<ComponentInfo> getComponents() {
     if (components == null) {
       components = new ArrayList<>();
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ConfigurationXml.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ConfigurationXml.java
index 0f471fe..8df3b46 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ConfigurationXml.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ConfigurationXml.java
@@ -25,6 +25,8 @@ import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
 import javax.xml.bind.annotation.XmlAnyAttribute;
 import javax.xml.bind.annotation.XmlElement;
 import javax.xml.bind.annotation.XmlRootElement;
@@ -38,6 +40,7 @@ import org.apache.ambari.server.state.PropertyInfo;
  * The elements within a service's configuration file.
  */
 @XmlRootElement(name="configuration")
+@XmlAccessorType(XmlAccessType.FIELD)
 public class ConfigurationXml implements Validable{
   
   @XmlAnyAttribute
@@ -92,6 +95,14 @@ public class ConfigurationXml implements Validable{
     return properties;
   }
 
+  /***
+   *
+   * @param listProperties
+   */
+  public void setProperties(List<PropertyInfo> listProperties) {
+    this.properties = listProperties;
+  }
+
   public Map<QName, String> getAttributes() {
     return attributes;
   }
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackMetainfoXml.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackMetainfoXml.java
index 753c2b8..a4d3fe5 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackMetainfoXml.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackMetainfoXml.java
@@ -40,10 +40,18 @@ public class StackMetainfoXml implements Validable{
     return minJdk;
   }
 
+  public void setMinJdk(String minJdk) {
+    this.minJdk = minJdk;
+  }
+
   public String getMaxJdk() {
     return maxJdk;
   }
 
+  public void setMaxJdk(String maxJdk) {
+    this.maxJdk = maxJdk;
+  }
+
   @XmlElement(name="minJdk")
   private String minJdk = null;
 
@@ -52,6 +60,10 @@ public class StackMetainfoXml implements Validable{
 
   @XmlElement(name="extends")
   private String extendsVersion = null;
+
+  public void setExtendsVersion(String extendsVersion) {
+    this.extendsVersion = extendsVersion;
+  }
   
   @XmlElement(name="versions")
   private Version version = new Version();
@@ -108,10 +120,14 @@ public class StackMetainfoXml implements Validable{
   public Version getVersion() {
     return version;
   }
+
+  public void setVersion(Version version) {
+    this.version = version;
+  }
   
   @XmlAccessorType(XmlAccessType.FIELD)
   public static class Version {
-    private Version() {
+    public Version() {
     }
     private boolean active = false;
     private String upgrade = null;
@@ -122,6 +138,10 @@ public class StackMetainfoXml implements Validable{
     public boolean isActive() {
       return active;
     }
+
+    public void setActive(boolean active) {
+      this.active = active;
+    }
     
     /**
      * @return the upgrade version number, if set
@@ -129,8 +149,13 @@ public class StackMetainfoXml implements Validable{
     public String getUpgrade() {
       return upgrade;
     }
-    
-    
+
+    /**
+     * Sets the upgrade version number
+     */
+    public void setUpgrade(String upgradeVersion) {
+      upgrade = upgradeVersion;
+    }
   }  
   
 }
diff --git 
a/ambari-server/src/main/resources/common-services/AMBARI_INFRA_SOLR/0.1.0/service_advisor.py
 
b/ambari-server/src/main/resources/common-services/AMBARI_INFRA_SOLR/0.1.0/service_advisor.py
index 581cbcd..5e25431 100644
--- 
a/ambari-server/src/main/resources/common-services/AMBARI_INFRA_SOLR/0.1.0/service_advisor.py
+++ 
b/ambari-server/src/main/resources/common-services/AMBARI_INFRA_SOLR/0.1.0/service_advisor.py
@@ -25,6 +25,8 @@ import traceback
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:
diff --git 
a/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/service_advisor.py
 
b/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/service_advisor.py
index a7b125b..ebfe973 100644
--- 
a/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/service_advisor.py
+++ 
b/ambari-server/src/main/resources/common-services/AMBARI_METRICS/0.1.0/service_advisor.py
@@ -32,6 +32,8 @@ from resource_management.core.logger import Logger
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:
diff --git 
a/ambari-server/src/main/resources/common-services/HAWQ/2.0.0/service_advisor.py
 
b/ambari-server/src/main/resources/common-services/HAWQ/2.0.0/service_advisor.py
index 434228d..544ff19 100644
--- 
a/ambari-server/src/main/resources/common-services/HAWQ/2.0.0/service_advisor.py
+++ 
b/ambari-server/src/main/resources/common-services/HAWQ/2.0.0/service_advisor.py
@@ -26,6 +26,8 @@ import traceback
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:
diff --git 
a/ambari-server/src/main/resources/common-services/LOGSEARCH/0.5.0/service_advisor.py
 
b/ambari-server/src/main/resources/common-services/LOGSEARCH/0.5.0/service_advisor.py
index ed303df..b9244df 100644
--- 
a/ambari-server/src/main/resources/common-services/LOGSEARCH/0.5.0/service_advisor.py
+++ 
b/ambari-server/src/main/resources/common-services/LOGSEARCH/0.5.0/service_advisor.py
@@ -25,6 +25,8 @@ import traceback
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:
diff --git 
a/ambari-server/src/main/resources/common-services/PXF/3.0.0/service_advisor.py 
b/ambari-server/src/main/resources/common-services/PXF/3.0.0/service_advisor.py
index d2a80a9..55899b1 100644
--- 
a/ambari-server/src/main/resources/common-services/PXF/3.0.0/service_advisor.py
+++ 
b/ambari-server/src/main/resources/common-services/PXF/3.0.0/service_advisor.py
@@ -23,6 +23,8 @@ import traceback
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:
diff --git 
a/ambari-server/src/main/resources/common-services/ZEPPELIN/0.7.0/service_advisor.py
 
b/ambari-server/src/main/resources/common-services/ZEPPELIN/0.7.0/service_advisor.py
index 4efb629..85e98ce 100644
--- 
a/ambari-server/src/main/resources/common-services/ZEPPELIN/0.7.0/service_advisor.py
+++ 
b/ambari-server/src/main/resources/common-services/ZEPPELIN/0.7.0/service_advisor.py
@@ -31,6 +31,8 @@ from resource_management.core.logger import Logger
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:
diff --git 
a/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.9/service_advisor.py
 
b/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.9/service_advisor.py
index fbc868d..bb93a78 100644
--- 
a/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.9/service_advisor.py
+++ 
b/ambari-server/src/main/resources/common-services/ZOOKEEPER/3.4.9/service_advisor.py
@@ -29,6 +29,8 @@ import inspect
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:
diff --git a/ambari-server/src/main/resources/scripts/stack_advisor.py 
b/ambari-server/src/main/resources/scripts/stack_advisor.py
index b39a6e8..b7a2ff7 100755
--- a/ambari-server/src/main/resources/scripts/stack_advisor.py
+++ b/ambari-server/src/main/resources/scripts/stack_advisor.py
@@ -48,6 +48,10 @@ STACK_ADVISOR_DEFAULT_IMPL_CLASS = 'DefaultStackAdvisor'
 STACK_ADVISOR_IMPL_PATH_TEMPLATE = os.path.join(STACKS_DIRECTORY, 
'{0}/{1}/services/stack_advisor.py')
 STACK_ADVISOR_IMPL_CLASS_TEMPLATE = '{0}{1}StackAdvisor'
 
+# After merging stack definitions, stack advisor may have deeper inheritance 
than the merged stack,
+# The extra classes are defined in this directory
+STACK_ADVISOR_BASE_MODULES = os.path.join(SCRIPT_DIRECTORY, 
'../stacks/{0}/{1}/stack-advisors')
+
 ADVISOR_CONTEXT = "advisor_context"
 CALL_TYPE = "call_type"
 
@@ -156,6 +160,8 @@ def instantiateStackAdvisor(stackName, stackVersion, 
parentVersions):
   versions = [stackVersion]
   versions.extend(parentVersions)
 
+  sys.path.append(STACK_ADVISOR_BASE_MODULES.format(stackName, versions[-1]))
+
   for version in reversed(versions):
     try:
       path = STACK_ADVISOR_IMPL_PATH_TEMPLATE.format(stackName, version)
diff --git 
a/ambari-server/src/main/resources/stacks/HDP/2.1/services/stack_advisor.py 
b/ambari-server/src/main/resources/stacks/HDP/2.1/services/stack_advisor.py
index aefb603..53f0b4d 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.1/services/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/HDP/2.1/services/stack_advisor.py
@@ -22,6 +22,11 @@ import socket
 
 # Local Imports
 
+try:
+  from stack_advisor_hdp206 import *
+except ImportError:
+  #Ignore ImportError
+  print("stack_advisor_hdp206 not found")
 
 class HDP21StackAdvisor(HDP206StackAdvisor):
 
diff --git 
a/ambari-server/src/main/resources/stacks/HDP/2.2/services/stack_advisor.py 
b/ambari-server/src/main/resources/stacks/HDP/2.2/services/stack_advisor.py
index 4cb0d9e..4af9764 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.2/services/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/HDP/2.2/services/stack_advisor.py
@@ -29,6 +29,11 @@ import xml.etree.ElementTree as ET
 
 # Local Imports
 
+try:
+  from stack_advisor_hdp21 import *
+except ImportError:
+  #Ignore ImportError
+  print("stack_advisor_hdp21 not found")
 
 class HDP22StackAdvisor(HDP21StackAdvisor):
 
diff --git 
a/ambari-server/src/main/resources/stacks/HDP/2.3/services/stack_advisor.py 
b/ambari-server/src/main/resources/stacks/HDP/2.3/services/stack_advisor.py
index ac4632b..8dc6877 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.3/services/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/HDP/2.3/services/stack_advisor.py
@@ -26,6 +26,11 @@ import socket
 
 # Local Imports
 
+try:
+  from stack_advisor_hdp22 import *
+except ImportError:
+  #Ignore ImportError
+  print("stack_advisor_hdp22 not found")
 
 DB_TYPE_DEFAULT_PORT_MAP = {"MYSQL":"3306", "ORACLE":"1521", 
"POSTGRES":"5432", "MSSQL":"1433", "SQLA":"2638"}
 
diff --git 
a/ambari-server/src/main/resources/stacks/HDP/2.4/services/stack_advisor.py 
b/ambari-server/src/main/resources/stacks/HDP/2.4/services/stack_advisor.py
index 985c101..bcfb908 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.4/services/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/HDP/2.4/services/stack_advisor.py
@@ -17,6 +17,11 @@ See the License for the specific language governing 
permissions and
 limitations under the License.
 """
 
+try:
+ from stack_advisor_hdp23 import *
+except ImportError:
+ #Ignore ImportError
+ print("stack_advisor_hdp23 not found")
 
 class HDP24StackAdvisor(HDP23StackAdvisor):
  pass
diff --git 
a/ambari-server/src/main/resources/stacks/HDP/2.5/services/stack_advisor.py 
b/ambari-server/src/main/resources/stacks/HDP/2.5/services/stack_advisor.py
index 40c583c..c23a067 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.5/services/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/HDP/2.5/services/stack_advisor.py
@@ -24,6 +24,11 @@ from ambari_commons.str_utils import string_set_equals
 from resource_management.core.exceptions import Fail
 from resource_management.libraries.functions.get_bare_principal import 
get_bare_principal
 
+try:
+  from stack_advisor_hdp24 import *
+except ImportError:
+  #Ignore ImportError
+  print("stack_advisor_hdp24 not found")
 
 class HDP25StackAdvisor(HDP24StackAdvisor):
 
diff --git a/ambari-server/src/main/resources/stacks/HDP/2.6/metainfo.xml 
b/ambari-server/src/main/resources/stacks/HDP/2.6/metainfo.xml
index a3c3d8b..b74369f 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.6/metainfo.xml
+++ b/ambari-server/src/main/resources/stacks/HDP/2.6/metainfo.xml
@@ -20,4 +20,6 @@
     <active>false</active>
   </versions>
   <extends>2.5</extends>
+  <minJdk>1.7</minJdk>
+  <maxJdk>1.8</maxJdk>
 </metainfo>
diff --git 
a/ambari-server/src/main/resources/stacks/HDP/2.6/services/stack_advisor.py 
b/ambari-server/src/main/resources/stacks/HDP/2.6/services/stack_advisor.py
index b3acd5e..88cd7ac 100644
--- a/ambari-server/src/main/resources/stacks/HDP/2.6/services/stack_advisor.py
+++ b/ambari-server/src/main/resources/stacks/HDP/2.6/services/stack_advisor.py
@@ -22,6 +22,11 @@ import re
 from resource_management.libraries.functions import format
 from resource_management.libraries.functions.version import compare_versions
 
+try:
+    from stack_advisor_hdp25 import *
+except ImportError:
+    #Ignore ImportError
+    print("stack_advisor_hdp25 not found")
 
 class HDP26StackAdvisor(HDP25StackAdvisor):
   def __init__(self):
diff --git 
a/contrib/management-packs/microsoft-r_mpack/src/main/resources/common-services/MICROSOFT_R_SERVER/8.0.5/service_advisor.py
 
b/contrib/management-packs/microsoft-r_mpack/src/main/resources/common-services/MICROSOFT_R_SERVER/8.0.5/service_advisor.py
index 0ecc310..b60157c 100644
--- 
a/contrib/management-packs/microsoft-r_mpack/src/main/resources/common-services/MICROSOFT_R_SERVER/8.0.5/service_advisor.py
+++ 
b/contrib/management-packs/microsoft-r_mpack/src/main/resources/common-services/MICROSOFT_R_SERVER/8.0.5/service_advisor.py
@@ -26,6 +26,8 @@ import traceback
 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
 STACKS_DIR = os.path.join(SCRIPT_DIR, '../../../../../stacks/')
 PARENT_FILE = os.path.join(STACKS_DIR, 'service_advisor.py')
+if "BASE_SERVICE_ADVISOR" in os.environ:
+  PARENT_FILE = os.environ["BASE_SERVICE_ADVISOR"]
 
 try:
   with open(PARENT_FILE, 'rb') as fp:

Reply via email to