Browse Source

AMBARI-15150. Add framework support for bulk delete API (ajit)

Ajit Kumar 9 years ago
parent
commit
5af48693ef
20 changed files with 543 additions and 70 deletions
  1. 10 0
      ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java
  2. 9 0
      ambari-server/src/main/java/org/apache/ambari/server/api/handlers/CreateHandler.java
  3. 21 0
      ambari-server/src/main/java/org/apache/ambari/server/api/handlers/DeleteHandler.java
  4. 9 0
      ambari-server/src/main/java/org/apache/ambari/server/api/handlers/QueryCreateHandler.java
  5. 11 0
      ambari-server/src/main/java/org/apache/ambari/server/api/handlers/UpdateHandler.java
  6. 123 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/DeleteResultMetadata.java
  7. 4 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/Result.java
  8. 14 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultImpl.java
  9. 25 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultMetadata.java
  10. 42 1
      ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java
  11. 6 2
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractResourceProvider.java
  12. 58 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DeleteStatusMetaData.java
  13. 13 4
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestStatusImpl.java
  14. 2 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/spi/RequestStatus.java
  15. 25 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/spi/RequestStatusMetaData.java
  16. 5 62
      ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java
  17. 70 0
      ambari-server/src/test/java/org/apache/ambari/server/api/services/DeleteResultMetaDataTest.java
  18. 34 1
      ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java
  19. 50 0
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/DeleteStatusMetaDataTest.java
  20. 12 0
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestStatusImplTest.java

+ 10 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/handlers/BaseManagementHandler.java

@@ -27,6 +27,7 @@ import org.apache.ambari.server.api.util.TreeNode;
 import org.apache.ambari.server.controller.spi.ClusterController;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.RequestStatusMetaData;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.ClusterControllerHelper;
 
@@ -100,6 +101,7 @@ public abstract class BaseManagementHandler implements RequestHandler {
         resourcesNode.addChild(resource, resource.getType() + ":" + count++);
       }
     }
+    result.setResultMetadata(convert(requestStatus.getStatusMetadata()));
     return result;
   }
 
@@ -133,4 +135,12 @@ public abstract class BaseManagementHandler implements RequestHandler {
    * @return the result of the persist operation
    */
   protected abstract Result persist(ResourceInstance resource, RequestBody body);
+
+  /**
+   * Convert {@link RequestStatusMetaData} object to {@link ResultMetadata} which will be
+   * included in {@link Result} object.
+   * @param requestStatusMetaData request status details
+   * @return result details
+   */
+  protected abstract ResultMetadata convert(RequestStatusMetaData requestStatusMetaData);
 }

+ 9 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/handlers/CreateHandler.java

@@ -73,4 +73,13 @@ public class CreateHandler extends BaseManagementHandler {
     }
     return result;
   }
+
+  @Override
+  protected ResultMetadata convert(RequestStatusMetaData requestStatusMetaData) {
+    if (requestStatusMetaData == null) {
+      return null;
+    }
+
+    throw new UnsupportedOperationException();
+  }
 }

+ 21 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/handlers/DeleteHandler.java

@@ -21,13 +21,17 @@ package org.apache.ambari.server.api.handlers;
 import org.apache.ambari.server.ConfigGroupNotFoundException;
 import org.apache.ambari.server.api.resources.ResourceInstance;
 
+import org.apache.ambari.server.api.services.DeleteResultMetadata;
 import org.apache.ambari.server.api.services.RequestBody;
 import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultMetadata;
 import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.api.services.ResultStatus;
+import org.apache.ambari.server.controller.internal.DeleteStatusMetaData;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
 import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.RequestStatusMetaData;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.security.authorization.AuthorizationException;
@@ -73,4 +77,21 @@ public class DeleteHandler extends BaseManagementHandler implements RequestHandl
 
     return result;
   }
+
+  @Override
+  protected ResultMetadata convert(RequestStatusMetaData requestStatusMetaData) {
+    if (requestStatusMetaData == null) {
+      return null;
+    }
+
+    if (requestStatusMetaData.getClass() != DeleteStatusMetaData.class) {
+      throw new IllegalArgumentException("RequestStatusDetails is not of type DeleteStatusDetails");
+    }
+
+    DeleteStatusMetaData statusDetails = (DeleteStatusMetaData) requestStatusMetaData;
+    DeleteResultMetadata resultDetails = new DeleteResultMetadata();
+    resultDetails.addDeletedKeys(statusDetails.getDeletedKeys());
+    resultDetails.addExceptions(statusDetails.getExceptionForKeys());
+    return resultDetails;
+  }
 }

