Explorar o código

[AMBARI-23310] Add SSO-related configuration recommendations to the stack advisor

* [AMBARI-23301] Add SSO-related configuration recommendations to the stack advisor

* [AMBARI-23301] Add SSO-related configuration recommendations to the stack advisor
Robert Levas %!s(int64=7) %!d(string=hai) anos
pai
achega
4109c0e3b9
Modificáronse 27 ficheiros con 1053 adicións e 71 borrados
  1. 5 0
      ambari-server/src/main/assemblies/server.xml
  2. 4 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java
  3. 1 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java
  4. 120 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/SingleSignOnConfigurationRecommendationCommand.java
  5. 2 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandType.java
  6. 1 0
      ambari-server/src/main/java/org/apache/ambari/server/configuration/AmbariServerConfigurationKey.java
  7. 15 4
      ambari-server/src/main/resources/scripts/stack_advisor.py
  8. 50 9
      ambari-server/src/main/resources/stacks/HDP/2.6/services/stack_advisor.py
  9. 286 0
      ambari-server/src/main/resources/stacks/ambari_configuration.py
  10. 6 0
      ambari-server/src/main/resources/stacks/service_advisor.py
  11. 125 0
      ambari-server/src/main/resources/stacks/stack_advisor.py
  12. 8 2
      ambari-server/src/test/python/TestStackAdvisor.py
  13. 9 4
      ambari-server/src/test/python/common-services/AMBARI_METRICS/test_service_advisor.py
  14. 10 5
      ambari-server/src/test/python/common-services/HAWQ/test_service_advisor.py
  15. 9 4
      ambari-server/src/test/python/common-services/LOGSEARCH/test_service_advisor.py
  16. 10 5
      ambari-server/src/test/python/common-services/PXF/test_service_advisor.py
  17. 10 3
      ambari-server/src/test/python/stacks/2.0.6/common/test_stack_advisor.py
  18. 10 3
      ambari-server/src/test/python/stacks/2.1/common/test_stack_advisor.py
  19. 11 4
      ambari-server/src/test/python/stacks/2.2/common/test_stack_advisor.py
  20. 5 0
      ambari-server/src/test/python/stacks/2.2/common/test_stack_advisor_perf.py
  21. 12 5
      ambari-server/src/test/python/stacks/2.3/common/test_stack_advisor.py
  22. 13 7
      ambari-server/src/test/python/stacks/2.5/common/test_stack_advisor.py
  23. 14 15
      ambari-server/src/test/python/stacks/2.6/common/test_stack_advisor.py
  24. 308 0
      ambari-server/src/test/python/stacks/test_ambari_configuration.py
  25. 7 1
      ambari-server/src/test/python/stacks/test_stack_adviser.py
  26. 0 0
      ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_stack/HIVE/package/.hash
  27. 2 0
      ambari-serviceadvisor/src/main/java/org/apache/ambari/serviceadvisor/ServiceAdvisorCommandType.java

+ 5 - 0
ambari-server/src/main/assemblies/server.xml

@@ -416,6 +416,11 @@
       <source>src/main/resources/stacks/service_advisor.py</source>
       <outputDirectory>/var/lib/ambari-server/resources/stacks</outputDirectory>
     </file>
+    <file>
+      <fileMode>755</fileMode>
+      <source>src/main/resources/stacks/ambari_configuration.py</source>
+      <outputDirectory>/var/lib/ambari-server/resources/stacks</outputDirectory>
+    </file>
     <file>
       <fileMode>755</fileMode>
       <source>src/main/python/bootstrap.py</source>

+ 4 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorHelper.java

@@ -29,6 +29,7 @@ import org.apache.ambari.server.api.services.stackadvisor.commands.ComponentLayo
 import org.apache.ambari.server.api.services.stackadvisor.commands.ConfigurationDependenciesRecommendationCommand;
 import org.apache.ambari.server.api.services.stackadvisor.commands.ConfigurationRecommendationCommand;
 import org.apache.ambari.server.api.services.stackadvisor.commands.ConfigurationValidationCommand;
+import org.apache.ambari.server.api.services.stackadvisor.commands.SingleSignOnConfigurationRecommendationCommand;
 import org.apache.ambari.server.api.services.stackadvisor.commands.StackAdvisorCommand;
 import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
 import org.apache.ambari.server.api.services.stackadvisor.validations.ValidationResponse;
