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

adoroszlai pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 2411ccf  AMBARI-25037. Allow skipping parts of Add Service request 
validation (#2717)
2411ccf is described below

commit 2411ccf6d633aa97cf48d181b6254f3ac36d5f89
Author: Doroszlai, Attila <[email protected]>
AuthorDate: Thu Dec 13 16:06:59 2018 +0100

    AMBARI-25037. Allow skipping parts of Add Service request validation (#2717)
---
 .../server/controller/AddServiceRequest.java       |  45 ++++++++-
 .../topology/addservice/RequestValidator.java      |  29 ++++--
 .../server/controller/AddServiceRequestTest.java   |   4 +
 .../topology/addservice/RequestValidatorTest.java  | 112 ++++++++++++++-------
 .../test/resources/add_service_api/request1.json   |   1 +
 5 files changed, 142 insertions(+), 49 deletions(-)

diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
index 04d63a1..02577ca 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AddServiceRequest.java
@@ -71,15 +71,17 @@ public class AddServiceRequest {
   private static final String STACK_VERSION = "stack_version";
   private static final String SERVICES = "services";
   private static final String COMPONENTS = "components";
+  private static final String VALIDATION = "validation";
 
   public static final Set<String> TOP_LEVEL_PROPERTIES = ImmutableSet.of(
-    OPERATION_TYPE, CONFIG_RECOMMENDATION_STRATEGY, PROVISION_ACTION_PROPERTY,
+    OPERATION_TYPE, CONFIG_RECOMMENDATION_STRATEGY, PROVISION_ACTION_PROPERTY, 
VALIDATION,
     STACK_NAME, STACK_VERSION, SERVICES, COMPONENTS, CONFIGURATIONS
   );
 
   private final OperationType operationType;
   private final ConfigRecommendationStrategy recommendationStrategy;
   private final ProvisionAction provisionAction;
+  private final ValidationType validationType;
   private final String stackName;
   private final String stackVersion;
   private final Set<Service> services;
@@ -93,6 +95,7 @@ public class AddServiceRequest {
     @JsonProperty(OPERATION_TYPE) OperationType operationType,
     @JsonProperty(CONFIG_RECOMMENDATION_STRATEGY) ConfigRecommendationStrategy 
recommendationStrategy,
     @JsonProperty(PROVISION_ACTION_PROPERTY) ProvisionAction provisionAction,
+    @JsonProperty(VALIDATION) ValidationType validationType,
     @JsonProperty(STACK_NAME) String stackName,
     @JsonProperty(STACK_VERSION) String stackVersion,
     @JsonProperty(SERVICES) Set<Service> services,
@@ -101,7 +104,7 @@ public class AddServiceRequest {
     @JsonProperty(CREDENTIALS) Set<Credential> credentials,
     @JsonProperty(CONFIGURATIONS) Collection<? extends Map<String, ?>> configs
   ) {
-    this(operationType, recommendationStrategy, provisionAction, stackName, 
stackVersion, services, components,
+    this(operationType, recommendationStrategy, provisionAction, 
validationType, stackName, stackVersion, services, components,
       security, credentials,
       ConfigurableHelper.parseConfigs(configs)
     );
@@ -111,6 +114,7 @@ public class AddServiceRequest {
     OperationType operationType,
     ConfigRecommendationStrategy recommendationStrategy,
     ProvisionAction provisionAction,
+    ValidationType validationType,
     String stackName,
     String stackVersion,
     Set<Service> services,
@@ -122,6 +126,7 @@ public class AddServiceRequest {
     this.operationType = null != operationType ? operationType : 
OperationType.ADD_SERVICE;
     this.recommendationStrategy = null != recommendationStrategy ? 
recommendationStrategy : ConfigRecommendationStrategy.NEVER_APPLY;
     this.provisionAction = null != provisionAction ? provisionAction : 
ProvisionAction.INSTALL_AND_START;
+    this.validationType = validationType != null ? validationType : 
ValidationType.DEFAULT;
     this.stackName = stackName;
     this.stackVersion = stackVersion;
     this.services = null != services ? services : emptySet();
@@ -160,6 +165,12 @@ public class AddServiceRequest {
     return provisionAction;
   }
 
+  @JsonProperty(VALIDATION)
+  @ApiModelProperty(name = VALIDATION)
+  public ValidationType getValidationType() {
+    return validationType;
+  }
+
   @JsonProperty(STACK_NAME)
   @ApiModelProperty(name = STACK_NAME)
   public String getStackName() {
@@ -233,6 +244,7 @@ public class AddServiceRequest {
     AddServiceRequest other = (AddServiceRequest) obj;
 
     return Objects.equals(operationType, other.operationType) &&
+      Objects.equals(validationType, other.validationType) &&
       Objects.equals(recommendationStrategy, other.recommendationStrategy) &&
       Objects.equals(provisionAction, other.provisionAction) &&
       Objects.equals(stackName, other.stackName) &&
@@ -246,7 +258,7 @@ public class AddServiceRequest {
 
   @Override
   public int hashCode() {
-    return Objects.hash(operationType, recommendationStrategy, 
provisionAction, stackName, stackVersion,
+    return Objects.hash(operationType, validationType, recommendationStrategy, 
provisionAction, stackName, stackVersion,
       services, components, configuration, security);
     // credentials is ignored for hashcode, since it's not serialized
   }
@@ -255,6 +267,7 @@ public class AddServiceRequest {
   public String toString() {
     return MoreObjects.toStringHelper(this)
       .add(OPERATION_TYPE, operationType)
+      .add(VALIDATION, validationType)
       .add(CONFIG_RECOMMENDATION_STRATEGY, recommendationStrategy)
       .add(PROVISION_ACTION_PROPERTY, provisionAction)
       .add(STACK_NAME, stackName)
@@ -273,6 +286,32 @@ public class AddServiceRequest {
     ADD_SERVICE, DELETE_SERVICE, MOVE_SERVICE
   }
 
+  public enum ValidationType {
+    /**
+     * Perform all validation checks.
+     */
+    STRICT {
+      @Override
+      public boolean strictValidation() {
+        return true;
+      }
+    },
+    /**
+     * Skip the parts of validation that are not strictly necessary.
+     */
+    PERMISSIVE {
+      @Override
+      public boolean strictValidation() {
+        return false;
+      }
+    },
+    ;
+
+    public static final ValidationType DEFAULT = STRICT;
+
+    public abstract boolean strictValidation();
+  }
+
   public static final class Component {
 
     static final String COMPONENT_NAME = "name";
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
index f976f82..4a8d14c 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/topology/addservice/RequestValidator.java
@@ -43,6 +43,7 @@ import org.apache.ambari.server.state.SecurityType;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptor;
 import org.apache.ambari.server.state.kerberos.KerberosDescriptorFactory;
+import org.apache.ambari.server.state.kerberos.KerberosServiceDescriptor;
 import org.apache.ambari.server.topology.Configuration;
 import org.apache.ambari.server.topology.SecurityConfigurationFactory;
 import org.apache.ambari.server.topology.StackFactory;
@@ -134,7 +135,7 @@ public class RequestValidator {
   @VisibleForTesting
   void validateSecurity() {
     request.getSecurity().ifPresent(requestSecurity -> {
-      CHECK.checkArgument(requestSecurity.getType() == 
cluster.getSecurityType(),
+      CHECK.checkArgument(!strictValidation() || requestSecurity.getType() == 
cluster.getSecurityType(),
         "Security type in the request (%s), if specified, should match 
cluster's security type (%s)",
         requestSecurity.getType(), cluster.getSecurityType()
       );
@@ -162,13 +163,17 @@ public class RequestValidator {
 
         KerberosDescriptor descriptor = 
kerberosDescriptorFactory.createInstance(descriptorMap);
 
-        Set<String> servicesWithNewDescriptor = 
descriptor.getServices().keySet();
-        Set<String> newServices = state.getNewServices().keySet();
-        Set<String> nonNewServices = 
ImmutableSet.copyOf(Sets.difference(servicesWithNewDescriptor, newServices));
+        if (strictValidation()) {
+          Map<String, KerberosServiceDescriptor> descriptorServices = 
descriptor.getServices();
+          Set<String> servicesWithNewDescriptor = descriptorServices != null ? 
descriptorServices.keySet() : ImmutableSet.of();
+          Set<String> newServices = state.getNewServices().keySet();
+          Set<String> nonNewServices = 
ImmutableSet.copyOf(Sets.difference(servicesWithNewDescriptor, newServices));
 
-        CHECK.checkArgument(nonNewServices.isEmpty(),
-          "Kerberos descriptor should be provided only for new services, but 
found other services: %s",
-          nonNewServices);
+          CHECK.checkArgument(nonNewServices.isEmpty(),
+            "Kerberos descriptor should be provided only for new services, but 
found other services: %s",
+            nonNewServices
+          );
+        }
 
         try {
           descriptor.toMap();
@@ -238,8 +243,10 @@ public class RequestValidator {
   void validateConfiguration() {
     Configuration config = request.getConfiguration();
 
-    for (String type : NOT_ALLOWED_CONFIG_TYPES) {
-      CHECK.checkArgument(!config.getProperties().containsKey(type), "Cannot 
change '%s' configuration in Add Service request", type);
+    if (strictValidation()) {
+      for (String type : NOT_ALLOWED_CONFIG_TYPES) {
+        CHECK.checkArgument(!config.getProperties().containsKey(type), "Cannot 
change '%s' configuration in Add Service request", type);
+      }
     }
 
     Configuration clusterConfig = getClusterDesiredConfigs();
@@ -262,6 +269,10 @@ public class RequestValidator {
       "Requested host not associated with cluster %s: %s", 
cluster.getClusterName(), unknownHosts);
   }
 
+  private boolean strictValidation() {
+    return request.getValidationType().strictValidation();
+  }
+
   private Configuration getClusterDesiredConfigs() {
     try {
       return Configuration.of(configHelper.calculateExistingConfigs(cluster));
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
index 35491d6..2d97bd0 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/AddServiceRequestTest.java
@@ -21,6 +21,8 @@ package org.apache.ambari.server.controller;
 import static org.apache.ambari.server.controller.AddServiceRequest.Component;
 import static 
org.apache.ambari.server.controller.AddServiceRequest.OperationType.ADD_SERVICE;
 import static org.apache.ambari.server.controller.AddServiceRequest.Service;
+import static 
org.apache.ambari.server.controller.AddServiceRequest.ValidationType.PERMISSIVE;
+import static 
org.apache.ambari.server.controller.AddServiceRequest.ValidationType.STRICT;
 import static 
org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_AND_START;
 import static 
org.apache.ambari.server.controller.internal.ProvisionAction.INSTALL_ONLY;
 import static 
org.apache.ambari.server.topology.ConfigRecommendationStrategy.ALWAYS_APPLY;
@@ -86,6 +88,7 @@ public class AddServiceRequestTest {
     assertEquals(ADD_SERVICE, request.getOperationType());
     assertEquals(ALWAYS_APPLY, request.getRecommendationStrategy());
     assertEquals(INSTALL_ONLY, request.getProvisionAction());
+    assertEquals(PERMISSIVE, request.getValidationType());
     assertEquals("HDP", request.getStackName());
     assertEquals("3.0", request.getStackVersion());
 
@@ -134,6 +137,7 @@ public class AddServiceRequestTest {
     assertEquals(ADD_SERVICE, request.getOperationType());
     assertEquals(NEVER_APPLY, request.getRecommendationStrategy());
     assertEquals(INSTALL_AND_START, request.getProvisionAction());
+    assertEquals(STRICT, request.getValidationType());
     assertNull(request.getStackName());
     assertNull(request.getStackVersion());
     assertEquals(Optional.empty(), request.getSecurity());
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
index d0ab25c..e9c3ff7 100644
--- 
a/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/topology/addservice/RequestValidatorTest.java
@@ -77,13 +77,19 @@ public class RequestValidatorTest extends EasyMockSupport {
 
   @Before
   public void setUp() {
-    validator.setState(RequestValidator.State.INITIAL);
-    expect(cluster.getClusterName()).andReturn("TEST").anyTimes();
-    expect(cluster.getServices()).andStubReturn(ImmutableMap.of());
-    expect(cluster.getSecurityType()).andStubReturn(SecurityType.NONE);
-    expect(request.getServices()).andStubReturn(ImmutableSet.of());
-    expect(request.getComponents()).andStubReturn(ImmutableSet.of());
-    expect(request.getSecurity()).andStubReturn(Optional.empty());
+    try {
+      validator.setState(RequestValidator.State.INITIAL);
+      expect(cluster.getClusterName()).andReturn("TEST").anyTimes();
+      expect(cluster.getServices()).andStubReturn(ImmutableMap.of());
+      expect(cluster.getSecurityType()).andStubReturn(SecurityType.NONE);
+      
expect(configHelper.calculateExistingConfigs(cluster)).andStubReturn(Configuration.newEmpty().asPair());
+      expect(request.getServices()).andStubReturn(ImmutableSet.of());
+      expect(request.getComponents()).andStubReturn(ImmutableSet.of());
+      expect(request.getSecurity()).andStubReturn(Optional.empty());
+      
expect(request.getValidationType()).andStubReturn(AddServiceRequest.ValidationType.DEFAULT);
+    } catch (Exception e) {
+      throw new IllegalStateException(e);
+    }
   }
 
   @After
@@ -347,22 +353,15 @@ public class RequestValidatorTest extends EasyMockSupport 
{
 
   @Test
   public void rejectsNoneSecurityWhenClusterHasKerberos() {
-    secureCluster();
-    
expect(request.getSecurity()).andReturn(Optional.of(SecurityConfiguration.NONE)).anyTimes();
-    replayAll();
-
-    IllegalArgumentException e = assertThrows(IllegalArgumentException.class, 
validator::validateSecurity);
-    assertTrue(e.getMessage().contains("KERBEROS"));
+    testBothValidationTypes(validator::validateSecurity, "KERBEROS", () -> {
+      secureCluster();
+      
expect(request.getSecurity()).andReturn(Optional.of(SecurityConfiguration.NONE)).anyTimes();
+    });
   }
 
   @Test
   public void rejectsKerberosSecurityWhenClusterHasNone() {
-    requestSpecifiesSecurity();
-    replayAll();
-
-    assertThrows(IllegalArgumentException.class, validator::validateSecurity);
-    IllegalArgumentException e = assertThrows(IllegalArgumentException.class, 
validator::validateSecurity);
-    assertTrue(e.getMessage().contains("KERBEROS"));
+    testBothValidationTypes(validator::validateSecurity, "KERBEROS", 
this::requestSpecifiesSecurity);
   }
 
   @Test
@@ -371,7 +370,6 @@ public class RequestValidatorTest extends EasyMockSupport {
     
expect(request.getSecurity()).andReturn(Optional.of(requestSecurity)).anyTimes();
     replayAll();
 
-    assertThrows(IllegalArgumentException.class, validator::validateSecurity);
     IllegalArgumentException e = assertThrows(IllegalArgumentException.class, 
validator::validateSecurity);
     assertTrue(e.getMessage().contains("Kerberos descriptor"));
   }
@@ -382,7 +380,6 @@ public class RequestValidatorTest extends EasyMockSupport {
     
expect(request.getSecurity()).andReturn(Optional.of(requestSecurity)).anyTimes();
     replayAll();
 
-    assertThrows(IllegalArgumentException.class, validator::validateSecurity);
     IllegalArgumentException e = assertThrows(IllegalArgumentException.class, 
validator::validateSecurity);
     assertTrue(e.getMessage().contains("Kerberos descriptor reference"));
   }
@@ -394,7 +391,6 @@ public class RequestValidatorTest extends EasyMockSupport {
     
expect(request.getSecurity()).andReturn(Optional.of(invalidConfig)).anyTimes();
     replayAll();
 
-    assertThrows(IllegalArgumentException.class, validator::validateSecurity);
     IllegalArgumentException e = assertThrows(IllegalArgumentException.class, 
validator::validateSecurity);
     assertTrue(e.getMessage().contains("Kerberos descriptor and reference"));
   }
@@ -442,7 +438,7 @@ public class RequestValidatorTest extends EasyMockSupport {
   }
 
   @Test
-  public void rejectsDescriptorWithAdditionalServices() {
+  public void acceptsDescriptorWithAdditionalServices() {
     String newService = "KAFKA", otherService = "ZOOKEEPER";
     secureCluster();
     requestServices(true, newService);
@@ -454,6 +450,18 @@ public class RequestValidatorTest extends EasyMockSupport {
   }
 
   @Test
+  public void acceptsDescriptorWithoutServices() {
+    secureCluster();
+    requestServices(true, "KAFKA");
+    KerberosDescriptor kerberosDescriptor = 
requestHasKerberosDescriptorFor(false);
+    replayAll();
+
+    validator.validateSecurity();
+
+    assertEquals(kerberosDescriptor, 
validator.getState().getKerberosDescriptor());
+  }
+
+  @Test
   public void combinesRequestConfigWithClusterAndStack() throws 
AmbariException {
     Configuration requestConfig = Configuration.newEmpty();
     requestConfig.setProperty("kafka-broker", "zookeeper.connect", 
"zookeeper.connect:request");
@@ -482,22 +490,47 @@ public class RequestValidatorTest extends EasyMockSupport 
{
 
   @Test
   public void rejectsKerberosEnvChange() {
-    Configuration requestConfig = Configuration.newEmpty();
-    requestConfig.setProperty("kerberos-env", "some-property", "some-value");
-    
expect(request.getConfiguration()).andReturn(requestConfig.copy()).anyTimes();
-    replayAll();
-
-    assertThrows(IllegalArgumentException.class, 
validator::validateConfiguration);
+    testBothValidationTypes(validator::validateConfiguration, () -> {
+      Configuration requestConfig = Configuration.newEmpty();
+      requestConfig.setProperty("kerberos-env", "some-property", "some-value");
+      
expect(request.getConfiguration()).andReturn(requestConfig.copy()).anyTimes();
+      
validator.setState(RequestValidator.State.INITIAL.with(simpleMockStack()));
+    });
   }
 
   @Test
   public void rejectsKrb5ConfChange() {
-    Configuration requestConfig = Configuration.newEmpty();
-    requestConfig.setProperty("krb5-conf", "some-property", "some-value");
-    
expect(request.getConfiguration()).andReturn(requestConfig.copy()).anyTimes();
+    testBothValidationTypes(validator::validateConfiguration, () -> {
+      Configuration requestConfig = Configuration.newEmpty();
+      requestConfig.setProperty("krb5-conf", "some-property", "some-value");
+      
expect(request.getConfiguration()).andReturn(requestConfig.copy()).anyTimes();
+      
validator.setState(RequestValidator.State.INITIAL.with(simpleMockStack()));
+    });
+  }
+
+  /**
+   * Tests that the state created by {@code testCaseSetup} is rejected by 
strict validation, but accepted by permissive one.
+   * @param validation the method to call on validator
+   * @param testCaseSetup code to setup the state ("when"), call to 
replayAll() should be omitted
+   */
+  private void testBothValidationTypes(Runnable validation, Runnable 
testCaseSetup) {
+    testBothValidationTypes(validation, null, testCaseSetup);
+  }
+
+  private void testBothValidationTypes(Runnable validation, String 
expectedMessage, Runnable testCaseSetup) {
+    testCaseSetup.run();
     replayAll();
+    Exception e = assertThrows(IllegalArgumentException.class, validation);
+    if (expectedMessage != null) {
+      assertTrue(e.getMessage().contains(expectedMessage));
+    };
 
-    assertThrows(IllegalArgumentException.class, 
validator::validateConfiguration);
+    resetAll();
+    setUp();
+    permissiveValidation();
+    testCaseSetup.run();
+    replayAll();
+    validation.run();
   }
 
   private static void verifyConfigOverrides(Configuration requestConfig, 
Configuration clusterConfig, Configuration stackConfig, Configuration 
actualConfig) {
@@ -529,10 +562,11 @@ public class RequestValidatorTest extends EasyMockSupport 
{
   private Stack simpleMockStack() {
     Stack stack = createNiceMock(Stack.class);
     Set<String> stackServices = ImmutableSet.of("KAFKA", "ZOOKEEPER");
-    expect(stack.getServices()).andReturn(stackServices).anyTimes();
-    
expect(stack.getServiceForComponent("KAFKA_BROKER")).andReturn("KAFKA").anyTimes();
-    
expect(stack.getServiceForComponent("ZOOKEEPER_SERVER")).andReturn("ZOOKEEPER").anyTimes();
-    
expect(stack.getServiceForComponent("ZOOKEEPER_CLIENT")).andReturn("ZOOKEEPER").anyTimes();
+    expect(stack.getServices()).andStubReturn(stackServices);
+    
expect(stack.getServiceForComponent("KAFKA_BROKER")).andStubReturn("KAFKA");
+    
expect(stack.getServiceForComponent("ZOOKEEPER_SERVER")).andStubReturn("ZOOKEEPER");
+    
expect(stack.getServiceForComponent("ZOOKEEPER_CLIENT")).andStubReturn("ZOOKEEPER");
+    
expect(stack.getValidDefaultConfig()).andStubReturn(Configuration.newEmpty());
     return stack;
   }
 
@@ -542,6 +576,10 @@ public class RequestValidatorTest extends EasyMockSupport {
     );
   }
 
+  private void permissiveValidation() {
+    
expect(request.getValidationType()).andReturn(AddServiceRequest.ValidationType.PERMISSIVE).anyTimes();
+  }
+
   private void requestServices(boolean validated, String... services) {
     expect(request.getServices()).andReturn(
       Arrays.stream(services)
diff --git a/ambari-server/src/test/resources/add_service_api/request1.json 
b/ambari-server/src/test/resources/add_service_api/request1.json
index 73320bd..75fb12f 100644
--- a/ambari-server/src/test/resources/add_service_api/request1.json
+++ b/ambari-server/src/test/resources/add_service_api/request1.json
@@ -1,6 +1,7 @@
 {
   "operation_type" : "ADD_SERVICE",
   "config_recommendation_strategy" : "ALWAYS_APPLY",
+  "validation": "PERMISSIVE",
   "provision_action" : "INSTALL_ONLY",
   "stack_name" : "HDP",
   "stack_version" : "3.0",

Reply via email to