+ 9 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/handlers/QueryCreateHandler.java

@@ -194,6 +194,15 @@ public class QueryCreateHandler extends BaseManagementHandler {
     return result;
   }
 
+  @Override
+  protected ResultMetadata convert(RequestStatusMetaData requestStatusMetaData) {
+    if (requestStatusMetaData == null) {
+      return null;
+    }
+
+    throw new UnsupportedOperationException();
+  }
+
   /**
    * Get the resource factory instance.
    * @return  a factory for creating resource instances

+ 11 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/handlers/UpdateHandler.java

@@ -21,11 +21,13 @@ package org.apache.ambari.server.api.handlers;
 import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.api.services.RequestBody;
 import org.apache.ambari.server.api.services.Result;
+import org.apache.ambari.server.api.services.ResultMetadata;
 import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.api.services.ResultStatus;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
 import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.RequestStatusMetaData;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.security.authorization.AuthorizationException;
@@ -72,4 +74,13 @@ public class UpdateHandler extends BaseManagementHandler {
 
     return result;
   }
+
+  @Override
+  protected ResultMetadata convert(RequestStatusMetaData requestStatusMetaData) {
+    if (requestStatusMetaData == null) {
+      return null;
+    }
+
+    throw new UnsupportedOperationException();
+  }
 }

+ 123 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/DeleteResultMetadata.java

@@ -0,0 +1,123 @@
+/**
+ * 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;
+
+import org.apache.ambari.server.ObjectNotFoundException;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.security.authorization.AuthorizationException;
+import org.apache.commons.lang.Validate;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Implementation of ResultDetails for DELETE API requests.
+ */
+public class DeleteResultMetadata implements ResultMetadata {
+  //Keys for all successful delete resources.
+  private final Set<String> deletedKeys;
+
+  //ResultStatus for keys which threw exception during delete.
+  private final Map<String, ResultStatus> excptions;
+
+  public DeleteResultMetadata() {
+    this.deletedKeys = new HashSet<>();
+    this.excptions = new HashMap<>();
+  }
+
+  /**
+   * Add successfully deleted key.
+   * @param key - successfully deleted key.
+   */
+  public void addDeletedKey(String key) {
+    Validate.notNull(key);
+    deletedKeys.add(key);
+  }
+
+  /**
+   * Add successfully deleted keys.
+   * @param keys - successfully deleted keys.
+   */
+  public void addDeletedKeys(Collection<String> keys) {
+    Validate.notNull(keys);
+    deletedKeys.addAll(keys);
+  }
+
+  /**
+   * Add exception thrown during delete of a key.
+   * @param key - successfully deleted keys.
+   * @param ex - exception
+   */
+  public void addException(String key, Exception ex) {
+    Validate.notNull(key);
+    Validate.notNull(ex);
+    ResultStatus resultStatus = getResultStatusForException(ex);
+    excptions.put(key, resultStatus);
+  }
+
+  /**
+   * Add exception thrown during delete of keys.
+   */
+  public void addExceptions(Map<String, Exception> exceptionKeyMap) {
+    if (exceptionKeyMap == null) {
+      return;
+    }
+
+    for (Map.Entry<String, Exception> exceptionEntry : exceptionKeyMap.entrySet()) {
+      ResultStatus resultStatus = getResultStatusForException(exceptionEntry.getValue());
+      excptions.put(exceptionEntry.getKey(), resultStatus);
+    }
+  }
+
+  public Set<String> getDeletedKeys() {
+    return Collections.unmodifiableSet(deletedKeys);
+  }
+
+  public Map<String, ResultStatus> getExcptions() {
+    return Collections.unmodifiableMap(excptions);
+  }
+
+  /**
+   * Factory method to create {@link ResultStatus} object for Exception.
+   *
+   * @param ex - exception
+   * @return ResultStatus
+   */
+  private ResultStatus getResultStatusForException(Exception ex) {
+    Validate.notNull(ex);
+    if (ex.getClass() == AuthorizationException.class) {
+      return new ResultStatus(ResultStatus.STATUS.FORBIDDEN, ex);
+    } else if (ex.getClass() == SystemException.class) {
+      return new ResultStatus(ResultStatus.STATUS.SERVER_ERROR, ex);
+    } else if (ex instanceof ObjectNotFoundException) {
+      return new ResultStatus(ResultStatus.STATUS.NOT_FOUND, ex);
+    } else if (ex.getClass() == UnsupportedPropertyException.class) {
+      return new ResultStatus(ResultStatus.STATUS.BAD_REQUEST, ex);
+    } else {
+      return new ResultStatus(ResultStatus.STATUS.SERVER_ERROR, ex);
+    }
+  }
+}

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