@@ -143,6 +144,9 @@ public class StackAdvisorHelper {
     } else if (requestType == StackAdvisorRequestType.CONFIGURATIONS) {
       command = new ConfigurationRecommendationCommand(recommendationsDir, recommendationsArtifactsLifetime, serviceAdvisorType,
           requestId, saRunner, metaInfo);
+    } else if (requestType == StackAdvisorRequestType.SSO_CONFIGURATIONS) {
+      command = new SingleSignOnConfigurationRecommendationCommand(recommendationsDir, recommendationsArtifactsLifetime, serviceAdvisorType,
+          requestId, saRunner, metaInfo);
     } else if (requestType == StackAdvisorRequestType.CONFIGURATION_DEPENDENCIES) {
       command = new ConfigurationDependenciesRecommendationCommand(recommendationsDir, recommendationsArtifactsLifetime, serviceAdvisorType,
           requestId, saRunner, metaInfo);

+ 1 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/StackAdvisorRequest.java

@@ -228,6 +228,7 @@ public class StackAdvisorRequest {
   public enum StackAdvisorRequestType {
     HOST_GROUPS("host_groups"),
     CONFIGURATIONS("configurations"),
+    SSO_CONFIGURATIONS("sso-configurations"),
     CONFIGURATION_DEPENDENCIES("configuration-dependencies");
 
     private String type;

+ 120 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/SingleSignOnConfigurationRecommendationCommand.java

@@ -0,0 +1,120 @@
+/*
+ * 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.api.services.stackadvisor.commands;
+
+import static org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse.BindingHostGroup;
+import static org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse.HostGroup;
+
+import java.io.File;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorException;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRequest;
+import org.apache.ambari.server.api.services.stackadvisor.StackAdvisorRunner;
+import org.apache.ambari.server.api.services.stackadvisor.recommendations.RecommendationResponse;
+import org.apache.ambari.server.state.ServiceInfo;
+
+/**
+ * {@link SingleSignOnConfigurationRecommendationCommand} implementation for
+ * single sign-on configuration recommendation.
+ */
+public class SingleSignOnConfigurationRecommendationCommand extends
+    StackAdvisorCommand<RecommendationResponse> {
+
+  public SingleSignOnConfigurationRecommendationCommand(File recommendationsDir,
+                                                        String recommendationsArtifactsLifetime,
+                                                        ServiceInfo.ServiceAdvisorType serviceAdvisorType,
+                                                        int requestId,
+                                                        StackAdvisorRunner saRunner,
+                                                        AmbariMetaInfo metaInfo) {
+    super(recommendationsDir, recommendationsArtifactsLifetime, serviceAdvisorType, requestId, saRunner, metaInfo);
+  }
+
+  @Override
+  protected StackAdvisorCommandType getCommandType() {
+    return StackAdvisorCommandType.RECOMMEND_CONFIGURATIONS_FOR_SSO;
+  }
+
+  @Override
+  protected void validate(StackAdvisorRequest request) throws StackAdvisorException {
+    if (request.getHosts() == null || request.getHosts().isEmpty() || request.getServices() == null
+        || request.getServices().isEmpty()) {
+      throw new StackAdvisorException("Hosts and services must not be empty");
+    }
+  }
+
+  @Override
+  protected RecommendationResponse updateResponse(StackAdvisorRequest request, RecommendationResponse response) {
+    response.getRecommendations().getBlueprint().setHostGroups(processHostGroups(request));
+    response.getRecommendations().getBlueprintClusterBinding().setHostGroups(processHostGroupBindings(request));
+    return response;
+  }
+
+  protected Set<HostGroup> processHostGroups(StackAdvisorRequest request) {
+    Set<HostGroup> resultSet = new HashSet<>();
+    for (Map.Entry<String, Set<String>> componentHost : request.getHostComponents().entrySet()) {
+      String hostGroupName = componentHost.getKey();
+      Set<String> components = componentHost.getValue();
+      if (hostGroupName != null && components != null) {
+        HostGroup hostGroup = new HostGroup();
+        Set<Map<String, String>> componentsSet = new HashSet<>();
+        for (String component : components) {
+          Map<String, String> componentMap = new HashMap<>();
+          componentMap.put("name", component);
+          componentsSet.add(componentMap);
+        }
+        hostGroup.setComponents(componentsSet);
+        hostGroup.setName(hostGroupName);
+        resultSet.add(hostGroup);
+      }
+    }
+    return resultSet;
+  }
+
+  private Set<BindingHostGroup> processHostGroupBindings(StackAdvisorRequest request) {
+    Set<BindingHostGroup> resultSet = new HashSet<>();
+    for (Map.Entry<String, Set<String>> hostBinding : request.getHostGroupBindings().entrySet()) {
+      String hostGroupName = hostBinding.getKey();
+      Set<String> hosts = hostBinding.getValue();
+      if (hostGroupName != null && hosts != null) {
+        BindingHostGroup bindingHostGroup = new BindingHostGroup();
+        Set<Map<String, String>> hostsSet = new HashSet<>();
+        for (String host : hosts) {
+          Map<String, String> hostMap = new HashMap<>();
+          hostMap.put("name", host);
+          hostsSet.add(hostMap);
+        }
+        bindingHostGroup.setHosts(hostsSet);
+        bindingHostGroup.setName(hostGroupName);
+        resultSet.add(bindingHostGroup);
+      }
+    }
+    return resultSet;
+  }
+
+  @Override
+  protected String getResultFileName() {
+    return "configurations.json";
+  }
+
+}

+ 2 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/stackadvisor/commands/StackAdvisorCommandType.java

@@ -29,6 +29,8 @@ public enum StackAdvisorCommandType {
 
   RECOMMEND_CONFIGURATIONS("recommend-configurations"),
 
+  RECOMMEND_CONFIGURATIONS_FOR_SSO("recommend-configurations-for-sso"),
+
   RECOMMEND_CONFIGURATION_DEPENDENCIES("recommend-configuration-dependencies"),
 
   VALIDATE_CONFIGURATIONS("validate-configurations");

+ 1 - 0
ambari-server/src/main/java/org/apache/ambari/server/configuration/AmbariServerConfigurationKey.java

@@ -77,6 +77,7 @@ public enum AmbariServerConfigurationKey {
   /* ********************************************************
    * SSO Configuration Keys
    * ******************************************************** */
+  SSO_MANAGE_SERVICES(AmbariServerConfigurationCategory.SSO_CONFIGURATION, "ambari.sso.manage_services", PLAINTEXT, "false", "A Boolean value indicating whether Ambari is to manage the SSO configuration for services or not."),
   SSO_ENABED_SERVICES(AmbariServerConfigurationCategory.SSO_CONFIGURATION, "ambari.sso.enabled_services", PLAINTEXT, null, "A comma-delimited list of services that are expected to be configured for SSO.  A \"*\" indicates all services.");
 
   private final AmbariServerConfigurationCategory configurationCategory;

+ 15 - 4
ambari-server/src/main/resources/scripts/stack_advisor.py

@@ -26,20 +26,24 @@ import traceback
 RECOMMEND_COMPONENT_LAYOUT_ACTION = 'recommend-component-layout'
 VALIDATE_COMPONENT_LAYOUT_ACTION = 'validate-component-layout'
 RECOMMEND_CONFIGURATIONS = 'recommend-configurations'
+RECOMMEND_CONFIGURATIONS_FOR_SSO = 'recommend-configurations-for-sso'
 RECOMMEND_CONFIGURATION_DEPENDENCIES = 'recommend-configuration-dependencies'
 VALIDATE_CONFIGURATIONS = 'validate-configurations'
 
 ALL_ACTIONS = [RECOMMEND_COMPONENT_LAYOUT_ACTION,
                VALIDATE_COMPONENT_LAYOUT_ACTION,
                RECOMMEND_CONFIGURATIONS,
+               RECOMMEND_CONFIGURATIONS_FOR_SSO,
                RECOMMEND_CONFIGURATION_DEPENDENCIES,
                VALIDATE_CONFIGURATIONS]
 USAGE = "Usage: <action> <hosts_file> <services_file>\nPossible actions are: {0}\n".format( str(ALL_ACTIONS) )
 
 SCRIPT_DIRECTORY = os.path.dirname(os.path.abspath(__file__))
-STACK_ADVISOR_PATH_TEMPLATE = os.path.join(SCRIPT_DIRECTORY, '../stacks/stack_advisor.py')
+STACKS_DIRECTORY = os.path.join(SCRIPT_DIRECTORY, '../stacks')
+STACK_ADVISOR_PATH = os.path.join(STACKS_DIRECTORY, 'stack_advisor.py')
+AMBARI_CONFIGURATION_PATH = os.path.join(STACKS_DIRECTORY, 'ambari_configuration.py')
 STACK_ADVISOR_DEFAULT_IMPL_CLASS = 'DefaultStackAdvisor'
-STACK_ADVISOR_IMPL_PATH_TEMPLATE = os.path.join(SCRIPT_DIRECTORY, './../stacks/{0}/{1}/services/stack_advisor.py')
+STACK_ADVISOR_IMPL_PATH_TEMPLATE = os.path.join(STACKS_DIRECTORY, '{0}/{1}/services/stack_advisor.py')
 STACK_ADVISOR_IMPL_CLASS_TEMPLATE = '{0}{1}StackAdvisor'
 
 ADVISOR_CONTEXT = "advisor_context"
@@ -115,6 +119,10 @@ def main(argv=None):
     services[ADVISOR_CONTEXT] = {CALL_TYPE : 'recommendConfigurations'}
     result = stackAdvisor.recommendConfigurations(services, hosts)
     result_file = os.path.join(actionDir, "configurations.json")
+  elif action == RECOMMEND_CONFIGURATIONS_FOR_SSO:
+    services[ADVISOR_CONTEXT] = {CALL_TYPE : 'recommendConfigurationsForSSO'}
+    result = stackAdvisor.recommendConfigurationsForSSO(services, hosts)
+    result_file = os.path.join(actionDir, "configurations.json")
   elif action == RECOMMEND_CONFIGURATION_DEPENDENCIES:
     services[ADVISOR_CONTEXT] = {CALL_TYPE : 'recommendConfigurationDependencies'}
     result = stackAdvisor.recommendConfigurationDependencies(services, hosts)
@@ -131,8 +139,11 @@ def instantiateStackAdvisor(stackName, stackVersion, parentVersions):
   """Instantiates StackAdvisor implementation for the specified Stack"""
   import imp
 
-  with open(STACK_ADVISOR_PATH_TEMPLATE, 'rb') as fp:
-    default_stack_advisor = imp.load_module('stack_advisor', fp, STACK_ADVISOR_PATH_TEMPLATE, ('.py', 'rb', imp.PY_SOURCE))
+  with open(AMBARI_CONFIGURATION_PATH, 'rb') as fp:
+    imp.load_module('ambari_configuration', fp, AMBARI_CONFIGURATION_PATH, ('.py', 'rb', imp.PY_SOURCE))
+
+  with open(STACK_ADVISOR_PATH, 'rb') as fp:
+    default_stack_advisor = imp.load_module('stack_advisor', fp, STACK_ADVISOR_PATH, ('.py', 'rb', imp.PY_SOURCE))
   className = STACK_ADVISOR_DEFAULT_IMPL_CLASS
   stack_advisor = default_stack_advisor
 

+ 50 - 9
ambari-server/src/main/resources/stacks/HDP/2.6/services/stack_advisor.py

@@ -48,6 +48,12 @@ class HDP26StackAdvisor(HDP25StackAdvisor):
       parentRecommendConfDict.update(childRecommendConfDict)
       return parentRecommendConfDict
 
+  def getServiceConfigurationRecommenderForSSODict(self):
+      return {
+        "ATLAS": self.recommendAtlasConfigurationsForSSO,
+        "RANGER": self.recommendRangerConfigurationsForSSO
+      }
+
   def recommendSPARK2Configurations(self, configurations, clusterData, services, hosts):
     """
     :type configurations dict
@@ -85,26 +91,51 @@ class HDP26StackAdvisor(HDP25StackAdvisor):
 
   def recommendAtlasConfigurations(self, configurations, clusterData, services, hosts):
     super(HDP26StackAdvisor, self).recommendAtlasConfigurations(configurations, clusterData, services, hosts)
-    servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
+    self.recommendAtlasConfigurationsForSSO(configurations, clusterData, services, hosts)
+
+  def recommendAtlasConfigurationsForSSO(self, configurations, clusterData, services, hosts):
+    ambari_configuration = self.get_ambari_configuration(services)
+
     putAtlasApplicationProperty = self.putProperty(configurations, "application-properties", services)
 
-    knox_host = 'localhost'
-    knox_port = '8443'
+    servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
+
+    # If KNOX is installed, automatically enable SSO using details from KNOX
     if 'KNOX' in servicesList:
+      knox_host = 'localhost'
+      knox_port = '8443'
+
       knox_hosts = self.getComponentHostNames(services, "KNOX", "KNOX_GATEWAY")
       if len(knox_hosts) > 0:
         knox_hosts.sort()
         knox_host = knox_hosts[0]
+
       if 'gateway-site' in services['configurations'] and 'gateway.port' in services['configurations']["gateway-site"]["properties"]:
         knox_port = services['configurations']["gateway-site"]["properties"]['gateway.port']
       putAtlasApplicationProperty('atlas.sso.knox.providerurl', 'https://{0}:{1}/gateway/knoxsso/api/v1/websso'.format(knox_host, knox_port))
 
-    knox_service_user = ''
-    if 'KNOX' in servicesList and 'knox-env' in services['configurations']:
-      knox_service_user = services['configurations']['knox-env']['properties']['knox_user']
-    else:
-      knox_service_user = 'knox'
-    putAtlasApplicationProperty('atlas.proxyusers',knox_service_user)
+    # If SSO should be enabled for this service
+    if ambari_configuration.should_enable_sso('ATLAS'):
+      putAtlasApplicationProperty('atlas.sso.knox.enabled', "true")
+
+      ambari_sso_details = ambari_configuration.get_ambari_sso_details()
+      if ambari_sso_details:
+        putAtlasApplicationProperty('atlas.sso.knox.providerurl', ambari_sso_details.get_jwt_provider_url())
+        putAtlasApplicationProperty('atlas.sso.knox.publicKey', ambari_sso_details.get_jwt_public_key(False, True))
+        putAtlasApplicationProperty('atlas.sso.knox.browser.useragent', 'Mozilla,Chrome')
+
+    # If SSO should be disabled for this service
+    elif ambari_configuration.should_disable_sso('ATLAS'):
+      putAtlasApplicationProperty('atlas.sso.knox.enabled', "false")
+
+    # Set the proxy user
+    knox_service_user = services['configurations']['knox-env']['properties']['knox_user'] \
+      if 'knox-env' in services['configurations'] and 'knox_user' in \
+         services['configurations']['knox-env']['properties'] \
+      else 'knox'
+    putAtlasApplicationProperty('atlas.proxyusers', knox_service_user)
+  pass
+
 
   def recommendDruidConfigurations(self, configurations, clusterData, services, hosts):
 
@@ -527,6 +558,16 @@ class HDP26StackAdvisor(HDP25StackAdvisor):
     else:
       putRangerUgsyncSite("ranger.usersync.group.searchenabled", "false")
 
+    self.recommendRangerConfigurationsForSSO(configurations, clusterData, services, hosts)
+
+  def recommendRangerConfigurationsForSSO(self, configurations, clusterData, services, hosts):
+    ambari_configuration = self.get_ambari_configuration(services)
+
+    # If SSO should be enabled for this service, continue
+    if ambari_configuration.should_enable_sso('RANGER'):
+      #TODO: See AMBARI-23332
+      pass
+
   def validateRangerUsersyncConfigurations(self, properties, recommendedDefaults, configurations, services, hosts):
     ranger_usersync_properties = properties
     validationItems = []

+ 286 - 0
ambari-server/src/main/resources/stacks/ambari_configuration.py

@@ -0,0 +1,286 @@
+#!/usr/bin/env ambari-python-wrap
+"""
+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.
+"""
+
+import os
+
+CERTIFICATE_HEADER = "-----BEGIN CERTIFICATE-----"
+CERTIFICATE_FOOTER = "-----END CERTIFICATE-----"
+
+
+def _get_from_dictionary(dictionary, key):
+  """
+  Safely returns the value from a dictionary that has the given key.
+
+  if the dictionary is None or does not contain the specified key, None is returned
+
+  :return: a dictionary
+  """
+  if dictionary and key in dictionary:
+    return dictionary[key]
+  else:
+    return None
+
+
+class AmbariConfiguration(object):
+  """
+  AmbariConfiguration is a class the encapsulates the Ambari server configuration data.
+
+  The Ambari server configurations are split into categories, where each category contains 0 or more
+  properties. For example, the 'ldap-configuration' category contains the
+  "ambari.ldap.authentication.enabled"
+  property.
+
+  ...
+  "ambari-server-configuration" : {
+    ...
+    "ldap-configuration" : {
+      ...
+      "ambari.ldap.authentication.enabled" : "true"
+      ...
+    },
+    ...
+    "sso-configuration" : {
+      ...
+      "ambari.sso.enabled_services" : "ATLAS, AMBARI"
+      ...
+    },
+    ...
+  }
+  ...
+  """
+
+  def __init__(self, services):
+    self.services = services
+
+  def get_ambari_server_configuration(self):
+    """
+    Safely returns the "ambari-server-configurations" dictionary from the services dictionary.
+
+    if the services dictionary is None or does not contain "ambari-server-configuration",
+    None is returned
+
+    :return: a dictionary
+    """
+    return _get_from_dictionary(self.services, "ambari-server-configuration")
+
+  def get_ambari_server_properties(self):
+    """
+    Safely returns the "ambari-server-properties" dictionary from the services dictionary.
+
+    if the services dictionary is None or does not contain "ambari-server-configuration",
+    None is returned
+
+    :return: a dictionary
+    """
+    return _get_from_dictionary(self.services, "ambari-server-properties")
+
+  def get_ambari_server_configuration_category(self, category):
+    """
+    Safely returns a dictionary of the properties for the requested category from the
+    "ambari-server-configurations" dictionary.
+
+    If the ambari-server-configurations dictionary is None or does not contain the
+    request category name, None is returned
+
+    :param category: the name of a category
+    :return: a dictionary
+    """
+    return _get_from_dictionary(self.get_ambari_server_configuration(), category)
+
+  def get_category_property_value(self, properties, property_name):
+    """
+    Safely gets the value of a property from a supplied dictionary of properties.
+
+    :param properties: a dictionary of properties
+    :param property_name: the name of a property to retrieve the value for
+    :return: a value or None, if the property does not exist
+    """
+    return _get_from_dictionary(properties, property_name)
+
+  def get_ambari_sso_configuration(self):
+    """
+    Safely gets a dictionary of properties for the "sso-configuration" category.
+
+    :return: a dictionary or None, if "sso-configuration" is not available
+    """
+    return self.get_ambari_server_configuration_category("sso-configuration")
+
+  def get_ambari_sso_configuration_value(self, property_name):
+    """
+    Safely gets a value for a "sso-configuration" property
+
+    :param property_name: the name of a property to retrieve the value for
+    :return: a value or None, if the property does not exist
+    """
+    return self.get_category_property_value(self.get_ambari_sso_configuration(), property_name)
+
+  def get_services_to_enable(self):
+    """
+    Safely gets the list of services that Ambari should enabled for SSO.
+
+    The returned value is a list of the relevant service names converted to lowercase.
+
+    :return: a list of service names converted to lowercase
+    """
+    sso_enabled_services = self.get_ambari_sso_configuration_value("ambari.sso.enabled_services")
+
+    return [x.strip().lower() for x in sso_enabled_services.strip().split(",")] \
+      if sso_enabled_services \
+      else []
+
+  def should_enable_sso(self, service_name):
+    """
+    Tests the configuration data to determine if the specified service should be configured by
+    Ambari to enable SSO integration.
+
+    The relevant property is "sso-configuration/ambari.sso.enabled_services", which is expected
+    to be a comma-delimited list of services to be enabled.
+
+    :param service_name: the name of the service to test
+    :return: True, if SSO should be enabled; False, otherwise
+    """
+    if "true" == self.get_ambari_sso_configuration_value("ambari.sso.manage_services"):
+      services_to_enable = self.get_services_to_enable()
+      return "*" in services_to_enable or service_name.lower() in services_to_enable
+    else:
+      return False
+
+  def should_disable_sso(self, service_name):
+    """
+    Tests the configuration data to determine if the specified service should be configured by
+    Ambari to disable SSO integration.
+
+    The relevant property is "sso-configuration/ambari.sso.enabled_services", which is expected
+    to be a comma-delimited list of services to be enabled.
+
+    :param service_name: the name of the service to test
+    :return: true, if SSO should be disabled; false, otherwise
+    """
+    if "true" == self.get_ambari_sso_configuration_value("ambari.sso.manage_services"):
+      services_to_enable = self.get_services_to_enable()
+      return "*" not in services_to_enable and service_name.lower() not in services_to_enable
+    else:
+      return False
+
+  def get_ambari_sso_details(self):
+    """
+    Gets a dictionary of properties that may be used to configure a service for SSO integration.
+    :return: a dictionary
+    """
+    return AmbariSSODetails(self.get_ambari_server_properties())
+
+
+class AmbariSSODetails(object):
+  """
+  AmbariSSODetails encapsulates the SSO configiuration data specified in the ambari-server-properties
+  """
+
+  def __init__(self, ambari_server_properties):
+    self.jwt_enabled = _get_from_dictionary(ambari_server_properties,
+                                            'authentication.jwt.enabled')
+    self.jwt_audiences = _get_from_dictionary(ambari_server_properties,
+                                              'authentication.jwt.audiences')
+    self.jwt_cookie_name = _get_from_dictionary(ambari_server_properties,
+                                                'authentication.jwt.cookieName')
+    self.jwt_provider_url = _get_from_dictionary(ambari_server_properties,
+                                                 'authentication.jwt.providerUrl')
+    self.jwt_public_key_file = _get_from_dictionary(ambari_server_properties,
+                                                    'authentication.jwt.publicKey')
+
+  def is_jwt_enabled(self):
+    """
+    Test is SSO/JWT authentication is enabled for Ambari
+    :return: True if JWT authentication is enabled for Ambari; False, otherwise
+    """
+    return "true" == self.jwt_enabled
+
+  def get_jwt_audiences(self):
+    """
+    Gets the configured JWT audiences list
+    :return the configured JWT audiences list:
+    """
+    return self.jwt_audiences
+
+  def get_jwt_cookie_name(self):
+    """
+    Gets the configured JWT cookie name
+    :return: the configured JWT cookie name
+    """
+    return self.jwt_cookie_name
+
+  def get_jwt_provider_url(self):
+    """
+    Gets the configured SSO provider URL
+    :return: the configured SSO provider URL
+    """
+    return self.jwt_provider_url
+
+  def get_jwt_public_key_file(self):
+    """
+    Gets the configured path to the public key file
+    :return: the configured path to the public key file
+    """
+    return self.jwt_public_key_file
+
+  def get_jwt_public_key(self, include_header_and_footer=False, remove_line_breaks=True):
+    """
+    Retrieves, formats, and returns the PEM data from the configured 509 certificate file.
+
+    Attempts to read in the file specified by the value in ambari-server-properties/authentication.jwt.publicKey.
+    If the file exists and is readable, the content is read.  If the header and foooter need to exists, and
+    do not, the will be added. If they need to be removed, they will be removed if they exist.  Any line
+    break characters will be leave alone unles the caller specifies them to be removed. Line break
+    characters will not be added if missing.
+
+    :param include_header_and_footer: True, to include the standard header and footer; False to remove
+    the standard header and footer
+    :param remove_line_breaks: True, remove and line breaks from PEM data; False to leave any existing line break as-is
+    :return:  formats, and returns the PEM data from an x509 certificate file
+    """
+    public_cert = None
+
+    if (self.jwt_public_key_file) and os.path.isfile(self.jwt_public_key_file):
+      with open(self.jwt_public_key_file, "r") as f:
+        public_cert = f.read()
+
+    if public_cert:
+      public_cert = public_cert.lstrip().rstrip()
+
+      if include_header_and_footer:
+        # Ensure the header and footer are in the string
+        if not public_cert.startswith(CERTIFICATE_HEADER):
+          public_cert = CERTIFICATE_HEADER + '\n' + public_cert
+
+        if not public_cert.endswith(CERTIFICATE_FOOTER):
+          public_cert = public_cert + '\n' + CERTIFICATE_FOOTER
+      else:
+        # Ensure the header and footer are not in the string
+        if public_cert.startswith(CERTIFICATE_HEADER):
+          public_cert = public_cert[len(CERTIFICATE_HEADER):]
+
+        if public_cert.endswith(CERTIFICATE_FOOTER):
+          public_cert = public_cert[:len(public_cert) - len(CERTIFICATE_FOOTER)]
+
+      # Remove any leading and ending line breaks
+      public_cert = public_cert.lstrip().rstrip()
+
+      if remove_line_breaks:
+        public_cert = public_cert.replace('\n', '')
+
+    return public_cert

+ 6 - 0
ambari-server/src/main/resources/stacks/service_advisor.py

@@ -85,6 +85,12 @@ class ServiceAdvisor(DefaultStackAdvisor):
     """
     pass
 
+  def getServiceConfigurationRecommendationsForSSO(self, configurations, clusterSummary, services, hosts):
+    """
+    Any SSO-related configuration recommendations for the service should be defined in this function.
+    """
+    pass
+
   def getServiceComponentLayoutValidations(self, services, hosts):
     """
     Returns an array of Validation objects about issues with the hostnames to which components are assigned.

+ 125 - 0
ambari-server/src/main/resources/stacks/stack_advisor.py

@@ -32,6 +32,7 @@ from math import ceil, floor
 from urlparse import urlparse
 
 # Local imports
+from ambari_configuration import AmbariConfiguration
 from resource_management.libraries.functions.data_structure_utils import get_from_dict
 from resource_management.core.exceptions import Fail
 
@@ -282,6 +283,64 @@ class StackAdvisor(object):
     """
     pass
 
+  def recommendConfigurationsForSSO(self, services, hosts):
+    """
+    Returns recommendation of SSO-related service configurations based on host-specific layout of components.
+
+    This function takes as input all details about services being installed, and hosts
+    they are being installed into, to recommend host-specific configurations.
+
+    @type services: dictionary
+    @param services: Dictionary containing all information about services and component layout selected by the user.
+    @type hosts: dictionary
+    @param hosts: Dictionary containing all information about hosts in this cluster
+    @rtype: dictionary
+    @return: Layout recommendation of service components on cluster hosts in Ambari Blueprints friendly format.
+        Example: {
+         "services": [
+          "HIVE",
+          "TEZ",
+          "YARN"
+         ],
+         "recommendations": {
+          "blueprint": {
+           "host_groups": [],
+           "configurations": {
+            "yarn-site": {
+             "properties": {
+              "yarn.scheduler.minimum-allocation-mb": "682",
+              "yarn.scheduler.maximum-allocation-mb": "2048",
+              "yarn.nodemanager.resource.memory-mb": "2048"
+             }
+            },
+            "tez-site": {
+             "properties": {
+              "tez.am.java.opts": "-server -Xmx546m -Djava.net.preferIPv4Stack=true -XX:+UseNUMA -XX:+UseParallelGC",
+              "tez.am.resource.memory.mb": "682"
+             }
+            },
+            "hive-site": {
+             "properties": {
+              "hive.tez.container.size": "682",
+              "hive.tez.java.opts": "-server -Xmx546m -Djava.net.preferIPv4Stack=true -XX:NewRatio=8 -XX:+UseNUMA -XX:+UseParallelGC",
+              "hive.auto.convert.join.noconditionaltask.size": "238026752"
+             }
+            }
+           }
+          },
+          "blueprint_cluster_binding": {
+           "host_groups": []
+          }
+         },
+         "hosts": [
+          "c6401.ambari.apache.org",
+          "c6402.ambari.apache.org",
+          "c6403.ambari.apache.org"
+         ]
+        }
+    """
+    pass
+
   def validateConfigurations(self, services, hosts):
     """"
     Returns array of Validation issues with configurations provided by user
@@ -1566,12 +1625,68 @@ class DefaultStackAdvisor(StackAdvisor):
 
     return recommendations
 
+  def recommendConfigurationsForSSO(self, services, hosts):
+    self.services = services
+
+    stackName = services["Versions"]["stack_name"]
+    stackVersion = services["Versions"]["stack_version"]
+    hostsList = [host["Hosts"]["host_name"] for host in hosts["items"]]
+    servicesList = [service["StackServices"]["service_name"] for service in services["services"]]
+    components = [component["StackServiceComponents"]["component_name"]
+                  for service in services["services"]
+                  for component in service["components"]]
+
+    clusterSummary = self.getConfigurationClusterSummary(servicesList, hosts, components, services)
+
+    recommendations = {
+      "Versions": {"stack_name": stackName, "stack_version": stackVersion},
+      "hosts": hostsList,
+      "services": servicesList,
+      "recommendations": {
+        "blueprint": {
+          "configurations": {},
+          "host_groups": []
+        },
+        "blueprint_cluster_binding": {
+          "host_groups": []
+        }
+      }
+    }
+
+    # If recommendation for config groups
+    if "config-groups" in services:
+      self.recommendConfigGroupsConfigurations(recommendations, services, components, hosts,
+                                 servicesList)
+    else:
+      configurations = recommendations["recommendations"]["blueprint"]["configurations"]
+
+      # there can be dependencies between service recommendations which require special ordering
+      # for now, make sure custom services (that have service advisors) run after standard ones
+      serviceAdvisors = []
+      recommenderDict = self.getServiceConfigurationRecommenderForSSODict()
+      for service in services["services"]:
+        serviceName = service["StackServices"]["service_name"]
+        calculation = recommenderDict.get(serviceName, None)
+        if calculation is not None:
+          calculation(configurations, clusterSummary, services, hosts)
+        else:
+          serviceAdvisor = self.getServiceAdvisor(serviceName)
+          if serviceAdvisor is not None:
+            serviceAdvisors.append(serviceAdvisor)
+      for serviceAdvisor in serviceAdvisors:
+        serviceAdvisor.getServiceConfigurationRecommendationsForSSO(configurations, clusterSummary, services, hosts)
+
+    return recommendations
+
   def getServiceConfigurationRecommender(self, service):
     return self.getServiceConfigurationRecommenderDict().get(service, None)
 
   def getServiceConfigurationRecommenderDict(self):
     return {}
 
+  def getServiceConfigurationRecommenderForSSODict(self):
+    return {}
+
   # Recommendation helper methods
   def isComponentHostsPopulated(self, component):
     hostnames = self.getComponentAttribute(component, "hostnames")
@@ -1593,6 +1708,16 @@ class DefaultStackAdvisor(StackAdvisor):
         return False
     return True
 
+  def get_ambari_configuration(self, services):
+    """
+    Gets the AmbariConfiguration object that can be used to request details about
+    the Ambari configuration. For example LDAP and SSO configurations
+
+    :param services: the services structure containing the "ambari-server-configurations" block
+    :return: an AmbariConfiguration
+    """
+    return AmbariConfiguration(services)
+
   def is_secured_cluster(self, services):
     """
     Detects if cluster is secured or not

+ 8 - 2
ambari-server/src/test/python/TestStackAdvisor.py

@@ -25,9 +25,15 @@ class TestStackAdvisorInitialization(TestCase):
     import imp
 
     self.test_directory = os.path.dirname(os.path.abspath(__file__))
-    stack_advisor_path = os.path.join(self.test_directory, '../../main/resources/scripts/stack_advisor.py')
+    resources_path = os.path.join(self.test_directory, '../../main/resources')
+    stack_advisor_path = os.path.abspath(os.path.join(resources_path, 'scripts/stack_advisor.py'))
+
+    ambari_configuration_path = os.path.abspath(os.path.join(resources_path, 'stacks/ambari_configuration.py'))
+    with open(ambari_configuration_path, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambari_configuration_path, ('.py', 'rb', imp.PY_SOURCE))
+
     with open(stack_advisor_path, 'rb') as fp:
-        self.stack_advisor = imp.load_module( 'stack_advisor', fp, stack_advisor_path, ('.py', 'rb', imp.PY_SOURCE) )
+      self.stack_advisor = imp.load_module( 'stack_advisor', fp, stack_advisor_path, ('.py', 'rb', imp.PY_SOURCE) )
 
   def test_stackAdvisorLoadedForNotHDPStack(self):
     path_template = os.path.join(self.test_directory, '../resources/stacks/{0}/{1}/services/stack_advisor.py')

+ 9 - 4
ambari-server/src/test/python/common-services/AMBARI_METRICS/test_service_advisor.py

@@ -26,13 +26,18 @@ from mock.mock import patch, MagicMock
 
 class TestAMBARI_METRICS010ServiceAdvisor(TestCase):
 
-  testDirectory = os.path.dirname(os.path.abspath(__file__))
-  stack_advisor_path = os.path.join(testDirectory, '../../../../main/resources/stacks/stack_advisor.py')
+  test_directory = os.path.dirname(os.path.abspath(__file__))
+  resources_path = os.path.join(test_directory, '../../../../main/resources')
+
+  ambari_configuration_path = os.path.abspath(os.path.join(resources_path, 'stacks/ambari_configuration.py'))
+  with open(ambari_configuration_path, 'rb') as fp:
+    imp.load_module('ambari_configuration', fp, ambari_configuration_path, ('.py', 'rb', imp.PY_SOURCE))
+
+  stack_advisor_path = os.path.join(resources_path, 'stacks/stack_advisor.py')
   with open(stack_advisor_path, 'rb') as fp:
     imp.load_module('stack_advisor', fp, stack_advisor_path, ('.py', 'rb', imp.PY_SOURCE))
 
-  serviceAdvisorPath = '../../../../main/resources/common-services/AMBARI_METRICS/0.1.0/service_advisor.py'
-  ambariMetrics010ServiceAdvisorPath = os.path.join(testDirectory, serviceAdvisorPath)
+  ambariMetrics010ServiceAdvisorPath = os.path.join(resources_path, 'common-services/AMBARI_METRICS/0.1.0/service_advisor.py')
   with open(ambariMetrics010ServiceAdvisorPath, 'rb') as fp:
     service_advisor_impl = imp.load_module('service_advisor_impl', fp, ambariMetrics010ServiceAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
 

+ 10 - 5
ambari-server/src/test/python/common-services/HAWQ/test_service_advisor.py

@@ -26,13 +26,18 @@ from mock.mock import patch, MagicMock
 
 class TestHAWQ200ServiceAdvisor(TestCase):
 
-  testDirectory = os.path.dirname(os.path.abspath(__file__))
-  stack_advisor_path = os.path.join(testDirectory, '../../../../main/resources/stacks/stack_advisor.py')
+  test_directory = os.path.dirname(os.path.abspath(__file__))
+  resources_path = os.path.join(test_directory, '../../../../main/resources')
+
+  ambari_configuration_path = os.path.abspath(os.path.join(resources_path, 'stacks/ambari_configuration.py'))
+  with open(ambari_configuration_path, 'rb') as fp:
+    imp.load_module('ambari_configuration', fp, ambari_configuration_path, ('.py', 'rb', imp.PY_SOURCE))
+
+  stack_advisor_path = os.path.join(resources_path, 'stacks/stack_advisor.py')
   with open(stack_advisor_path, 'rb') as fp:
     imp.load_module('stack_advisor', fp, stack_advisor_path, ('.py', 'rb', imp.PY_SOURCE))
 
-  serviceAdvisorPath = '../../../../main/resources/common-services/HAWQ/2.0.0/service_advisor.py'
-  hawq200ServiceAdvisorPath = os.path.join(testDirectory, serviceAdvisorPath)
+  hawq200ServiceAdvisorPath = os.path.join(resources_path, 'common-services/HAWQ/2.0.0/service_advisor.py')
   with open(hawq200ServiceAdvisorPath, 'rb') as fp:
     service_advisor_impl = imp.load_module('service_advisor_impl', fp, hawq200ServiceAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
 
@@ -44,7 +49,7 @@ class TestHAWQ200ServiceAdvisor(TestCase):
     return 'c6401.ambari.apache.org' if value is None else value
 
   def load_json(self, filename):
-    file = os.path.join(self.testDirectory, "../configs", filename)
+    file = os.path.join(self.test_directory, "../configs", filename)
     with open(file, 'rb') as f:
       data = json.load(f)
     return data

+ 9 - 4
ambari-server/src/test/python/common-services/LOGSEARCH/test_service_advisor.py

@@ -26,13 +26,18 @@ from mock.mock import patch, MagicMock
 
 class TestLOGSEARCH050ServiceAdvisor(TestCase):
 
-  testDirectory = os.path.dirname(os.path.abspath(__file__))
-  stack_advisor_path = os.path.join(testDirectory, '../../../../main/resources/stacks/stack_advisor.py')
+  test_directory = os.path.dirname(os.path.abspath(__file__))
+  resources_path = os.path.join(test_directory, '../../../../main/resources')
+
+  ambari_configuration_path = os.path.abspath(os.path.join(resources_path, 'stacks/ambari_configuration.py'))
+  with open(ambari_configuration_path, 'rb') as fp:
+    imp.load_module('ambari_configuration', fp, ambari_configuration_path, ('.py', 'rb', imp.PY_SOURCE))
+
+  stack_advisor_path = os.path.join(resources_path, 'stacks/stack_advisor.py')
   with open(stack_advisor_path, 'rb') as fp:
     imp.load_module('stack_advisor', fp, stack_advisor_path, ('.py', 'rb', imp.PY_SOURCE))
 
-  serviceAdvisorPath = '../../../../main/resources/common-services/LOGSEARCH/0.5.0/service_advisor.py'
-  logserch050ServiceAdvisorPath = os.path.join(testDirectory, serviceAdvisorPath)
+  logserch050ServiceAdvisorPath = os.path.join(resources_path, 'common-services/LOGSEARCH/0.5.0/service_advisor.py')
   with open(logserch050ServiceAdvisorPath, 'rb') as fp:
     service_advisor_impl = imp.load_module('service_advisor_impl', fp, logserch050ServiceAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
 

+ 10 - 5
ambari-server/src/test/python/common-services/PXF/test_service_advisor.py

@@ -24,13 +24,18 @@ from unittest import TestCase
 
 class TestPXF300ServiceAdvisor(TestCase):
 
-  testDirectory = os.path.dirname(os.path.abspath(__file__))
-  stack_advisor_path = os.path.join(testDirectory, '../../../../main/resources/stacks/stack_advisor.py')
+  test_directory = os.path.dirname(os.path.abspath(__file__))
+  resources_path = os.path.join(test_directory, '../../../../main/resources')
+
+  ambari_configuration_path = os.path.abspath(os.path.join(resources_path, 'stacks/ambari_configuration.py'))
+  with open(ambari_configuration_path, 'rb') as fp:
+    imp.load_module('ambari_configuration', fp, ambari_configuration_path, ('.py', 'rb', imp.PY_SOURCE))
+
+  stack_advisor_path = os.path.join(resources_path, 'stacks/stack_advisor.py')
   with open(stack_advisor_path, 'rb') as fp:
     imp.load_module('stack_advisor', fp, stack_advisor_path, ('.py', 'rb', imp.PY_SOURCE))
 
-  serviceAdvisorPath = '../../../../main/resources/common-services/PXF/3.0.0/service_advisor.py'
-  pxf300ServiceAdvisorPath = os.path.join(testDirectory, serviceAdvisorPath)
+  pxf300ServiceAdvisorPath = os.path.join(resources_path, 'common-services/PXF/3.0.0/service_advisor.py')
   with open(pxf300ServiceAdvisorPath, 'rb') as fp:
     service_advisor_impl = imp.load_module('service_advisor_impl', fp, pxf300ServiceAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
 
@@ -40,7 +45,7 @@ class TestPXF300ServiceAdvisor(TestCase):
     self.PXF_PATH = "export HBASE_CLASSPATH=${HBASE_CLASSPATH}:/usr/lib/pxf/pxf-hbase.jar"
 
   def load_json(self, filename):
-    file = os.path.join(self.testDirectory, "../configs", filename)
+    file = os.path.join(self.test_directory, "../configs", filename)
     with open(file, 'rb') as f:
       data = json.load(f)
     return data

+ 10 - 3
ambari-server/src/test/python/stacks/2.0.6/common/test_stack_advisor.py

@@ -27,11 +27,18 @@ class TestHDP206StackAdvisor(TestCase):
     import os
 
     testDirectory = os.path.dirname(os.path.abspath(__file__))
-    stackAdvisorPath = os.path.join(testDirectory, '../../../../../main/resources/stacks/stack_advisor.py')
-    hdp206StackAdvisorPath = os.path.join(testDirectory, '../../../../../main/resources/stacks/HDP/2.0.6/services/stack_advisor.py')
+
+    stacksPath = os.path.join(testDirectory, '../../../../../main/resources/stacks')
+    stackAdvisorPath = os.path.join(stacksPath, 'stack_advisor.py')
+    ambariConfigurationPath = os.path.abspath(os.path.join(stacksPath, 'ambari_configuration.py'))
+    hdp206StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.0.6/services/stack_advisor.py')
+
     hdp206StackAdvisorClassName = 'HDP206StackAdvisor'
+
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(stackAdvisorPath, 'rb') as fp:
-      stack_advisor = imp.load_module( 'stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE) )
+      imp.load_module( 'stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE) )
     with open(hdp206StackAdvisorPath, 'rb') as fp:
       self.stack_advisor_impl = imp.load_module('stack_advisor_impl', fp, hdp206StackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
     clazz = getattr(self.stack_advisor_impl, hdp206StackAdvisorClassName)

+ 10 - 3
ambari-server/src/test/python/stacks/2.1/common/test_stack_advisor.py

@@ -27,10 +27,17 @@ class TestHDP21StackAdvisor(TestCase):
     import imp
 
     self.testDirectory = os.path.dirname(os.path.abspath(__file__))
-    stackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/stack_advisor.py')
-    hdp206StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.0.6/services/stack_advisor.py')
-    hdp21StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.1/services/stack_advisor.py')
+
+    stacksPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks')
+    stackAdvisorPath = os.path.join(stacksPath, 'stack_advisor.py')
+    ambariConfigurationPath = os.path.abspath(os.path.join(stacksPath, 'ambari_configuration.py'))
+    hdp206StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.0.6/services/stack_advisor.py')
+    hdp21StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.1/services/stack_advisor.py')
+
     hdp21StackAdvisorClassName = 'HDP21StackAdvisor'
+
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(stackAdvisorPath, 'rb') as fp:
       imp.load_module('stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(hdp206StackAdvisorPath, 'rb') as fp:

+ 11 - 4
ambari-server/src/test/python/stacks/2.2/common/test_stack_advisor.py

@@ -27,11 +27,18 @@ class TestHDP22StackAdvisor(TestCase):
     import imp
     self.maxDiff = None
     self.testDirectory = os.path.dirname(os.path.abspath(__file__))
-    stackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/stack_advisor.py')
-    hdp206StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.0.6/services/stack_advisor.py')
-    hdp21StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.1/services/stack_advisor.py')
-    hdp22StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.2/services/stack_advisor.py')
+
+    stacksPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks')
+    stackAdvisorPath = os.path.join(stacksPath, 'stack_advisor.py')
+    ambariConfigurationPath = os.path.join(stacksPath, 'ambari_configuration.py')
+    hdp206StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.0.6/services/stack_advisor.py')
+    hdp21StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.1/services/stack_advisor.py')
+    hdp22StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.2/services/stack_advisor.py')
+
     hdp22StackAdvisorClassName = 'HDP22StackAdvisor'
+
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(stackAdvisorPath, 'rb') as fp:
       imp.load_module('stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(hdp206StackAdvisorPath, 'rb') as fp:

+ 5 - 0
ambari-server/src/test/python/stacks/2.2/common/test_stack_advisor_perf.py

@@ -31,6 +31,11 @@ class TestStackAdvisorPerformance(TestCase):
     self.testDirectory = os.path.dirname(os.path.abspath(__file__))
 
   def instantiate_stack_advisor(self):
+    stacksPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks')
+    ambariConfigurationPath = os.path.abspath(os.path.join(stacksPath, 'ambari_configuration.py'))
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
+
     self.load_stack_advisor('main/resources/stacks/stack_advisor.py', 'stack_advisor')
 
     stack_advisors = (

+ 12 - 5
ambari-server/src/test/python/stacks/2.3/common/test_stack_advisor.py

@@ -30,12 +30,19 @@ class TestHDP23StackAdvisor(TestCase):
     self.maxDiff = None
     if 'util' in dir(unittest): unittest.util._MAX_LENGTH=2000
     self.testDirectory = os.path.dirname(os.path.abspath(__file__))
-    stackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/stack_advisor.py')
-    hdp206StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.0.6/services/stack_advisor.py')
-    hdp21StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.1/services/stack_advisor.py')
-    hdp22StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.2/services/stack_advisor.py')
-    hdp23StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.3/services/stack_advisor.py')
+
+    stacksPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks')
+    stackAdvisorPath = os.path.join(stacksPath, 'stack_advisor.py')
+    ambariConfigurationPath = os.path.abspath(os.path.join(stacksPath, 'ambari_configuration.py'))
+    hdp206StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.0.6/services/stack_advisor.py')
+    hdp21StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.1/services/stack_advisor.py')
+    hdp22StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.2/services/stack_advisor.py')
+    hdp23StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.3/services/stack_advisor.py')
+
     hdp23StackAdvisorClassName = 'HDP23StackAdvisor'
+
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(stackAdvisorPath, 'rb') as fp:
       imp.load_module('stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(hdp206StackAdvisorPath, 'rb') as fp:

+ 13 - 7
ambari-server/src/test/python/stacks/2.5/common/test_stack_advisor.py

@@ -28,15 +28,21 @@ class TestHDP25StackAdvisor(TestCase):
     import imp
     self.maxDiff = None
     self.testDirectory = os.path.dirname(os.path.abspath(__file__))
-    stackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/stack_advisor.py')
-    hdp206StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.0.6/services/stack_advisor.py')
-    hdp21StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.1/services/stack_advisor.py')
-    hdp22StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.2/services/stack_advisor.py')
-    hdp23StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.3/services/stack_advisor.py')
-    hdp24StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.4/services/stack_advisor.py')
-    hdp25StackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/HDP/2.5/services/stack_advisor.py')
+
+    stacksPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks')
+    stackAdvisorPath = os.path.join(stacksPath, 'stack_advisor.py')
+    ambariConfigurationPath = os.path.abspath(os.path.join(stacksPath, 'ambari_configuration.py'))
+    hdp206StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.0.6/services/stack_advisor.py')
+    hdp21StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.1/services/stack_advisor.py')
+    hdp22StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.2/services/stack_advisor.py')
+    hdp23StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.3/services/stack_advisor.py')
+    hdp24StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.4/services/stack_advisor.py')
+    hdp25StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.5/services/stack_advisor.py')
+
     hdp25StackAdvisorClassName = 'HDP25StackAdvisor'
 
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(stackAdvisorPath, 'rb') as fp:
       imp.load_module('stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(hdp206StackAdvisorPath, 'rb') as fp:

+ 14 - 15
ambari-server/src/test/python/stacks/2.6/common/test_stack_advisor.py

@@ -27,23 +27,22 @@ class TestHDP26StackAdvisor(TestCase):
     import imp
     self.maxDiff = None
     self.testDirectory = os.path.dirname(os.path.abspath(__file__))
-    stackAdvisorPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks/stack_advisor.py')
-    hdp206StackAdvisorPath = os.path.join(self.testDirectory,
-                                          '../../../../../main/resources/stacks/HDP/2.0.6/services/stack_advisor.py')
-    hdp21StackAdvisorPath = os.path.join(self.testDirectory,
-                                         '../../../../../main/resources/stacks/HDP/2.1/services/stack_advisor.py')
-    hdp22StackAdvisorPath = os.path.join(self.testDirectory,
-                                         '../../../../../main/resources/stacks/HDP/2.2/services/stack_advisor.py')
-    hdp23StackAdvisorPath = os.path.join(self.testDirectory,
-                                         '../../../../../main/resources/stacks/HDP/2.3/services/stack_advisor.py')
-    hdp24StackAdvisorPath = os.path.join(self.testDirectory,
-                                         '../../../../../main/resources/stacks/HDP/2.4/services/stack_advisor.py')
-    hdp25StackAdvisorPath = os.path.join(self.testDirectory,
-                                         '../../../../../main/resources/stacks/HDP/2.5/services/stack_advisor.py')
-    hdp26StackAdvisorPath = os.path.join(self.testDirectory,
-                                         '../../../../../main/resources/stacks/HDP/2.6/services/stack_advisor.py')
+
+    stacksPath = os.path.join(self.testDirectory, '../../../../../main/resources/stacks')
+    stackAdvisorPath = os.path.join(stacksPath, 'stack_advisor.py')
+    ambariConfigurationPath = os.path.abspath(os.path.join(stacksPath, 'ambari_configuration.py'))
+    hdp206StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.0.6/services/stack_advisor.py')
+    hdp21StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.1/services/stack_advisor.py')
+    hdp22StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.2/services/stack_advisor.py')
+    hdp23StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.3/services/stack_advisor.py')
+    hdp24StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.4/services/stack_advisor.py')
+    hdp25StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.5/services/stack_advisor.py')
+    hdp26StackAdvisorPath = os.path.join(stacksPath, 'HDP/2.6/services/stack_advisor.py')
+
     hdp26StackAdvisorClassName = 'HDP26StackAdvisor'
 
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(stackAdvisorPath, 'rb') as fp:
       imp.load_module('stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
     with open(hdp206StackAdvisorPath, 'rb') as fp:

+ 308 - 0
ambari-server/src/test/python/stacks/test_ambari_configuration.py

@@ -0,0 +1,308 @@
+"""
+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.
+"""
+
+import os
+
+from mock.mock import MagicMock, patch
+from unittest import TestCase
+
+# Mock classes for reading from a file
+class MagicFile(object):
+  def __init__(self, data):
+    self.data = data
+
+  def read(self):
+    return self.data
+
+  def __exit__(self, exc_type, exc_val, exc_tb):
+    pass
+
+  def __enter__(self):
+    return self
+pass
+
+class TestAmbariConfiguration(TestCase):
+
+  def setUp(self):
+    import imp
+    self.test_directory = os.path.dirname(os.path.abspath(__file__))
+
+    relative_path = '../../../main/resources/stacks/ambari_configuration.py'
+    ambari_configuration_path = os.path.abspath(os.path.join(self.test_directory, relative_path))
+    class_name = 'AmbariConfiguration'
+
+    with open(ambari_configuration_path, 'rb') as fp:
+      ambari_configuration_impl = imp.load_module('ambari_configuration', fp,
+                                                  ambari_configuration_path,
+                                                  ('.py', 'rb', imp.PY_SOURCE))
+
+    self.ambari_configuration_class = getattr(ambari_configuration_impl, class_name)
+
+  def testMissingData(self):
+    ambari_configuration = self.ambari_configuration_class('{}')
+    self.assertIsNone(ambari_configuration.get_ambari_server_configuration())
+    self.assertIsNone(ambari_configuration.get_ambari_server_properties())
+
+  def testMissingSSOConfiguration(self):
+    services_json = {
+      "ambari-server-configuration": {
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    self.assertIsNone(ambari_configuration.get_ambari_sso_configuration())
+    self.assertIsNone(ambari_configuration.get_ambari_sso_configuration_value("ambari.sso.property"))
+    self.assertFalse(ambari_configuration.should_enable_sso("AMBARI"))
+
+  def testMissingAmbariProperties(self):
+    services_json = {
+      "ambari-server-configuration": {
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    ambari_sso_details = ambari_configuration.get_ambari_sso_details()
+    self.assertFalse(ambari_sso_details.is_jwt_enabled())
+    self.assertIsNone(ambari_sso_details.get_jwt_audiences())
+    self.assertIsNone(ambari_sso_details.get_jwt_cookie_name())
+    self.assertIsNone(ambari_sso_details.get_jwt_provider_url())
+    self.assertIsNone(ambari_sso_details.get_jwt_public_key_file())
+    self.assertIsNone(ambari_sso_details.get_jwt_public_key())
+
+  def testAmbariSSOConfigurationNotManagingServices(self):
+    services_json = {
+      "ambari-server-configuration": {
+        "sso-configuration": {
+          "ambari.sso.enabled_services": "AMBARI"
+        }
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    self.assertIsNotNone(ambari_configuration.get_ambari_sso_configuration())
+    self.assertEquals("AMBARI", ambari_configuration.get_ambari_sso_configuration_value("ambari.sso.enabled_services"))
+    self.assertFalse(ambari_configuration.should_enable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_disable_sso("AMBARI"))
+
+    services_json = {
+      "ambari-server-configuration": {
+        "sso-configuration": {
+          "ambari.sso.manage_services" : "false",
+          "ambari.sso.enabled_services" : "AMBARI, RANGER"
+        }
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    self.assertIsNotNone(ambari_configuration.get_ambari_sso_configuration())
+    self.assertEquals("AMBARI, RANGER", ambari_configuration.get_ambari_sso_configuration_value("ambari.sso.enabled_services"))
+    self.assertFalse(ambari_configuration.should_enable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_disable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_enable_sso("RANGER"))
+    self.assertFalse(ambari_configuration.should_disable_sso("RANGER"))
+
+    services_json = {
+      "ambari-server-configuration": {
+        "sso-configuration": {
+          "ambari.sso.manage_services" : "false",
+          "ambari.sso.enabled_services" : "*"
+        }
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    self.assertIsNotNone(ambari_configuration.get_ambari_sso_configuration())
+    self.assertEquals("*", ambari_configuration.get_ambari_sso_configuration_value("ambari.sso.enabled_services"))
+    self.assertFalse(ambari_configuration.should_enable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_disable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_enable_sso("RANGER"))
+    self.assertFalse(ambari_configuration.should_disable_sso("RANGER"))
+
+  def testAmbariSSOConfigurationManagingServices(self):
+    services_json = {
+      "ambari-server-configuration": {
+        "sso-configuration": {
+          "ambari.sso.manage_services" : "true",
+          "ambari.sso.enabled_services": "AMBARI"
+        }
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    self.assertIsNotNone(ambari_configuration.get_ambari_sso_configuration())
+    self.assertEquals("AMBARI", ambari_configuration.get_ambari_sso_configuration_value("ambari.sso.enabled_services"))
+    self.assertTrue(ambari_configuration.should_enable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_disable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_enable_sso("RANGER"))
+    self.assertTrue(ambari_configuration.should_disable_sso("RANGER"))
+
+    services_json = {
+      "ambari-server-configuration": {
+        "sso-configuration": {
+          "ambari.sso.manage_services" : "true",
+          "ambari.sso.enabled_services" : "AMBARI, RANGER"
+        }
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    self.assertIsNotNone(ambari_configuration.get_ambari_sso_configuration())
+    self.assertEquals("AMBARI, RANGER", ambari_configuration.get_ambari_sso_configuration_value("ambari.sso.enabled_services"))
+    self.assertTrue(ambari_configuration.should_enable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_disable_sso("AMBARI"))
+    self.assertTrue(ambari_configuration.should_enable_sso("RANGER"))
+    self.assertFalse(ambari_configuration.should_disable_sso("RANGER"))
+
+    services_json = {
+      "ambari-server-configuration": {
+        "sso-configuration": {
+          "ambari.sso.manage_services" : "true",
+          "ambari.sso.enabled_services" : "*"
+        }
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    self.assertIsNotNone(ambari_configuration.get_ambari_sso_configuration())
+    self.assertEquals("*", ambari_configuration.get_ambari_sso_configuration_value("ambari.sso.enabled_services"))
+    self.assertTrue(ambari_configuration.should_enable_sso("AMBARI"))
+    self.assertFalse(ambari_configuration.should_disable_sso("AMBARI"))
+    self.assertTrue(ambari_configuration.should_enable_sso("RANGER"))
+    self.assertFalse(ambari_configuration.should_disable_sso("RANGER"))
+
+  def testAmbariJWTProperties(self):
+    services_json = {
+      "ambari-server-properties": {
+        "authentication.jwt.publicKey": "/etc/ambari-server/conf/jwt-cert.pem",
+        "authentication.jwt.enabled": "true",
+        "authentication.jwt.providerUrl": "https://knox.ambari.apache.org",
+        "authentication.jwt.cookieName": "hadoop-jwt",
+        "authentication.jwt.audiences": ""
+      },
+      "ambari-server-configuration": {
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    ambari_sso_details = ambari_configuration.get_ambari_sso_details()
+    self.assertTrue(ambari_sso_details.is_jwt_enabled())
+    self.assertEquals('', ambari_sso_details.get_jwt_audiences())
+    self.assertEquals('hadoop-jwt', ambari_sso_details.get_jwt_cookie_name())
+    self.assertEquals('https://knox.ambari.apache.org', ambari_sso_details.get_jwt_provider_url())
+    self.assertEquals('/etc/ambari-server/conf/jwt-cert.pem', ambari_sso_details.get_jwt_public_key_file())
+    self.assertIsNone(ambari_sso_details.get_jwt_public_key())  # This is none since the file does not exist for unit tests.
+
+
+  @patch("os.path.isfile", new=MagicMock(return_value=True))
+  @patch('__builtin__.open')
+  def testReadCertFileWithHeaderAndFooter(self, open_mock):
+    mock_file = MagicFile(
+      '-----BEGIN CERTIFICATE-----\n'
+      'MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\n'
+      '................................................................\n'
+      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy\n'
+      '-----END CERTIFICATE-----\n')
+    open_mock.side_effect = [mock_file, mock_file, mock_file, mock_file]
+
+    services_json = {
+      "ambari-server-properties": {
+        "authentication.jwt.publicKey": "/etc/ambari-server/conf/jwt-cert.pem",
+        "authentication.jwt.enabled": "true",
+        "authentication.jwt.providerUrl": "https://knox.ambari.apache.org",
+        "authentication.jwt.cookieName": "hadoop-jwt",
+        "authentication.jwt.audiences": ""
+      },
+      "ambari-server-configuration": {
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    ambari_sso_details = ambari_configuration.get_ambari_sso_details()
+
+    self.assertEquals('-----BEGIN CERTIFICATE-----\n'
+                      'MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\n'
+                      '................................................................\n'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy\n'
+                      '-----END CERTIFICATE-----',
+                      ambari_sso_details.get_jwt_public_key(True, False))
+
+    self.assertEquals('-----BEGIN CERTIFICATE-----'
+                      'MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD'
+                      '................................................................'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy'
+                      '-----END CERTIFICATE-----',
+                      ambari_sso_details.get_jwt_public_key(True, True))
+
+    self.assertEquals('MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\n'
+                      '................................................................\n'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy',
+                      ambari_sso_details.get_jwt_public_key(False, False))
+
+    self.assertEquals('MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD'
+                      '................................................................'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy',
+                      ambari_sso_details.get_jwt_public_key(False, True))
+
+  @patch("os.path.isfile", new=MagicMock(return_value=True))
+  @patch('__builtin__.open')
+  def testReadCertFileWithoutHeaderAndFooter(self, open_mock):
+    mock_file = MagicFile(
+      'MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\n'
+      '................................................................\n'
+      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy\n')
+    open_mock.side_effect = [mock_file, mock_file, mock_file, mock_file]
+
+    services_json = {
+      "ambari-server-properties": {
+        "authentication.jwt.publicKey": "/etc/ambari-server/conf/jwt-cert.pem",
+        "authentication.jwt.enabled": "true",
+        "authentication.jwt.providerUrl": "https://knox.ambari.apache.org",
+        "authentication.jwt.cookieName": "hadoop-jwt",
+        "authentication.jwt.audiences": ""
+      },
+      "ambari-server-configuration": {
+      }
+    }
+
+    ambari_configuration = self.ambari_configuration_class(services_json)
+    ambari_sso_details = ambari_configuration.get_ambari_sso_details()
+
+    self.assertEquals('-----BEGIN CERTIFICATE-----\n'
+                      'MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\n'
+                      '................................................................\n'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy\n'
+                      '-----END CERTIFICATE-----',
+                      ambari_sso_details.get_jwt_public_key(True, False))
+
+    self.assertEquals('-----BEGIN CERTIFICATE-----'
+                      'MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD'
+                      '................................................................'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy'
+                      '-----END CERTIFICATE-----',
+                      ambari_sso_details.get_jwt_public_key(True, True))
+
+    self.assertEquals('MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD\n'
+                      '................................................................\n'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy',
+                      ambari_sso_details.get_jwt_public_key(False, False))
+
+    self.assertEquals('MIIE3DCCA8SgAwIBAgIJAKfbOMmFyOlNMA0GCSqGSIb3DQEBBQUAMIGkMQswCQYD'
+                      '................................................................'
+                      'dXRpbmcxFzAVBgNVBAMTDmNsb3VkYnJlYWstcmdsMSUwIwYJKoZIhvcNAQkBFhZy',
+                      ambari_sso_details.get_jwt_public_key(False, True))
+

+ 7 - 1
ambari-server/src/test/python/stacks/test_stack_adviser.py

@@ -25,10 +25,16 @@ class TestBasicAdvisor(TestCase):
     import imp
     self.maxDiff = None
     self.testDirectory = os.path.dirname(os.path.abspath(__file__))
-    stackAdvisorPath = os.path.abspath(os.path.join(self.testDirectory, '../../../main/resources/stacks/stack_advisor.py'))
 
     default_sa_classname = 'DefaultStackAdvisor'
 
+    stacksPath = os.path.join(self.testDirectory, '../../../main/resources/stacks')
+
+    ambariConfigurationPath = os.path.abspath(os.path.join(stacksPath, 'ambari_configuration.py'))
+    with open(ambariConfigurationPath, 'rb') as fp:
+      imp.load_module('ambari_configuration', fp, ambariConfigurationPath, ('.py', 'rb', imp.PY_SOURCE))
+
+    stackAdvisorPath = os.path.abspath(os.path.join(stacksPath, 'stack_advisor.py'))
     with open(stackAdvisorPath, 'rb') as fp:
       stack_advisor_impl = imp.load_module('stack_advisor', fp, stackAdvisorPath, ('.py', 'rb', imp.PY_SOURCE))
 

+ 0 - 0
ambari-server/src/test/resources/TestAmbaryServer.samples/dummy_stack/HIVE/package/.hash


+ 2 - 0
ambari-serviceadvisor/src/main/java/org/apache/ambari/serviceadvisor/ServiceAdvisorCommandType.java

@@ -29,6 +29,8 @@ public enum ServiceAdvisorCommandType {
 
   RECOMMEND_CONFIGURATIONS("recommend-configurations"),
 
+  RECOMMEND_CONFIGURATIONS_FOR_SSO("recommend-configurations-for-sso"),
+
   RECOMMEND_CONFIGURATION_DEPENDENCIES("recommend-configuration-dependencies"),
 
   VALIDATE_CONFIGURATIONS("validate-configurations");