@@ -45,4 +45,8 @@ public interface Result {
   ResultStatus getStatus();
 
   void setResultStatus(ResultStatus status);
+
+  void setResultMetadata(ResultMetadata resultMetadata);
+
+  ResultMetadata getResultMetadata();
 }

+ 14 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultImpl.java

@@ -39,6 +39,11 @@ public class ResultImpl implements Result {
    */
   private ResultStatus m_status;
 
+  /**
+   * Result metadata.
+   */
+  private ResultMetadata m_resultMetadata;
+
   /**
    * Tree structure which holds the results
    */
@@ -82,5 +87,14 @@ public class ResultImpl implements Result {
   public void setResultStatus(ResultStatus status) {
     m_status = status;
   }
+
+  public void setResultMetadata(ResultMetadata resultMetadata) {
+    m_resultMetadata = resultMetadata;
+  }
+
+  public ResultMetadata getResultMetadata() {
+    return m_resultMetadata;
+  }
+
 }
 

+ 25 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/ResultMetadata.java

@@ -0,0 +1,25 @@
+/**
+ * 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;
+
+/**
+ * Marker interface for Metadata object for API result.
+ */
+public interface ResultMetadata {
+}

+ 42 - 1
ambari-server/src/main/java/org/apache/ambari/server/api/services/serializers/JsonSerializer.java

@@ -18,6 +18,8 @@
 
 package org.apache.ambari.server.api.services.serializers;
 
+import org.apache.ambari.server.api.services.DeleteResultMetadata;
+import org.apache.ambari.server.api.services.ResultMetadata;
 import org.apache.ambari.server.api.services.ResultStatus;
 import org.apache.ambari.server.api.services.Result;
 import org.apache.ambari.server.api.util.TreeNodeImpl;
@@ -63,7 +65,7 @@ public class JsonSerializer implements ResultSerializer {
 
       TreeNode<Resource> treeNode = result.getResultTree();
       processNode(treeNode);
-
+      processResultMetadata(result.getResultMetadata());
       m_generator.close();
       return bytesOut.toString("UTF-8");
     } catch (IOException e) {
@@ -101,6 +103,45 @@ public class JsonSerializer implements ResultSerializer {
     return bytesOut;
   }
 
+  private void processResultMetadata(ResultMetadata resultMetadata) throws IOException {
+    if (resultMetadata == null) {
+      return;
+    }
+
+    if (resultMetadata.getClass() == DeleteResultMetadata.class) {
+      processResultMetadata((DeleteResultMetadata) resultMetadata);
+    } else {
+      throw new IllegalArgumentException("ResultDetails is not of type DeleteResultDetails, cannot parse");
+    }
+  }
+
+  private void processResultMetadata(DeleteResultMetadata deleteResultMetadata) throws IOException {
+    m_generator.writeStartObject();
+    m_generator.writeArrayFieldStart("deleteResult");
+    //write successfully deleted keys
+    for (String key : deleteResultMetadata.getDeletedKeys()) {
+      m_generator.writeStartObject();
+      m_generator.writeObjectFieldStart("deleted");
+      m_generator.writeStringField("key", key);
+      m_generator.writeEndObject();
+      m_generator.writeEndObject();
+    }
+
+    //write exceptions
+    for (Map.Entry<String, ResultStatus> entry : deleteResultMetadata.getExcptions().entrySet()) {
+      ResultStatus resultStatus = entry.getValue();
+      m_generator.writeStartObject();
+      m_generator.writeObjectFieldStart("error");
+      m_generator.writeStringField("key", entry.getKey());
+      m_generator.writeNumberField("code", resultStatus.getStatusCode());
+      m_generator.writeStringField("message", resultStatus.getMessage());
+      m_generator.writeEndObject();
+      m_generator.writeEndObject();
+    }
+    m_generator.writeEndArray();
+    m_generator.writeEndObject();
+  }
+
   private void processNode(TreeNode<Resource> node) throws IOException {
     if (isObject(node)) {
       m_generator.writeStartObject();

+ 6 - 2
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractResourceProvider.java

@@ -205,6 +205,10 @@ public abstract class AbstractResourceProvider extends BaseProvider implements R
    * @return the request status
    */
   protected RequestStatus getRequestStatus(RequestStatusResponse response, Set<Resource> associatedResources) {
+    return getRequestStatus(response, associatedResources, null);
+  }
+
+  protected RequestStatus getRequestStatus(RequestStatusResponse response, Set<Resource> associatedResources, RequestStatusMetaData requestStatusMetaData) {
     if (response != null){
       Resource requestResource = new ResourceImpl(Resource.Type.Request);
       if (response.getMessage() != null){
@@ -212,9 +216,9 @@ public abstract class AbstractResourceProvider extends BaseProvider implements R
       }
       requestResource.setProperty(PropertyHelper.getPropertyId("Requests", "id"), response.getRequestId());
       requestResource.setProperty(PropertyHelper.getPropertyId("Requests", "status"), "Accepted");
-      return new RequestStatusImpl(requestResource, associatedResources);
+      return new RequestStatusImpl(requestResource, associatedResources, requestStatusMetaData);
     }
-    return new RequestStatusImpl(null, associatedResources);
+    return new RequestStatusImpl(null, associatedResources, requestStatusMetaData);
   }
 
   /**

+ 58 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DeleteStatusMetaData.java

@@ -0,0 +1,58 @@
+/**
+ * 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.controller.internal;
+
+import org.apache.ambari.server.controller.spi.RequestStatusMetaData;
+import org.apache.commons.lang.Validate;
+
+import javax.annotation.concurrent.NotThreadSafe;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+@NotThreadSafe
+public class DeleteStatusMetaData implements RequestStatusMetaData {
+  private Set<String> deletedKeys;
+  private Map<String, Exception> exceptionMap;
+  public DeleteStatusMetaData() {
+    this.deletedKeys = new HashSet<>();
+    this.exceptionMap = new HashMap<>();
+  }
+
+  public void addDeletedKey(String key) {
+    Validate.notEmpty(key, "Key should not be empty");
+    deletedKeys.add(key);
+  }
+
+  public Set<String> getDeletedKeys() {
+    return Collections.unmodifiableSet(deletedKeys);
+  }
+
+  public void addException(String key, Exception exception) {
+    Validate.notEmpty(key, "Key should not be empty");
+    Validate.notNull(exception, "Exception cannot be null");
+    exceptionMap.put(key, exception);
+  }
+
+  public Map<String, Exception> getExceptionForKeys() {
+    return Collections.unmodifiableMap(exceptionMap);
+  }
+}

+ 13 - 4
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestStatusImpl.java

@@ -18,11 +18,11 @@
 package org.apache.ambari.server.controller.internal;
 
 import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.RequestStatusMetaData;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 
 import java.util.Collections;
-import java.util.HashSet;
 import java.util.Set;
 
 /**
@@ -32,16 +32,20 @@ public class RequestStatusImpl implements RequestStatus{
 
   private final Resource requestResource;
   private final Set<Resource> associatedResources;
-
+  private final RequestStatusMetaData requestStatusMetaData;
   public RequestStatusImpl(Resource requestResource) {
-    this.requestResource     = requestResource;
-    this.associatedResources = Collections.emptySet();
+    this(requestResource, null, null);
   }
 
   public RequestStatusImpl(Resource requestResource, Set<Resource> associatedResources) {
+    this(requestResource, associatedResources, null);
+  }
+
+  public RequestStatusImpl(Resource requestResource, Set<Resource> associatedResources, RequestStatusMetaData requestStatusMetaData) {
     this.requestResource     = requestResource;
     this.associatedResources = associatedResources == null ?
         Collections.<Resource>emptySet() : associatedResources;
+    this.requestStatusMetaData = requestStatusMetaData;
   }
 
   @Override
@@ -60,4 +64,9 @@ public class RequestStatusImpl implements RequestStatus{
     return requestResource == null ? Status.Complete :
         Status.valueOf((String) requestResource.getPropertyValue(PropertyHelper.getPropertyId("Requests", "status")));
   }
+
+  @Override
+  public RequestStatusMetaData getStatusMetadata() {
+    return requestStatusMetaData;
+  }
 }

+ 2 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/spi/RequestStatus.java

@@ -47,6 +47,8 @@ public interface RequestStatus {
    */
   public Status getStatus();
 
+  RequestStatusMetaData getStatusMetadata();
+
   /**
    * Request status.
    */

+ 25 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/spi/RequestStatusMetaData.java

@@ -0,0 +1,25 @@
+/**
+ * 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.controller.spi;
+
+/**
+ * Marker interface for metadata embedded in {@link RequestStatus}.
+ */
+public interface RequestStatusMetaData {
+}

+ 5 - 62
ambari-server/src/test/java/org/apache/ambari/server/api/handlers/DeleteHandlerTest.java

@@ -28,6 +28,7 @@ import org.apache.ambari.server.api.services.Result;
 import org.apache.ambari.server.api.services.ResultStatus;
 import org.apache.ambari.server.api.services.persistence.PersistenceManager;
 import org.apache.ambari.server.api.util.TreeNode;
+import org.apache.ambari.server.controller.internal.DeleteStatusMetaData;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 import org.apache.ambari.server.controller.spi.Resource;
@@ -57,66 +58,6 @@ public class DeleteHandlerTest {
     ViewRegistry.initInstance(new ViewRegistry(publisher));
   }
 
-  @Test
-  public void testHandleRequest__Synchronous_NoPropsInBody() throws Exception {
-    Request request = createMock(Request.class);
-    RequestBody body = createNiceMock(RequestBody.class);
-    ResourceInstance resource = createMock(ResourceInstance.class);
-    PersistenceManager pm = createStrictMock(PersistenceManager.class);
-    RequestStatus status = createMock(RequestStatus.class);
-    Resource resource1 = createMock(Resource.class);
-    Resource resource2 = createMock(Resource.class);
-    Predicate userPredicate = createNiceMock(Predicate.class);
-    Query query = createNiceMock(Query.class);
-    Renderer renderer = new DefaultRenderer();
-
-    Set<Resource> setResources = new HashSet<Resource>();
-    setResources.add(resource1);
-    setResources.add(resource2);
-
-    // expectations
-    expect(request.getResource()).andReturn(resource).atLeastOnce();
-    expect(request.getBody()).andReturn(body).atLeastOnce();
-
-    expect(request.getQueryPredicate()).andReturn(userPredicate).atLeastOnce();
-    expect(resource.getQuery()).andReturn(query).atLeastOnce();
-    expect(request.getRenderer()).andReturn(renderer);
-
-    query.setRenderer(renderer);
-    query.setUserPredicate(userPredicate);
-
-    expect(pm.delete(resource, body)).andReturn(status);
-    expect(status.getStatus()).andReturn(RequestStatus.Status.Complete);
-    expect(status.getAssociatedResources()).andReturn(setResources);
-    expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
-    expect(resource2.getType()).andReturn(Resource.Type.Cluster).anyTimes();
-
-    replay(request, body, resource, pm, status, resource1, resource2, userPredicate, query);
-
-    Result result = new TestDeleteHandler(pm).handleRequest(request);
-
-    assertNotNull(result);
-    TreeNode<Resource> tree = result.getResultTree();
-    assertEquals(1, tree.getChildren().size());
-    TreeNode<Resource> resourcesNode = tree.getChild("resources");
-    assertEquals(2, resourcesNode.getChildren().size());
-    boolean foundResource1 = false;
-    boolean foundResource2 = false;
-    for(TreeNode<Resource> child : resourcesNode.getChildren()) {
-      Resource r = child.getObject();
-      if (r == resource1 && ! foundResource1) {
-        foundResource1 = true;
-      } else if (r == resource2 && ! foundResource2) {
-        foundResource2 = true;
-      } else {
-        fail();
-      }
-    }
-
-    assertEquals(ResultStatus.STATUS.OK, result.getStatus().getStatus());
-    verify(request, body, resource, pm, status, resource1, resource2, userPredicate, query);
-  }
-
   @Test
   public void testHandleRequest__Synchronous() throws Exception {
     Request request = createMock(Request.class);
@@ -130,7 +71,7 @@ public class DeleteHandlerTest {
     Query query = createNiceMock(Query.class);
     Renderer renderer = new DefaultRenderer();
 
-    Set<Resource> setResources = new HashSet<Resource>();
+    Set<Resource> setResources = new HashSet<>();
     setResources.add(resource1);
     setResources.add(resource2);
 
@@ -148,6 +89,7 @@ public class DeleteHandlerTest {
     expect(pm.delete(resource, body)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Complete);
     expect(status.getAssociatedResources()).andReturn(setResources);
+    expect(status.getStatusMetadata()).andReturn(new DeleteStatusMetaData());
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
     expect(resource2.getType()).andReturn(Resource.Type.Cluster).anyTimes();
 
@@ -207,6 +149,7 @@ public class DeleteHandlerTest {
     expect(pm.delete(resource, body)).andReturn(status);
     expect(status.getStatus()).andReturn(RequestStatus.Status.Accepted);
     expect(status.getAssociatedResources()).andReturn(setResources);
+    expect(status.getStatusMetadata()).andReturn(null);
     expect(resource1.getType()).andReturn(Resource.Type.Cluster).anyTimes();
     expect(resource2.getType()).andReturn(Resource.Type.Cluster).anyTimes();
     expect(status.getRequestResource()).andReturn(requestResource).anyTimes();
@@ -297,7 +240,7 @@ public class DeleteHandlerTest {
     assertNotNull(result);
     assertEquals(ResultStatus.STATUS.ACCEPTED, result.getStatus().getStatus());
   }  
-  
+
   private class TestDeleteHandler extends DeleteHandler {
     private PersistenceManager m_testPm;
 

+ 70 - 0
ambari-server/src/test/java/org/apache/ambari/server/api/services/DeleteResultMetaDataTest.java

@@ -0,0 +1,70 @@
+/**
+ * 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;
+
+import com.google.common.collect.Sets;
+import org.apache.ambari.server.HostNotFoundException;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.security.authorization.AuthorizationException;
+import org.apache.commons.collections.CollectionUtils;
+import org.junit.Test;
+
+import java.util.Collections;
+import java.util.Map;
+
+import static org.junit.Assert.*;
+
+public class DeleteResultMetaDataTest {
+
+  @Test
+  public void testDeletedKeys() {
+    String key1 = "key1";
+    String key2 = "key2";
+    DeleteResultMetadata metadata = new DeleteResultMetadata();
+    metadata.addDeletedKey(key1);
+    metadata.addDeletedKey(key2);
+    assertTrue(CollectionUtils.isEqualCollection(Sets.newHashSet(key1, key2), metadata.getDeletedKeys()));
+    assertTrue(metadata.getExcptions().isEmpty());
+  }
+
+  @Test
+  public void testException() {
+    String key1 = "key1";
+    String key2 = "key2";
+    String key3 = "key3";
+    String key4 = "key4";
+    String key5 = "key5";
+    DeleteResultMetadata metadata = new DeleteResultMetadata();
+    metadata.addException(key1, new AuthorizationException("Exception"));
+    metadata.addException(key2, new SystemException("Exception"));
+    metadata.addException(key3, new HostNotFoundException("Exception"));
+    metadata.addException(key4, new UnsupportedPropertyException(Resource.Type.Action, Collections.<String>emptySet()));
+    metadata.addException(key5, new NullPointerException());
+
+    assertTrue(metadata.getDeletedKeys().isEmpty());
+    Map<String, ResultStatus> resultStatusMap =  metadata.getExcptions();
+    assertEquals(resultStatusMap.get(key1).getStatus(), ResultStatus.STATUS.FORBIDDEN);
+    assertEquals(resultStatusMap.get(key2).getStatus(), ResultStatus.STATUS.SERVER_ERROR);
+    assertEquals(resultStatusMap.get(key3).getStatus(), ResultStatus.STATUS.NOT_FOUND);
+    assertEquals(resultStatusMap.get(key4).getStatus(), ResultStatus.STATUS.BAD_REQUEST);
+    assertEquals(resultStatusMap.get(key5).getStatus(), ResultStatus.STATUS.SERVER_ERROR);
+  }
+}

+ 34 - 1
ambari-server/src/test/java/org/apache/ambari/server/api/services/serializers/JsonSerializerTest.java

@@ -18,11 +18,13 @@
 
 package org.apache.ambari.server.api.services.serializers;
 
+import org.apache.ambari.server.api.services.DeleteResultMetadata;
 import org.apache.ambari.server.api.services.Result;
 import org.apache.ambari.server.api.services.ResultImpl;
 import org.apache.ambari.server.api.services.ResultStatus;
 import org.apache.ambari.server.api.util.TreeNode;
 import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.security.authorization.AuthorizationException;
 import org.junit.Test;
 
 import javax.ws.rs.core.UriInfo;
@@ -37,6 +39,7 @@ import static org.junit.Assert.assertEquals;
  * JSONSerializer unit tests
  */
 public class JsonSerializerTest {
+
   @Test
   public void testSerialize() throws Exception {
     UriInfo uriInfo = createMock(UriInfo.class);
@@ -207,6 +210,36 @@ public class JsonSerializerTest {
 
     verify(uriInfo, resource/*, resource2*/);
   }
-    
+
+  @Test
+  public void testDeleteResultMetadata() throws Exception {
+    Result result = new ResultImpl(true);
+    result.setResultStatus(new ResultStatus(ResultStatus.STATUS.OK));
+    DeleteResultMetadata metadata = new DeleteResultMetadata();
+    metadata.addDeletedKey("key1");
+    metadata.addException("key2", new AuthorizationException());
+    result.setResultMetadata(metadata);
+
+    String expected =
+        "{\n" +
+        "  \"deleteResult\" : [\n"+
+        "    {\n" +
+        "      \"deleted\" : {\n" +
+        "        \"key\" : \"key1\"\n" +
+        "      }\n" +
+        "    },\n" +
+        "    {\n" +
+        "      \"error\" : {\n" +
+        "        \"key\" : \"key2\",\n" +
+        "        \"code\" : 403,\n" +
+        "        \"message\" : \"org.apache.ambari.server.security.authorization.AuthorizationException:"+
+                              " The authenticated user is not authorized to perform the requested operation\"\n" +
+        "      }\n" +
+        "    }\n" +
+        "  ]\n" +
+        "}";
+    String  json = new JsonSerializer().serialize(result).toString().replace("\r", "");
+    assertEquals(expected, json);
+  }
   
 }

+ 50 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/DeleteStatusMetaDataTest.java

@@ -0,0 +1,50 @@
+/**
+ * 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.controller.internal;
+
+import com.google.common.collect.Sets;
+import org.apache.ambari.server.ObjectNotFoundException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.junit.Test;
+
+import static org.apache.commons.collections.CollectionUtils.isEqualCollection;
+import static org.junit.Assert.assertTrue;
+
+public class DeleteStatusMetaDataTest {
+  private String key1 = "key1";
+  private String key2 = "key2";
+
+  @Test
+  public void testDeletedKeys() {
+    DeleteStatusMetaData metaData = new DeleteStatusMetaData();
+    metaData.addDeletedKey(key1);
+    metaData.addDeletedKey(key2);
+    assertTrue(isEqualCollection(Sets.newHashSet(key1, key2), metaData.getDeletedKeys()));
+    assertTrue(metaData.getExceptionForKeys().isEmpty());
+  }
+
+  @Test
+  public void testExceptions() {
+    DeleteStatusMetaData metaData = new DeleteStatusMetaData();
+    metaData.addException(key1, new SystemException("test"));
+    metaData.addException(key2, new ObjectNotFoundException("test"));
+    assertTrue(metaData.getExceptionForKeys().get(key1) instanceof SystemException);
+    assertTrue(metaData.getExceptionForKeys().get(key2) instanceof ObjectNotFoundException);
+  }
+}

+ 12 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestStatusImplTest.java

@@ -19,6 +19,7 @@
 package org.apache.ambari.server.controller.internal;
 
 import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.RequestStatusMetaData;
 import org.apache.ambari.server.controller.spi.Resource;
 import org.junit.Assert;
 import org.junit.Test;
@@ -30,6 +31,7 @@ import java.util.Set;
  * RequestStatusImpl Tests
  */
 public class RequestStatusImplTest {
+
   @Test
   public void testGetAssociatedResources() throws Exception {
     RequestStatusImpl status = new RequestStatusImpl(null);
@@ -63,4 +65,14 @@ public class RequestStatusImplTest {
     status = new RequestStatusImpl(requestResource);
     Assert.assertEquals(RequestStatus.Status.InProgress, status.getStatus());
   }
+
+  @Test
+  public void testGetRequestStatusMetadata() throws Exception {
+    RequestStatusImpl status = new RequestStatusImpl(null);
+    Assert.assertNull(status.getStatusMetadata());
+    RequestStatusMetaData metaData = new RequestStatusMetaData() {};
+
+    status = new RequestStatusImpl(null, null, metaData);
+    Assert.assertEquals(metaData, status.getStatusMetadata());
+  }
 }