Forráskód Böngészése

AMBARI 3608. Custom Action: Add support for Custom Action Definition

Sumit Mohanty 11 éve
szülő
commit
8b0e64c512
42 módosított fájl, 2724 hozzáadás és 675 törlés
  1. 138 0
      ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDefinition.java
  2. 43 7
      ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionManager.java
  3. 30 0
      ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionType.java
  4. 82 0
      ambari-server/src/main/java/org/apache/ambari/server/actionmanager/CustomActionDBAccessor.java
  5. 222 0
      ambari-server/src/main/java/org/apache/ambari/server/actionmanager/CustomActionDBAccessorImpl.java
  6. 28 0
      ambari-server/src/main/java/org/apache/ambari/server/actionmanager/TargetHostType.java
  7. 73 67
      ambari-server/src/main/java/org/apache/ambari/server/api/services/ActionService.java
  8. 17 0
      ambari-server/src/main/java/org/apache/ambari/server/api/services/RequestService.java
  9. 0 11
      ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java
  10. 92 28
      ambari-server/src/main/java/org/apache/ambari/server/controller/ActionRequest.java
  11. 150 15
      ambari-server/src/main/java/org/apache/ambari/server/controller/ActionResponse.java
  12. 2 14
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
  13. 57 62
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
  14. 3 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java
  15. 89 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/ExecuteActionRequest.java
  16. 250 70
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ActionResourceProvider.java
  17. 2 2
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestImpl.java
  18. 109 36
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestResourceProvider.java
  19. 2 2
      ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
  20. 71 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ActionDefinitionDAO.java
  21. 6 6
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ExecutionCommandDAO.java
  22. 154 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ActionEntity.java
  23. 3 0
      ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
  24. 2 0
      ambari-server/src/main/resources/META-INF/persistence.xml
  25. 103 105
      ambari-server/src/main/resources/key_properties.json
  26. 7 2
      ambari-server/src/main/resources/properties.json
  27. 1 1
      ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestActionDBAccessorImpl.java
  28. 3 3
      ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestActionManager.java
  29. 3 3
      ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestActionScheduler.java
  30. 124 0
      ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestCustomActionDBAccessorImpl.java
  31. 2 2
      ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java
  32. 41 33
      ambari-server/src/test/java/org/apache/ambari/server/api/services/ActionServiceTest.java
  33. 53 0
      ambari-server/src/test/java/org/apache/ambari/server/controller/ActionRequestTest.java
  34. 46 0
      ambari-server/src/test/java/org/apache/ambari/server/controller/ActionResponseTest.java
  35. 220 35
      ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
  36. 0 45
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AbstractResourceProviderTest.java
  37. 210 111
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ActionResourceProviderTest.java
  38. 7 2
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestImplTest.java
  39. 134 8
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestResourceProviderTest.java
  40. 137 0
      ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ActionDefinitionDAOTest.java
  41. 4 3
      ambari-web/app/controllers/main/host/details.js
  42. 4 2
      ambari-web/app/utils/ajax.js

+ 138 - 0
ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionDefinition.java

@@ -0,0 +1,138 @@
+/**
+ * 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.actionmanager;
+
+import org.apache.ambari.server.orm.entities.ActionEntity;
+
+/**
+ * The resource describing the definition of an action
+ */
+public class ActionDefinition {
+  private String actionName;
+  private ActionType actionType;
+  private String inputs;
+  private String targetService;
+  private String targetComponent;
+  private String description;
+  private TargetHostType targetType;
+  private Short defaultTimeout;
+
+  /**
+   * Create an instance of ActionDefinition
+   * @param actionName      The name of the action
+   * @param actionType      The type fo the action
+   * @param inputs          Expected input of the action
+   * @param targetService   Target service type (e.g. HDFS)
+   * @param targetComponent Target component type (e.g. DATANODE)
+   * @param description     Short description of the action
+   * @param targetType      Selection criteria for target hosts
+   * @param defaultTimeout  The timeout value for this action when executed
+   */
+  public ActionDefinition(String actionName, ActionType actionType, String inputs,
+                          String targetService, String targetComponent, String description,
+                          TargetHostType targetType, Short defaultTimeout) {
+    setActionName(actionName);
+    setActionType(actionType);
+    setInputs(inputs);
+    setTargetService(targetService);
+    setTargetComponent(targetComponent);
+    setDescription(description);
+    setTargetType(targetType);
+    setDefaultTimeout(defaultTimeout);
+  }
+
+  /**
+   * Create an instance of ActionDefinition
+   * @param entity  The entity corresponding to the action
+   */
+  public ActionDefinition(ActionEntity entity) {
+    setActionName(entity.getActionName());
+    setActionType(entity.getActionType());
+    setInputs(entity.getInputs());
+    setTargetService(entity.getTargetService());
+    setTargetComponent(entity.getTargetComponent());
+    setDescription(entity.getDescription());
+    setTargetType(entity.getTargetType());
+    setDefaultTimeout(entity.getDefaultTimeout());
+  }
+
+  public String getActionName() {
+    return actionName;
+  }
+
+  public void setActionName(String actionName) {
+    this.actionName = actionName;
+  }
+
+  public ActionType getActionType() {
+    return actionType;
+  }
+
+  public void setActionType(ActionType actionType) {
+    this.actionType = actionType;
+  }
+
+  public String getInputs() {
+    return inputs;
+  }
+
+  public void setInputs(String inputs) {
+    this.inputs = inputs;
+  }
+
+  public String getTargetService() {
+    return targetService;
+  }
+
+  public void setTargetService(String targetService) {
+    this.targetService = targetService;
+  }
+
+  public String getTargetComponent() {
+    return targetComponent;
+  }
+
+  public void setTargetComponent(String targetComponent) {
+    this.targetComponent = targetComponent;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public TargetHostType getTargetType() {
+    return targetType;
+  }
+
+  public void setTargetType(TargetHostType targetType) {
+    this.targetType = targetType;
+  }
+
+  public Short getDefaultTimeout() {
+    return defaultTimeout;
+  }
+
+  public void setDefaultTimeout(Short defaultTimeout) {
+    this.defaultTimeout = defaultTimeout;
+  }
+}

+ 43 - 7
ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionManager.java

@@ -21,6 +21,7 @@ import com.google.inject.Inject;
 import com.google.inject.Singleton;
 import com.google.inject.name.Named;
 import com.google.inject.persist.UnitOfWork;
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.agent.ActionQueue;
 import org.apache.ambari.server.agent.CommandReport;
 import org.apache.ambari.server.controller.HostsMap;
@@ -42,23 +43,25 @@ import java.util.concurrent.atomic.AtomicLong;
  */
 @Singleton
 public class ActionManager {
+  private static Logger LOG = LoggerFactory.getLogger(ActionManager.class);
   private final ActionScheduler scheduler;
   private final ActionDBAccessor db;
   private final ActionQueue actionQueue;
-  private static Logger LOG = LoggerFactory.getLogger(ActionManager.class);
   private final AtomicLong requestCounter;
+  private final CustomActionDBAccessor cdb;
 
   @Inject
   public ActionManager(@Named("schedulerSleeptime") long schedulerSleepTime,
-      @Named("actionTimeout") long actionTimeout,
-      ActionQueue aq, Clusters fsm, ActionDBAccessor db, HostsMap hostsMap,
-      ServerActionManager serverActionManager, UnitOfWork unitOfWork) {
+                       @Named("actionTimeout") long actionTimeout,
+                       ActionQueue aq, Clusters fsm, ActionDBAccessor db, HostsMap hostsMap,
+                       ServerActionManager serverActionManager, UnitOfWork unitOfWork, CustomActionDBAccessor cdb) {
     this.actionQueue = aq;
     this.db = db;
     scheduler = new ActionScheduler(schedulerSleepTime, actionTimeout, db,
         actionQueue, fsm, 2, hostsMap, serverActionManager, unitOfWork);
     requestCounter = new AtomicLong(
         db.getLastPersistedRequestIdWhenInitialized());
+    this.cdb = cdb;
   }
 
   public void start() {
@@ -94,8 +97,8 @@ public class ActionManager {
   /**
    * Get all actions(stages) for a request.
    *
-   * @param requestId  the request id
-   * @return  list of all stages associated with the given request id
+   * @param requestId the request id
+   * @return list of all stages associated with the given request id
    */
   public List<Stage> getActions(long requestId) {
     return db.getAllStages(requestId);
@@ -114,7 +117,7 @@ public class ActionManager {
         LOG.debug("Processing command report : " + report.toString());
       }
       String actionId = report.getActionId();
-      long [] requestStageIds = StageUtils.getRequestStage(actionId);
+      long[] requestStageIds = StageUtils.getRequestStage(actionId);
       long requestId = requestStageIds[0];
       long stageId = requestStageIds[1];
       HostRoleCommand command = db.getTask(report.getTaskId());
@@ -167,6 +170,7 @@ public class ActionManager {
 
   /**
    * Returns last 20 requests
+   *
    * @return
    */
   public List<Long> getRequests() {
@@ -175,6 +179,7 @@ public class ActionManager {
 
   /**
    * Returns last 20 requests
+   *
    * @return
    */
   public List<Long> getRequestsByStatus(RequestStatus status) {
@@ -188,4 +193,35 @@ public class ActionManager {
   public String getRequestContext(long requestId) {
     return db.getRequestContext(requestId);
   }
+
+  /** CRUD operations of Action resources **/
+
+  public ActionDefinition getActionDefinition(String actionName)
+      throws AmbariException {
+    return cdb.getActionDefinition(actionName);
+  }
+
+  public List<ActionDefinition> getAllActionDefinition()
+      throws AmbariException {
+    return cdb.getActionDefinitions();
+  }
+
+  public void deleteActionDefinition(String actionName)
+      throws AmbariException {
+    cdb.deleteActionDefinition(actionName);
+  }
+
+  public void updateActionDefinition(String actionName, ActionType actionType, String description,
+                                     TargetHostType targetType, Short defaultTimeout)
+      throws AmbariException {
+    cdb.updateActionDefinition(actionName, actionType, description, targetType, defaultTimeout);
+  }
+
+  public void createActionDefinition(String actionName, ActionType actionType, String inputs, String description,
+                                     String serviceType, String componentType, TargetHostType targetType,
+                                     Short defaultTimeout)
+      throws AmbariException {
+    cdb.createActionDefinition(actionName, actionType, inputs, description, targetType, serviceType,
+        componentType, defaultTimeout);
+  }
 }

+ 30 - 0
ambari-server/src/main/java/org/apache/ambari/server/actionmanager/ActionType.java

@@ -0,0 +1,30 @@
+/**
+ * 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.actionmanager;
+
+/**
+ * Defines various action types
+ */
+public enum ActionType {
+  DISABLED, // Action cannot be executed
+  SYSTEM, // System defined
+  USER, // User defined
+  SYSTEM_REQUIRES_ADMIN, // Requires admin privileges
+  USER_REQUIRES_ADMIN, // Requires admin privileges
+  SYSTEM_DISABLED; // Ambari has disabled the action
+}

+ 82 - 0
ambari-server/src/main/java/org/apache/ambari/server/actionmanager/CustomActionDBAccessor.java

@@ -0,0 +1,82 @@
+/**
+ * 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.actionmanager;
+
+import org.apache.ambari.server.AmbariException;
+
+import java.util.List;
+
+/**
+ * The DB accessor implementation for Action definitions
+ */
+public interface CustomActionDBAccessor {
+
+  /**
+   * Given an actionName, get the Action resource
+   *
+   * @param actionName name of the action
+   * @return
+   * @throws AmbariException
+   */
+  public ActionDefinition getActionDefinition(String actionName) throws AmbariException;
+
+  /**
+   * Get all action definition resources
+   *
+   * @return
+   */
+  public List<ActionDefinition> getActionDefinitions();
+
+  /**
+   * Create an action definition resource
+   *
+   * @param actionName     name of the action
+   * @param actionType     type of the action
+   * @param inputs         inputs required by the action
+   * @param description    a short description of the action
+   * @param targetType     the host target type
+   * @param serviceType    the service type on which the action must be executed
+   * @param componentType  the component type on which the action must be executed
+   * @param defaultTimeout the default timeout for this action
+   * @throws AmbariException
+   */
+  public void createActionDefinition(String actionName, ActionType actionType, String inputs, String description,
+                                     TargetHostType targetType, String serviceType, String componentType,
+                                     Short defaultTimeout) throws AmbariException;
+
+  /**
+   * Update an action definition
+   *
+   * @param actionName     name of the action
+   * @param actionType     type of the action
+   * @param description    a short description of the action
+   * @param targetType     the host target type
+   * @param defaultTimeout the default timeout for this action
+   * @throws AmbariException
+   */
+  public void updateActionDefinition(String actionName, ActionType actionType, String description,
+                                     TargetHostType targetType, Short defaultTimeout) throws AmbariException;
+
+  /**
+   * Delete an action definition
+   *
+   * @param actionName
+   * @throws AmbariException
+   */
+  public void deleteActionDefinition(String actionName) throws AmbariException;
+}

+ 222 - 0
ambari-server/src/main/java/org/apache/ambari/server/actionmanager/CustomActionDBAccessorImpl.java

@@ -0,0 +1,222 @@
+/**
+ * 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.actionmanager;
+
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.Singleton;
+import com.google.inject.persist.Transactional;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.orm.dao.ActionDefinitionDAO;
+import org.apache.ambari.server.orm.entities.ActionEntity;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An implementation of DB accessor for Custom Action
+ */
+@Singleton
+public class CustomActionDBAccessorImpl implements CustomActionDBAccessor {
+  public static final Short MIN_TIMEOUT = 60;
+  private static final Logger LOG = LoggerFactory.getLogger(CustomActionDBAccessorImpl.class);
+  private static final Short MAX_TIMEOUT = 600;
+  @Inject
+  private ActionDefinitionDAO actionDefinitionDAO;
+
+  @Inject
+  public CustomActionDBAccessorImpl(Injector injector) {
+    injector.injectMembers(this);
+  }
+
+  /**
+   * Given an actionName, get the Action resource
+   *
+   * @param actionName name of the action
+   * @return
+   * @throws AmbariException
+   */
+  @Override
+  public ActionDefinition getActionDefinition(String actionName) {
+    ActionEntity action =
+        actionDefinitionDAO.findByPK(actionName);
+    if (action != null) {
+      return new ActionDefinition(action);
+    }
+
+    return null;
+  }
+
+  /**
+   * Get all action definition resources
+   *
+   * @return
+   */
+  @Override
+  public List<ActionDefinition> getActionDefinitions() {
+    List<ActionDefinition> result = new ArrayList<ActionDefinition>();
+    List<ActionEntity> entities = actionDefinitionDAO.findAll();
+    for (ActionEntity entity : entities) {
+      result.add(new ActionDefinition(entity));
+    }
+    return result;
+  }
+
+  /**
+   * Create an action definition resource
+   *
+   * @param actionName     name of the action
+   * @param actionType     type of the action
+   * @param inputs         inputs required by the action
+   * @param description    a short description of the action
+   * @param targetType     the host target type
+   * @param serviceType    the service type on which the action must be executed
+   * @param componentType  the component type on which the action must be executed
+   * @param defaultTimeout the default timeout for this action
+   * @throws AmbariException
+   */
+  @Override
+  @Transactional
+  public void createActionDefinition(String actionName, ActionType actionType, String inputs, String description,
+                                     TargetHostType targetType, String serviceType, String componentType,
+                                     Short defaultTimeout)
+      throws AmbariException {
+    validateCreateInput(actionName, actionType, inputs, description, defaultTimeout);
+    ActionEntity entity =
+        actionDefinitionDAO.findByPK(actionName);
+    if (entity == null) {
+      entity = new ActionEntity();
+      entity.setActionName(actionName);
+      entity.setActionType(actionType);
+      entity.setInputs(inputs);
+      entity.setTargetService(serviceType);
+      entity.setTargetComponent(componentType);
+      entity.setDescription(description);
+      entity.setTargetType(targetType);
+      entity.setDefaultTimeout(defaultTimeout);
+      actionDefinitionDAO.merge(entity);
+    } else {
+      throw new AmbariException("Action definition " + actionName + " already exists");
+    }
+  }
+
+  /**
+   * Update an action definition
+   *
+   * @param actionName     name of the action
+   * @param actionType     type of the action
+   * @param description    a short description of the action
+   * @param targetType     the host target type
+   * @param defaultTimeout the default timeout for this action
+   * @throws AmbariException
+   */
+  @Override
+  @Transactional
+  public void updateActionDefinition(String actionName, ActionType actionType, String description,
+                                     TargetHostType targetType, Short defaultTimeout)
+      throws AmbariException {
+    ActionEntity entity = actionDefinitionDAO.findByPK(actionName);
+    if (entity != null) {
+      if (actionType != null) {
+        if (actionType == ActionType.SYSTEM_DISABLED) {
+          throw new AmbariException("Action type cannot be " + actionType);
+        }
+        entity.setActionType(actionType);
+      }
+      if (description != null) {
+        if (description.isEmpty()) {
+          throw new AmbariException("Action description cannot be empty");
+        }
+        entity.setDescription(description);
+      }
+      if (targetType != null) {
+        entity.setTargetType(targetType);
+      }
+      if (defaultTimeout != null) {
+        if (defaultTimeout < MIN_TIMEOUT || defaultTimeout > MAX_TIMEOUT) {
+          throw new AmbariException("Default timeout should be between " + MIN_TIMEOUT + " and " + MAX_TIMEOUT);
+        }
+        entity.setDefaultTimeout(defaultTimeout);
+      }
+      actionDefinitionDAO.merge(entity);
+    } else {
+      throw new AmbariException("Action definition " + actionName + " does not exist");
+    }
+  }
+
+  /**
+   * Delete an action definition
+   *
+   * @param actionName
+   * @throws AmbariException
+   */
+  @Override
+  public void deleteActionDefinition(String actionName)
+      throws AmbariException {
+    validateActionName(actionName);
+    ActionDefinition ad = getActionDefinition(actionName);
+    if (ad != null) {
+      actionDefinitionDAO.removeByPK(actionName);
+    }
+  }
+
+  private void validateCreateInput(String actionName, ActionType actionType, String inputs,
+                                   String description, Short defaultTimeout)
+      throws AmbariException {
+
+    validateActionName(actionName);
+
+    if (defaultTimeout < MIN_TIMEOUT || defaultTimeout > MAX_TIMEOUT) {
+      throw new AmbariException("Default timeout should be between " + MIN_TIMEOUT + " and " + MAX_TIMEOUT);
+    }
+
+    if (actionType == ActionType.SYSTEM_DISABLED) {
+      throw new AmbariException("Action type cannot be " + actionType);
+    }
+
+    if (description == null || description.isEmpty()) {
+      throw new AmbariException("Action description cannot be empty");
+    }
+
+    if (actionType == null || actionType == ActionType.SYSTEM_DISABLED) {
+      throw new AmbariException("Action type cannot be " + actionType);
+    }
+
+    if (inputs != null && !inputs.isEmpty()) {
+      String[] parameters = inputs.split(",");
+      for (String parameter : parameters) {
+        if (parameter.trim().isEmpty()) {
+          throw new AmbariException("Empty parameter cannot be specified as an input parameter");
+        }
+      }
+    }
+  }
+
+  private void validateActionName(String actionName)
+      throws AmbariException {
+    if (actionName == null || actionName.isEmpty()) {
+      throw new AmbariException("Action name cannot be empty");
+    }
+    String trimmedName = actionName.replaceAll("\\s+", "");
+    if (actionName.length() > trimmedName.length()) {
+      throw new AmbariException("Action name cannot contain white spaces");
+    }
+  }
+}

+ 28 - 0
ambari-server/src/main/java/org/apache/ambari/server/actionmanager/TargetHostType.java

@@ -0,0 +1,28 @@
+/**
+ * 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.actionmanager;
+
+/**
+ * An enum describing how to select a host from a group of hosts eligible for action execution
+ */
+public enum TargetHostType {
+  ANY, // Any host
+  ALL, // On all hosts
+  MAJORITY, // On majority of the hosts
+  SPECIFIC; // On specific hosts specified during invocation
+}

+ 73 - 67
ambari-server/src/main/java/org/apache/ambari/server/api/services/ActionService.java

@@ -15,10 +15,16 @@
  * 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.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+import javax.ws.rs.DELETE;
 import javax.ws.rs.GET;
 import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -26,109 +32,109 @@ import javax.ws.rs.core.Context;
 import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.UriInfo;
+import java.util.Collections;
 
-import org.apache.ambari.server.api.resources.ResourceInstance;
-import org.apache.ambari.server.controller.spi.Resource;
-
-import java.util.HashMap;
-import java.util.Map;
 
+/**
+ * Service responsible for action definition resource requests.
+ */
+@Path("/actions/")
 public class ActionService extends BaseService {
-  /**
-   * Parent cluster name.
-   */
-  private String m_clusterName;
-  
-  private String m_serviceName;
 
   /**
-   * Constructor.
+   * Handles: GET /actions/{actionName}
+   * Get a specific action definition.
    *
-   * @param clusterName cluster id
-   * @param serviceName service
+   * @param headers     http headers
+   * @param ui          uri info
+   * @param actionName action name
+   * @return action definition instance representation
    */
-  public ActionService(String clusterName, String serviceName) {
-    m_clusterName = clusterName;
-    m_serviceName = serviceName;
+  @GET
+  @Path("{actionName}")
+  @Produces("text/plain")
+  public Response getActionDefinition(@Context HttpHeaders headers, @Context UriInfo ui,
+                             @PathParam("actionName") String actionName) {
+
+    return handleRequest(headers, null, ui, Request.Type.GET, createActionDefinitionResource(actionName));
   }
 
   /**
-   * Handles URL: /clusters/{clusterId}/services/{serviceName}/actions
-   * Get all actions for a service in a cluster.
+   * Handles: GET  /actions
+   * Get all action definitions.
    *
    * @param headers http headers
    * @param ui      uri info
-   * @return service collection resource representation
+   * @return action definition collection resource representation
    */
   @GET
   @Produces("text/plain")
-  public Response getActions(@Context HttpHeaders headers, @Context UriInfo ui) {
-    return handleRequest(headers, null, ui, Request.Type.GET,
-        createActionResource(m_clusterName, m_serviceName, null));
+  public Response getActionDefinitions(@Context HttpHeaders headers, @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET, createActionDefinitionResource(null));
   }
 
   /**
-   * Handles URL: /clusters/{clusterId}/services/{serviceName}/actions.  
-   * The body should contain:
-   * <pre>
-   * {
-   *     "actionName":"name_string",
-   *     "parameters":
-   *     {
-   *         "key1":"value1",
-   *         // ...
-   *         "keyN":"valueN"
-   *     }
-   * }
-   * </pre>
-   * Get all services for a cluster.
+   * Handles: POST /actions/{actionName}
+   * Create a specific action definition.
    *
-   * @param headers http headers
-   * @param ui      uri info
-   * @return service collection resource representation
+   * @param headers     http headers
+   * @param ui          uri info
+   * @param actionName  action name
+   * @return information regarding the action definition being created
    */
-  @POST
-  @Produces("text/plain")
-  public Response createActions(String body,@Context HttpHeaders headers, @Context UriInfo ui) {
-    return handleRequest(headers, body, ui, Request.Type.POST,
-        createActionResource(m_clusterName, m_serviceName, null));
+   @POST
+   @Path("{actionName}")
+   @Produces("text/plain")
+   public Response createActionDefinition(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                 @PathParam("actionName") String actionName) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST, createActionDefinitionResource(actionName));
   }
-  
+
   /**
-   * Handles: POST /clusters/{clusterId}/services/{serviceId}/{actionName}
-   * Create a specific action.
+   * Handles: PUT /actions/{actionName}
+   * Update a specific action definition.
    *
-   * @param body        http body
    * @param headers     http headers
    * @param ui          uri info
    * @param actionName  action name
-   *
-   * @return information regarding the created action
+   * @return information regarding the updated action
    */
-  @POST
+  @PUT
   @Path("{actionName}")
   @Produces("text/plain")
-  public Response createAction(String body, @Context HttpHeaders headers, @Context UriInfo ui,
-                               @PathParam("actionName") String actionName) {
-    return handleRequest(headers, body, ui, Request.Type.POST,
-        createActionResource(m_clusterName, m_serviceName, actionName));
+  public Response updateActionDefinition(String body, @Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("actionName") String actionName) {
+
+    return handleRequest(headers, body, ui, Request.Type.PUT, createActionDefinitionResource(actionName));
   }
 
   /**
-   * Create an action resource instance.
+   * Handles: DELETE /actions/{actionName}
+   * Delete a specific action definition.
    *
-   * @param clusterName cluster name
-   * @param serviceName service name
+   * @param headers     http headers
+   * @param ui          uri info
    * @param actionName  action name
-   *
-   * @return an action resource instance
+   * @return information regarding the deleted action definition
    */
-  ResourceInstance createActionResource(String clusterName, String serviceName, String actionName) {
-    Map<Resource.Type,String> mapIds = new HashMap<Resource.Type, String>();
-    mapIds.put(Resource.Type.Cluster, clusterName);
-    mapIds.put(Resource.Type.Service, serviceName);
-    mapIds.put(Resource.Type.Action, actionName);
+  @DELETE
+  @Path("{actionName}")
+  @Produces("text/plain")
+  public Response deleteActionDefinition(@Context HttpHeaders headers, @Context UriInfo ui,
+                                @PathParam("actionName") String actionName) {
+    return handleRequest(headers, null, ui, Request.Type.DELETE, createActionDefinitionResource(actionName));
+  }
 
-    return createResource(Resource.Type.Action, mapIds);
+  /**
+   * Create a action definition resource instance.
+   *
+   * @param actionName action name
+   *
+   * @return a action definition resource instance
+   */
+  ResourceInstance createActionDefinitionResource(String actionName) {
+    return createResource(Resource.Type.Action,
+        Collections.singletonMap(Resource.Type.Action, actionName));
   }
 }

+ 17 - 0
ambari-server/src/main/java/org/apache/ambari/server/api/services/RequestService.java

@@ -23,6 +23,7 @@ import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.controller.spi.Resource;
 
 import javax.ws.rs.GET;
+import javax.ws.rs.POST;
 import javax.ws.rs.Path;
 import javax.ws.rs.PathParam;
 import javax.ws.rs.Produces;
@@ -97,6 +98,22 @@ public class RequestService extends BaseService {
     return new TaskService(m_clusterName, requestId);
   }
 
+  /**
+   * Handles: POST /clusters/{clusterId}/requests
+   * Create multiple services.
+   *
+   * @param body        http body
+   * @param headers     http headers
+   * @param ui          uri info
+   * @return information regarding the created services
+   */
+  @POST
+  @Produces("text/plain")
+  public Response createRequests(String body, @Context HttpHeaders headers, @Context UriInfo ui) {
+
+    return handleRequest(headers, body, ui, Request.Type.POST, createRequestResource(m_clusterName, null));
+  }
+
   /**
    * Create a request resource instance.
    *

+ 0 - 11
ambari-server/src/main/java/org/apache/ambari/server/api/services/ServiceService.java

@@ -179,17 +179,6 @@ public class ServiceService extends BaseService {
 
     return new ComponentService(m_clusterName, serviceName);
   }
-  
-  /**
-   * Get the components sub-resource.
-   *
-   * @param serviceName service id
-   * @return the action service
-   */
-  @Path("{serviceName}/actions")
-  public ActionService getActionHandler(@PathParam("serviceName") String serviceName) {
-    return new ActionService(m_clusterName, serviceName);
-  }
 
   /**
    * Create a service resource instance.

+ 92 - 28
ambari-server/src/main/java/org/apache/ambari/server/controller/ActionRequest.java

@@ -15,56 +15,120 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.ambari.server.controller;
 
-import java.util.Map;
+package org.apache.ambari.server.controller;
 
+/**
+ * Used to perform CRUD operations of Action
+ */
 public class ActionRequest {
-  private String clusterName; 
 
-  private String serviceName;
-  
-  private String actionName; //for CREATE only
+  private String actionName;  //CRUD
+  private String actionType;  //C
+  private String inputs;  //C
+  private String targetService;  //C
+  private String targetComponent;  //C
+  private String description;  //CU
+  private String targetType;  //CU
+  private String defaultTimeout;  //CU
 
-  private Map<String, String> parameters; //for CREATE only
+  public ActionRequest(
+      String actionName, String actionType, String inputs,
+      String targetService, String targetComponent, String description, String targetType,
+      String defaultTimeout) {
+    setActionName(actionName);
+    setActionType(actionType);
+    setInputs(inputs);
+    setTargetService(targetService);
+    setTargetComponent(targetComponent);
+    setDescription(description);
+    setTargetType(targetType);
+    setDefaultTimeout(defaultTimeout);
+  }
+
+  /**
+   * Create the request to get all defined actions
+   *
+   * @return
+   */
+  public static ActionRequest getAllRequest() {
+    return new ActionRequest(null, null, null, null, null, null, null, null);
+  }
 
-  public ActionRequest(String clusterName, String serviceName,
-      String actionName, Map<String, String> params) {
-    this.clusterName = clusterName;
-    this.serviceName = serviceName;
+  public String getActionName() {
+    return actionName;
+  }
+
+  public void setActionName(String actionName) {
     this.actionName = actionName;
-    this.parameters = params;
   }
 
-  public String getClusterName() {
-    return clusterName;
+  public String getActionType() {
+    return actionType;
   }
 
-  public void setClusterName(String clusterName) {
-    this.clusterName = clusterName;
+  public void setActionType(String actionType) {
+    this.actionType = actionType;
   }
 
-  public String getServiceName() {
-    return serviceName;
+  public String getInputs() {
+    return inputs;
   }
 
-  public void setServiceName(String serviceName) {
-    this.serviceName = serviceName;
+  public void setInputs(String inputs) {
+    this.inputs = inputs;
   }
 
-  public String getActionName() {
-    return actionName;
+  public String getTargetService() {
+    return targetService;
   }
 
-  public void setActionName(String actionName) {
-    this.actionName = actionName;
+  public void setTargetService(String targetService) {
+    this.targetService = targetService;
+  }
+
+  public String getTargetComponent() {
+    return targetComponent;
+  }
+
+  public void setTargetComponent(String targetComponent) {
+    this.targetComponent = targetComponent;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getTargetType() {
+    return targetType;
+  }
+
+  public void setTargetType(String targetType) {
+    this.targetType = targetType;
+  }
+
+  public String getDefaultTimeout() {
+    return defaultTimeout;
   }
 
-  public Map<String, String> getParameters() {
-    return parameters;
+  public void setDefaultTimeout(String defaultTimeout) {
+    this.defaultTimeout = defaultTimeout;
   }
 
-  public void setParameters(Map<String, String> parameters) {
-    this.parameters = parameters;
+  @Override
+  public String toString() {
+    return (new StringBuilder()).
+        append("actionName :" + actionName).
+        append(", actionType :" + actionType).
+        append(", inputs :" + inputs).
+        append(", targetService :" + targetService).
+        append(", targetComponent :" + targetComponent).
+        append(", description :" + description).
+        append(", targetType :" + targetType).
+        append(", defaultTimeout :" + defaultTimeout).toString();
   }
 }

+ 150 - 15
ambari-server/src/main/java/org/apache/ambari/server/controller/ActionResponse.java

@@ -15,30 +15,45 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package org.apache.ambari.server.controller;
 
+import org.apache.ambari.server.actionmanager.ActionDefinition;
 
+/**
+ * Used to respond to GET requests for actions
+ */
 public class ActionResponse {
-  private String clusterName; 
-
-  private String serviceName;
   
   private String actionName;
+  private String actionType;
+  private String inputs;
+  private String targetService;
+  private String targetComponent;
+  private String description;
+  private String targetType;
+  private String defaultTimeout;
 
-  public String getClusterName() {
-    return clusterName;
+  public ActionResponse(String actionName, String actionType, String inputs,
+      String targetService, String targetComponent, String description, String targetType,
+      String defaultTimeout) {
+    setActionName(actionName);
+    setActionType(actionType);
+    setInputs(inputs);
+    setTargetService(targetService);
+    setTargetComponent(targetComponent);
+    setDescription(description);
+    setTargetType(targetType);
+    setDefaultTimeout(defaultTimeout);
   }
 
-  public void setClusterName(String clusterName) {
-    this.clusterName = clusterName;
-  }
-
-  public String getServiceName() {
-    return serviceName;
-  }
-
-  public void setServiceName(String serviceName) {
-    this.serviceName = serviceName;
+  public ActionResponse(ActionDefinition ad) {
+    this(ad.getActionName(), ad.getActionType().toString(), ad.getInputs(),
+        null == ad.getTargetService() ? "" : ad.getTargetService().toString(),
+        null == ad.getTargetComponent() ? "" : ad.getTargetComponent().toString(),
+        ad.getDescription(),
+        null == ad.getTargetType() ? "" : ad.getTargetType().toString(),
+        ad.getDefaultTimeout().toString());
   }
 
   public String getActionName() {
@@ -48,4 +63,124 @@ public class ActionResponse {
   public void setActionName(String actionName) {
     this.actionName = actionName;
   }
+
+  public String getActionType() {
+    return actionType;
+  }
+
+  public void setActionType(String actionType) {
+    this.actionType = actionType;
+  }
+
+  public String getInputs() {
+    return inputs;
+  }
+
+  public void setInputs(String inputs) {
+    this.inputs = inputs;
+  }
+
+  public String getTargetService() {
+    return targetService;
+  }
+
+  public void setTargetService(String targetService) {
+    this.targetService = targetService;
+  }
+
+  public String getTargetComponent() {
+    return targetComponent;
+  }
+
+  public void setTargetComponent(String targetComponent) {
+    this.targetComponent = targetComponent;
+  }
+  
+  public String getDescription() {
+    return description;
+  }
+  
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public String getTargetType() {
+    return targetType;
+  }
+
+  public void setTargetType(String targetType) {
+    this.targetType = targetType;
+  }
+
+  public String getDefaultTimeout() {
+    return defaultTimeout;
+  }
+
+  public void setDefaultTimeout(String defaultTimeout) {
+    this.defaultTimeout = defaultTimeout;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) return true;
+    if (o == null || getClass() != o.getClass()) return false;
+
+    ActionResponse that = (ActionResponse) o;
+
+    if (actionName != null ?
+        !actionName.equals(that.actionName) : that.actionName != null) {
+      return false;
+    }
+
+    if (actionType != null ?
+        !actionType.equals(that.actionType) : that.actionType != null) {
+      return false;
+    }
+
+    if (description != null ?
+        !description.equals(that.description) : that.description != null) {
+      return false;
+    }
+
+    if (inputs != null ?
+        !inputs.equals(that.inputs) : that.inputs != null) {
+      return false;
+    }
+
+    if (targetService != null ?
+        !targetService.equals(that.targetService) : that.targetService != null) {
+      return false;
+    }
+
+    if (targetComponent != null ?
+        !targetComponent.equals(that.targetComponent) : that.targetComponent != null) {
+      return false;
+    }
+
+    if (targetType != null ?
+        !targetType.equals(that.targetType) : that.targetType != null) {
+      return false;
+    }
+
+    if (defaultTimeout != null ?
+        !defaultTimeout.equals(that.defaultTimeout) : that.defaultTimeout != null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = 1;
+    result = 31 + (actionName != null ? actionName.hashCode() : 0);
+    result = result + (actionType != null ? actionType.hashCode() : 0);
+    result = result + (inputs != null ? inputs.hashCode() : 0);
+    result = result + (description != null ? description.hashCode() : 0);
+    result = result + (targetService != null ? targetService.hashCode() : 0);
+    result = result + (targetComponent != null ? targetComponent.hashCode() : 0);
+    result = result + (targetType != null ? targetType.hashCode() : 0);
+    result = result + (defaultTimeout != null ? defaultTimeout.hashCode() : 0);
+    return result;
+  }
 }

+ 2 - 14
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java

@@ -228,26 +228,14 @@ public interface AmbariManagementController {
   /**
    * Create the action defined by the attributes in the given request object.
    *
-   * @param request the request object which defines the action to be created
+   * @param actionRequest the request object which defines the action to be created
    * @param requestProperties the request properties
    *
    * @throws AmbariException thrown if the action cannot be created
    */
-  public RequestStatusResponse createActions(Set<ActionRequest> request, Map<String, String> requestProperties)
+  public RequestStatusResponse createAction(ExecuteActionRequest actionRequest, Map<String, String> requestProperties)
       throws AmbariException;
   
-  /**
-   * Get the actions identified by the given request objects.
-   *
-   * @param request  the request objects which identify the actions to be returned
-   *
-   * @return a set of actions responses
-   *
-   * @throws AmbariException thrown if the resource cannot be read
-   */
-  public Set<ActionResponse> getActions(Set<ActionRequest> request)
-      throws AmbariException;
-
   /**
    * Get supported stacks.
    * 

+ 57 - 62
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java

@@ -45,11 +45,15 @@ import org.apache.ambari.server.ServiceComponentHostNotFoundException;
 import org.apache.ambari.server.ServiceComponentNotFoundException;
 import org.apache.ambari.server.ServiceNotFoundException;
 import org.apache.ambari.server.StackAccessException;
+import org.apache.ambari.server.actionmanager.ActionDefinition;
 import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.actionmanager.ActionType;
+import org.apache.ambari.server.actionmanager.CustomActionDBAccessorImpl;
 import org.apache.ambari.server.actionmanager.ExecutionCommandWrapper;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
 import org.apache.ambari.server.actionmanager.Stage;
 import org.apache.ambari.server.actionmanager.StageFactory;
+import org.apache.ambari.server.actionmanager.TargetHostType;
 import org.apache.ambari.server.agent.ExecutionCommand;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.configuration.Configuration;
@@ -2063,32 +2067,6 @@ public class AmbariManagementControllerImpl implements
     }
   }
 
-  @Override
-  public Set<ActionResponse> getActions(Set<ActionRequest> request)
-      throws AmbariException {
-    Set<ActionResponse> responses = new HashSet<ActionResponse>();
-
-    for (ActionRequest actionRequest : request) {
-      if (actionRequest.getServiceName() == null) {
-        LOG.warn("No service name specified - skipping request");
-        //TODO throw error?
-        continue;
-      }
-      ActionResponse actionResponse = new ActionResponse();
-      actionResponse.setClusterName(actionRequest.getClusterName());
-      actionResponse.setServiceName(actionRequest.getServiceName());
-      if (actionMetadata.getActions(actionRequest.getServiceName()) != null
-          && !actionMetadata.getActions(actionRequest.getServiceName())
-              .isEmpty()) {
-        actionResponse.setActionName(actionMetadata.getActions(
-            actionRequest.getServiceName()).get(0));
-      }
-      responses.add(actionResponse);
-    }
-
-    return responses;
-  }
-
   /**
    * Get a request response for the given request ids.  Note that this method
    * fully populates a request resource including the set of task sub-resources
@@ -2309,7 +2287,7 @@ public class AmbariManagementControllerImpl implements
     return hostName;
   }
 
-  private void addServiceCheckAction(ActionRequest actionRequest, Stage stage)
+  private void addServiceCheckAction(ExecuteActionRequest actionRequest, Stage stage)
       throws AmbariException {
     String clusterName = actionRequest.getClusterName();
     String componentName = actionMetadata.getClient(actionRequest
@@ -2351,12 +2329,12 @@ public class AmbariManagementControllerImpl implements
     }
 
     stage.addHostRoleExecutionCommand(hostName, Role.valueOf(actionRequest
-        .getActionName()), RoleCommand.EXECUTE,
+        .getCommandName()), RoleCommand.EXECUTE,
         new ServiceComponentHostOpInProgressEvent(componentName, hostName,
             System.currentTimeMillis()), clusterName, actionRequest
             .getServiceName());
 
-    stage.getExecutionCommandWrapper(hostName, actionRequest.getActionName()).getExecutionCommand()
+    stage.getExecutionCommandWrapper(hostName, actionRequest.getCommandName()).getExecutionCommand()
         .setRoleParams(actionRequest.getParameters());
 
     Cluster cluster = clusters.getCluster(clusterName);
@@ -2367,7 +2345,7 @@ public class AmbariManagementControllerImpl implements
       cluster, actionRequest.getServiceName(), hostName);
 
     ExecutionCommand execCmd = stage.getExecutionCommandWrapper(hostName,
-      actionRequest.getActionName()).getExecutionCommand();
+      actionRequest.getCommandName()).getExecutionCommand();
 
     execCmd.setConfigurations(configurations);
     execCmd.setConfigurationTags(configTags);
@@ -2383,7 +2361,7 @@ public class AmbariManagementControllerImpl implements
   }
 
   private void addDecommissionDatanodeAction(
-      ActionRequest decommissionRequest, Stage stage)
+      ExecuteActionRequest decommissionRequest, Stage stage)
       throws AmbariException {
     String hdfsExcludeFileType = "hdfs-exclude-file";
     // Find hdfs admin host, just decommission from namenode.
@@ -2403,11 +2381,16 @@ public class AmbariManagementControllerImpl implements
 
     if (excludeFileTag == null) {
       throw new IllegalArgumentException("No exclude file specified"
-          + " when decommissioning datanodes");
+          + " when decommissioning datanodes. Provide parameter excludeFileTag with the tag for config type "
+          + hdfsExcludeFileType);
     }
 
     Config config = clusters.getCluster(clusterName).getConfig(
         hdfsExcludeFileType, excludeFileTag);
+    if(config == null){
+      throw new AmbariException("Decommissioning datanodes requires the cluster to be associated with config type " +
+      hdfsExcludeFileType + " with a list of datanodes to be decommissioned (\"datanodes\" : list).");
+    }
 
     LOG.info("Decommissioning data nodes: " + config.getProperties().get("datanodes") +
         " " + hdfsExcludeFileType + " tag: " + excludeFileTag);
@@ -2447,7 +2430,7 @@ public class AmbariManagementControllerImpl implements
   }
 
   @Override
-  public RequestStatusResponse createActions(Set<ActionRequest> request, Map<String, String> requestProperties)
+  public RequestStatusResponse createAction(ExecuteActionRequest actionRequest, Map<String, String> requestProperties)
       throws AmbariException {
     String clusterName = null;
 
@@ -2463,41 +2446,40 @@ public class AmbariManagementControllerImpl implements
 
     String logDir = ""; //TODO empty for now
 
-    for (ActionRequest actionRequest : request) {
-      if (actionRequest.getClusterName() == null
-          || actionRequest.getClusterName().isEmpty()
-          || actionRequest.getServiceName() == null
-          || actionRequest.getServiceName().isEmpty()
-          || actionRequest.getActionName() == null
-          || actionRequest.getActionName().isEmpty()) {
-        throw new AmbariException("Invalid action request : " + "cluster="
-            + actionRequest.getClusterName() + ", service="
-            + actionRequest.getServiceName() + ", action="
-            + actionRequest.getActionName());
-      } else if (clusterName == null) {
-        clusterName = actionRequest.getClusterName();
-      } else if (!clusterName.equals(actionRequest.getClusterName())) {
-        throw new AmbariException("Requests for different clusters found");
-      }
+    if (actionRequest.getClusterName() == null
+        || actionRequest.getClusterName().isEmpty()
+        || actionRequest.getServiceName() == null
+        || actionRequest.getServiceName().isEmpty()
+        || actionRequest.getCommandName() == null
+        || actionRequest.getCommandName().isEmpty()) {
+      throw new AmbariException("Invalid action request : " + "cluster="
+          + actionRequest.getClusterName() + ", service="
+          + actionRequest.getServiceName() + ", command="
+          + actionRequest.getCommandName());
     }
 
+    clusterName = actionRequest.getClusterName();
+
     Stage stage = stageFactory.createNew(actionManager.getNextRequestId(),
         logDir, clusterName, requestContext);
 
     stage.setStageId(0);
-    for (ActionRequest actionRequest : request) {
-      LOG.info("Received a createAction request"
-          + ", clusterName=" + actionRequest.getClusterName()
-          + ", serviceName=" + actionRequest.getServiceName()
-          + ", request=" + actionRequest.toString());
-
-      if (actionRequest.getActionName().contains("SERVICE_CHECK")) {
-        addServiceCheckAction(actionRequest, stage);
-      } else if (actionRequest.getActionName().equals("DECOMMISSION_DATANODE")) {
-        addDecommissionDatanodeAction(actionRequest, stage);
-      } else {
-        throw new AmbariException("Unsupported action");
-      }
+    LOG.info("Received a createAction request"
+        + ", clusterName=" + actionRequest.getClusterName()
+        + ", serviceName=" + actionRequest.getServiceName()
+        + ", request=" + actionRequest.toString());
+
+    if (!isValidCommand(actionRequest.getCommandName(), actionRequest.getServiceName())) {
+      throw new AmbariException(
+          "Unsupported action " + actionRequest.getCommandName() + " for " + actionRequest.getServiceName());
+    }
+
+    if (actionRequest.getCommandName().contains("SERVICE_CHECK")) {
+      addServiceCheckAction(actionRequest, stage);
+    } else if (actionRequest.getCommandName().equals("DECOMMISSION_DATANODE")) {
+      addDecommissionDatanodeAction(actionRequest, stage);
+    } else {
+      throw new AmbariException("Unsupported action " + actionRequest.getCommandName());
     }
 
     Cluster cluster = clusters.getCluster(clusterName);
@@ -2532,6 +2514,19 @@ public class AmbariManagementControllerImpl implements
 
   }
 
+  private Boolean isValidCommand(String command, String service) {
+    List<String> actions = actionMetadata.getActions(service);
+    if (actions == null || actions.size() == 0) {
+      return false;
+    }
+
+    if (!actions.contains(command)) {
+      return false;
+    }
+
+    return true;
+  }
+
   private Set<StackResponse> getStacks(StackRequest request)
       throws AmbariException {
     Set<StackResponse> response;

+ 3 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/ControllerModule.java

@@ -24,6 +24,8 @@ import java.util.Properties;
 
 import org.apache.ambari.server.actionmanager.ActionDBAccessor;
 import org.apache.ambari.server.actionmanager.ActionDBAccessorImpl;
+import org.apache.ambari.server.actionmanager.CustomActionDBAccessor;
+import org.apache.ambari.server.actionmanager.CustomActionDBAccessorImpl;
 import org.apache.ambari.server.actionmanager.ExecutionCommandWrapper;
 import org.apache.ambari.server.actionmanager.HostRoleCommandFactory;
 import org.apache.ambari.server.actionmanager.HostRoleCommandFactoryImpl;
@@ -96,6 +98,7 @@ public class ControllerModule extends AbstractModule {
     bind(Gson.class).in(Scopes.SINGLETON);
     bind(Clusters.class).to(ClustersImpl.class);
     bind(ActionDBAccessor.class).to(ActionDBAccessorImpl.class);
+    bind(CustomActionDBAccessor.class).to(CustomActionDBAccessorImpl.class);
     bindConstant().annotatedWith(Names.named("schedulerSleeptime")).to(10000L);
     bindConstant().annotatedWith(Names.named("actionTimeout")).to(600000L);
 

+ 89 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/ExecuteActionRequest.java

@@ -0,0 +1,89 @@
+/**
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class to capture details used to create action or custom commands
+ */
+public class ExecuteActionRequest {
+  private String clusterName;
+  private String commandName;
+  private String actionName;
+  private String serviceName;
+  private String componentName;
+  private List<String> hosts;
+  private Map<String, String> parameters;
+
+  public ExecuteActionRequest(String clusterName, String commandName,
+                       String actionName, String serviceName, String componentName,
+                       List<String> hosts, Map<String, String> parameters) {
+    this.clusterName = clusterName;
+    this.commandName = commandName;
+    this.actionName = actionName;
+    this.serviceName = serviceName;
+    this.componentName = componentName;
+    this.parameters = parameters;
+    this.hosts = hosts;
+  }
+
+  /**
+   * Create an ExecuteActionRequest to execute a command
+   */
+  public ExecuteActionRequest(String clusterName, String commandName, String serviceName,
+                       Map<String, String> parameters) {
+    this.clusterName = clusterName;
+    this.commandName = commandName;
+    this.serviceName = serviceName;
+    this.parameters = parameters;
+  }
+
+  public String getClusterName() {
+    return clusterName;
+  }
+
+  public String getCommandName() {
+    return commandName;
+  }
+
+  public String getActionName() {
+    return actionName;
+  }
+
+  public String getServiceName() {
+    return serviceName;
+  }
+
+  public String getComponentName() {
+    return componentName;
+  }
+
+  public Map<String, String> getParameters() {
+    return parameters;
+  }
+
+  public List<String> getHosts() {
+    return hosts;
+  }
+
+  public Boolean isCommand() {
+    return actionName == null || actionName.isEmpty();
+  }
+}

+ 250 - 70
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ActionResourceProvider.java

@@ -15,126 +15,306 @@
  * 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.AmbariException;
+import org.apache.ambari.server.actionmanager.ActionDefinition;
+import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.actionmanager.ActionType;
+import org.apache.ambari.server.actionmanager.CustomActionDBAccessorImpl;
+import org.apache.ambari.server.actionmanager.TargetHostType;
 import org.apache.ambari.server.controller.ActionRequest;
+import org.apache.ambari.server.controller.ActionResponse;
 import org.apache.ambari.server.controller.AmbariManagementController;
-import org.apache.ambari.server.controller.spi.*;
 import org.apache.ambari.server.controller.RequestStatusResponse;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.Resource.Type;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
 
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.HashSet;
+import java.util.List;
 import java.util.Map;
-import java.util.Map.Entry;
 import java.util.Set;
 
-/**
- * Resource provider for action resources.
- */
-class ActionResourceProvider extends AbstractControllerResourceProvider {
-
-  // ----- Property ID constants ---------------------------------------------
-
-  // Actions
-  protected static final String ACTION_CLUSTER_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Actions", "cluster_name");
-  protected static final String ACTION_SERVICE_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Actions", "service_name");
-  protected static final String ACTION_ACTION_NAME_PROPERTY_ID = PropertyHelper.getPropertyId("Actions", "action_name");
+public class ActionResourceProvider extends AbstractControllerResourceProvider {
 
+  public static final String ACTION_NAME_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "action_name");
+  public static final String ACTION_TYPE_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "action_type");
+  public static final String INPUTS_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "inputs");
+  public static final String TARGET_SERVICE_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "target_service");
+  public static final String TARGET_COMPONENT_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "target_component");
+  public static final String DESCRIPTION_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "description");
+  public static final String TARGET_HOST_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "target_type");
+  public static final String DEFAULT_TIMEOUT_PROPERTY_ID = PropertyHelper
+      .getPropertyId("Actions", "default_timeout");
+  private static Set<String> pkPropertyIds = new HashSet<String>(
+      Arrays.asList(new String[]{ACTION_NAME_PROPERTY_ID}));
+  private Boolean enableExperimental = false;
 
-  private static Set<String> pkPropertyIds =
-      new HashSet<String>(Arrays.asList(new String[]{
-          ACTION_CLUSTER_NAME_PROPERTY_ID,
-          ACTION_SERVICE_NAME_PROPERTY_ID}));
+  public ActionResourceProvider(Set<String> propertyIds,
+                                Map<Type, String> keyPropertyIds,
+                                AmbariManagementController managementController) {
+    super(propertyIds, keyPropertyIds, managementController);
+  }
 
-  ActionResourceProvider(Set<String> propertyIds,
-                         Map<Resource.Type, String> keyPropertyIds,
-                         AmbariManagementController managementController) {
+  public Boolean getEnableExperimental() {
+    return enableExperimental;
+  }
 
-    super(propertyIds, keyPropertyIds, managementController);
+  public void setEnableExperimental(Boolean enabled) {
+    enableExperimental = enabled;
   }
 
   @Override
   public RequestStatus createResources(Request request)
       throws SystemException,
-             UnsupportedPropertyException,
-             ResourceAlreadyExistsException,
-             NoSuchParentResourceException {
+      UnsupportedPropertyException,
+      ResourceAlreadyExistsException,
+      NoSuchParentResourceException {
+
+    if (!getEnableExperimental()) {
+      throw new UnsupportedOperationException("Not currently supported.");
+    }
+
+    for (final Map<String, Object> properties : request.getProperties()) {
+      createResources(new Command<Void>() {
+        @Override
+        public Void invoke() throws AmbariException {
+          ActionRequest actionReq = getRequest(properties);
+          LOG.info("Received a create request for Action with"
+              + ", actionName = " + actionReq.getActionName()
+              + ", actionType = " + actionReq.getActionType()
+              + ", description = " + actionReq.getDescription()
+              + ", service = " + actionReq.getTargetService());
+
+          createActionDefinition(actionReq);
+          return null;
+        }
+      });
+    }
+    notifyCreate(Type.Action, request);
+
+    return getRequestStatus(null);
+  }
+
+  @Override
+  public RequestStatus updateResources(final Request request, Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    if (!getEnableExperimental()) {
+      throw new UnsupportedOperationException("Not currently supported.");
+    }
 
     final Set<ActionRequest> requests = new HashSet<ActionRequest>();
-    
-    final Map<String, String> requestInfoProperties = request.getRequestInfoProperties();
-    
-    for (Map<String, Object> propertyMap : request.getProperties()) {
-      requests.add(getRequest(propertyMap));
+    RequestStatusResponse response;
+
+    for (Map<String, Object> requestPropertyMap : request.getProperties()) {
+      Set<Map<String, Object>> propertyMaps = getPropertyMaps(requestPropertyMap, predicate);
+      for (Map<String, Object> propertyMap : propertyMaps) {
+        ActionRequest actionReq = getRequest(propertyMap);
+        LOG.info("Received a update request for Action with"
+            + ", actionName = " + actionReq.getActionName()
+            + ", actionType = " + actionReq.getActionType()
+            + ", description = " + actionReq.getDescription()
+            + ", timeout = " + actionReq.getDefaultTimeout());
+
+        requests.add(actionReq);
+      }
     }
-    return getRequestStatus(createResources(new Command<RequestStatusResponse>() {
+    response = modifyResources(new Command<RequestStatusResponse>() {
       @Override
       public RequestStatusResponse invoke() throws AmbariException {
-        return getManagementController().createActions(requests, requestInfoProperties);
+        return updateActionDefinitions(requests, request.getRequestInfoProperties());
       }
-    }));
+    });
+    notifyUpdate(Type.Action, request, predicate);
+    return getRequestStatus(response);
   }
 
   @Override
   public Set<Resource> getResources(Request request, Predicate predicate)
-      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-    throw new UnsupportedOperationException("Not currently supported.");
-  }
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
 
-  @Override
-  public RequestStatus updateResources(Request request, Predicate predicate)
-      throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-    throw new UnsupportedOperationException("Not currently supported.");
+    final Set<ActionRequest> requests = new HashSet<ActionRequest>();
+    if (predicate != null) {
+      for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+        ActionRequest actionReq = getRequest(propertyMap);
+        LOG.debug("Received a get request for Action with"
+            + ", actionName = " + actionReq.getActionName());
+        requests.add(actionReq);
+      }
+    } else {
+      LOG.debug("Received a get request for all Actions");
+      requests.add(ActionRequest.getAllRequest());
+    }
+
+    Set<ActionResponse> responses = getResources(new Command<Set<ActionResponse>>() {
+      @Override
+      public Set<ActionResponse> invoke() throws AmbariException {
+        return getActionDefinitions(requests);
+      }
+    });
+
+    Set<String> requestedIds = getRequestPropertyIds(request, predicate);
+    Set<Resource> resources = new HashSet<Resource>();
+
+    for (ActionResponse response : responses) {
+      Resource resource = new ResourceImpl(Type.Action);
+      setResourceProperty(resource, ACTION_NAME_PROPERTY_ID,
+          response.getActionName(), requestedIds);
+      setResourceProperty(resource, ACTION_TYPE_PROPERTY_ID,
+          response.getActionType(), requestedIds);
+      setResourceProperty(resource, INPUTS_PROPERTY_ID,
+          response.getInputs(), requestedIds);
+      setResourceProperty(resource, TARGET_SERVICE_PROPERTY_ID,
+          response.getTargetService(), requestedIds);
+      setResourceProperty(resource, TARGET_COMPONENT_PROPERTY_ID,
+          response.getTargetComponent(), requestedIds);
+      setResourceProperty(resource, DESCRIPTION_PROPERTY_ID,
+          response.getDescription(), requestedIds);
+      setResourceProperty(resource, TARGET_HOST_PROPERTY_ID,
+          response.getTargetType(), requestedIds);
+      setResourceProperty(resource, DEFAULT_TIMEOUT_PROPERTY_ID,
+          response.getDefaultTimeout(), requestedIds);
+      resources.add(resource);
+    }
+    return resources;
   }
 
   @Override
   public RequestStatus deleteResources(Predicate predicate)
       throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-    throw new UnsupportedOperationException("Not currently supported.");
-  }
-
-  @Override
-  public Set<String> checkPropertyIds(Set<String> propertyIds) {
-    propertyIds = super.checkPropertyIds(propertyIds);
 
-    if (propertyIds.isEmpty()) {
-      return propertyIds;
+    if (!getEnableExperimental()) {
+      throw new UnsupportedOperationException("Not currently supported.");
     }
-    Set<String> unsupportedProperties = new HashSet<String>();
 
-    for (String propertyId : propertyIds) {
-      String propertyCategory = PropertyHelper.getPropertyCategory(propertyId);
-      if (propertyCategory == null || !propertyCategory.equals("parameters")) {
-        unsupportedProperties.add(propertyId);
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      final ActionRequest request = getRequest(propertyMap);
+      try {
+        LOG.info("Received a delete request for Action with"
+            + ", actionName = " + request.getActionName());
+
+        deleteActionDefinition(request);
+      } catch (AmbariException ex) {
+        throw new NoSuchResourceException(ex.getMessage());
       }
     }
-    return unsupportedProperties;
+    notifyDelete(Type.Action, predicate);
+    return getRequestStatus(null);
+  }
+
+  private ActionRequest getRequest(Map<String, Object> properties) {
+    ActionRequest ar = new ActionRequest(
+        (String) properties.get(ACTION_NAME_PROPERTY_ID),
+        (String) properties.get(ACTION_TYPE_PROPERTY_ID),
+        (String) properties.get(INPUTS_PROPERTY_ID),
+        (String) properties.get(TARGET_SERVICE_PROPERTY_ID),
+        (String) properties.get(TARGET_COMPONENT_PROPERTY_ID),
+        (String) properties.get(DESCRIPTION_PROPERTY_ID),
+        (String) properties.get(TARGET_HOST_PROPERTY_ID),
+        (String) properties.get(DEFAULT_TIMEOUT_PROPERTY_ID));
+
+    return ar;
   }
 
   @Override
-  protected Set<String> getPKPropertyIds() {
+  public Set<String> getPKPropertyIds() {
     return pkPropertyIds;
   }
 
-  private ActionRequest getRequest(Map<String, Object> properties) {
-    Map<String, String> params = new HashMap<String, String>();
-    for (Entry<String, Object> entry : properties.entrySet()) {
-      String propertyid = entry.getKey();
-
-      String propertyCategory = PropertyHelper.getPropertyCategory(propertyid);
-      if (propertyCategory != null &&
-          propertyCategory.equals("parameters") &&
-          null != entry.getValue()) {
-        params.put(PropertyHelper.getPropertyName(propertyid), entry.getValue().toString());
+  private ActionManager getActionManager() {
+    return getManagementController().getActionManager();
+  }
+
+  protected synchronized void createActionDefinition(ActionRequest request)
+      throws AmbariException {
+    if (request.getActionName() == null
+        || request.getActionName().isEmpty()) {
+      throw new IllegalArgumentException("Action name should be provided");
+    }
+
+    LOG.info("Received a createActionDefinition request = " + request.toString());
+    if (request.getTargetType() == null || request.getActionType() == null) {
+      throw new AmbariException("Both target_type and action_type must be specified.");
+    }
+    TargetHostType targetType = TargetHostType.valueOf(request.getTargetType());
+    ActionType actionType = ActionType.valueOf(request.getActionType());
+
+    Short defaultTimeout = CustomActionDBAccessorImpl.MIN_TIMEOUT;
+    if (request.getDefaultTimeout() != null && !request.getDefaultTimeout().isEmpty()) {
+      defaultTimeout = Short.parseShort(request.getDefaultTimeout());
+    }
+
+    getActionManager().createActionDefinition(request.getActionName(), actionType, request.getInputs(),
+        request.getDescription(), request.getTargetService(), request.getTargetComponent(),
+        targetType, defaultTimeout);
+  }
+
+  protected synchronized Set<ActionResponse> getActionDefinitions(Set<ActionRequest> requests)
+      throws AmbariException {
+    Set<ActionResponse> responses = new HashSet<ActionResponse>();
+    for (ActionRequest request : requests) {
+      if (request.getActionName() == null) {
+        List<ActionDefinition> ads = getActionManager().getAllActionDefinition();
+        for (ActionDefinition ad : ads) {
+          responses.add(new ActionResponse(ad));
+        }
+      } else {
+        ActionDefinition ad = getActionManager().getActionDefinition(request.getActionName());
+        if (ad != null) {
+          responses.add(new ActionResponse(ad));
+        }
       }
     }
-    return new ActionRequest(
-        (String)  properties.get(ACTION_CLUSTER_NAME_PROPERTY_ID),
-        (String)  properties.get(ACTION_SERVICE_NAME_PROPERTY_ID),
-        (String)  properties.get(ACTION_ACTION_NAME_PROPERTY_ID),
-        params);
+
+    return responses;
+  }
+
+  protected synchronized RequestStatusResponse updateActionDefinitions(Set<ActionRequest> requests,
+                                                                       Map<String, String> requestProperties)
+      throws AmbariException {
+    RequestStatusResponse response = null;
+    for (ActionRequest request : requests) {
+      if (null != request.getInputs() || null != request.getTargetService()
+          || null != request.getTargetComponent()) {
+        throw new AmbariException("Cannot update inputs, target_service, or target_component");
+      }
+      TargetHostType targetType = request.getTargetType() == null ? null
+          : TargetHostType.valueOf(request.getTargetType());
+      ActionType actionType = request.getActionType() == null ? null : ActionType.valueOf(request.getActionType());
+      Short defaultTimeout = null;
+      if (request.getDefaultTimeout() != null && !request.getDefaultTimeout().isEmpty()) {
+        defaultTimeout = Short.parseShort(request.getDefaultTimeout());
+      }
+      getActionManager().updateActionDefinition(request.getActionName(), actionType,
+          request.getDescription(), targetType, defaultTimeout);
+    }
+    return response;
+  }
+
+  protected synchronized void deleteActionDefinition(ActionRequest request)
+      throws AmbariException {
+    getActionManager().deleteActionDefinition(request.getActionName());
   }
 }

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

@@ -141,7 +141,7 @@ public class RequestImpl implements Request {
     for (Map<String, Object> map : properties) {
       for (Entry<String, Object> entry : map.entrySet()) {
         sb.append(" { propertyName=").append(entry.getKey()).append(", propertyValue=").
-            append(entry.getValue().toString()).append(" }, ");
+            append(entry.getValue()==null?"NULL":entry.getValue().toString()).append(" }, ");
       }
     }
     sb.append(" ], temporalInfo=[");
@@ -151,7 +151,7 @@ public class RequestImpl implements Request {
       for (Entry<String, TemporalInfo> entry :
         m_mapTemporalInfo.entrySet()) {
         sb.append(" { propertyName=").append(entry.getKey()).append(", temporalInfo=").
-            append(entry.getValue().toString());
+            append(entry.getValue()==null?"NULL":entry.getValue().toString());
       }
     }
     sb.append(" ]");

+ 109 - 36
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/RequestResourceProvider.java

@@ -17,19 +17,24 @@
  */
 package org.apache.ambari.server.controller.internal;
 
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.actionmanager.ActionManager;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
 import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.controller.ExecuteActionRequest;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.RequestStatusResponse;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
 import org.apache.ambari.server.controller.spi.Predicate;
 import org.apache.ambari.server.controller.spi.Request;
 import org.apache.ambari.server.controller.spi.RequestStatus;
 import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
 import org.apache.ambari.server.controller.spi.SystemException;
 import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
@@ -47,17 +52,22 @@ class RequestResourceProvider extends AbstractControllerResourceProvider {
   // ----- Property ID constants ---------------------------------------------
   // Requests
   protected static final String REQUEST_CLUSTER_NAME_PROPERTY_ID = "Requests/cluster_name";
-  protected static final String REQUEST_ID_PROPERTY_ID           = "Requests/id";
-  protected static final String REQUEST_STATUS_PROPERTY_ID       = "Requests/request_status";
-  protected static final String REQUEST_CONTEXT_ID               = "Requests/request_context";
-  protected static final String REQUEST_TASK_CNT_ID              = "Requests/task_count";
-  protected static final String REQUEST_FAILED_TASK_CNT_ID       = "Requests/failed_task_count";
-  protected static final String REQUEST_ABORTED_TASK_CNT_ID      = "Requests/aborted_task_count";
-  protected static final String REQUEST_TIMED_OUT_TASK_CNT_ID    = "Requests/timed_out_task_count";
-  protected static final String REQUEST_COMPLETED_TASK_CNT_ID    = "Requests/completed_task_count";
-  protected static final String REQUEST_QUEUED_TASK_CNT_ID       = "Requests/queued_task_count";
-  protected static final String REQUEST_PROGRESS_PERCENT_ID      = "Requests/progress_percent";
-
+  protected static final String REQUEST_ID_PROPERTY_ID = "Requests/id";
+  protected static final String REQUEST_STATUS_PROPERTY_ID = "Requests/request_status";
+  protected static final String REQUEST_CONTEXT_ID = "Requests/request_context";
+  protected static final String REQUEST_TASK_CNT_ID = "Requests/task_count";
+  protected static final String REQUEST_FAILED_TASK_CNT_ID = "Requests/failed_task_count";
+  protected static final String REQUEST_ABORTED_TASK_CNT_ID = "Requests/aborted_task_count";
+  protected static final String REQUEST_TIMED_OUT_TASK_CNT_ID = "Requests/timed_out_task_count";
+  protected static final String REQUEST_COMPLETED_TASK_CNT_ID = "Requests/completed_task_count";
+  protected static final String REQUEST_QUEUED_TASK_CNT_ID = "Requests/queued_task_count";
+  protected static final String REQUEST_PROGRESS_PERCENT_ID = "Requests/progress_percent";
+  protected static final String COMMAND_ID = "command";
+  protected static final String ACTION_ID = "action";
+  protected static final String HOSTS_ID = "hosts";
+  protected static final String SERVICE_NAME_ID = "service_name";
+  protected static final String COMPONENT_NAME_ID = "component_name";
+  protected static final String INPUTS_ID = "parameters";
   private static Set<String> pkPropertyIds =
       new HashSet<String>(Arrays.asList(new String[]{
           REQUEST_ID_PROPERTY_ID}));
@@ -80,16 +90,27 @@ class RequestResourceProvider extends AbstractControllerResourceProvider {
   // ----- ResourceProvider ------------------------------------------------
 
   @Override
-  public RequestStatus createResources(Request request) {
-    throw new UnsupportedOperationException("Not currently supported.");
+  public RequestStatus createResources(Request request)
+      throws SystemException, UnsupportedPropertyException, NoSuchParentResourceException, ResourceAlreadyExistsException {
+    if (request.getProperties().size() > 1) {
+      throw new UnsupportedOperationException("Multiple actions/commands cannot be executed at the same time.");
+    }
+    final ExecuteActionRequest actionRequest = getActionRequest(request);
+    final Map<String, String> requestInfoProperties = request.getRequestInfoProperties();
+    return getRequestStatus(createResources(new Command<RequestStatusResponse>() {
+      @Override
+      public RequestStatusResponse invoke() throws AmbariException {
+        return getManagementController().createAction(actionRequest, requestInfoProperties);
+      }
+    }));
   }
 
   @Override
   public Set<Resource> getResources(Request request, Predicate predicate)
       throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
 
-    Set<String>   requestedIds = getRequestPropertyIds(request, predicate);
-    Set<Resource> resources    = new HashSet<Resource>();
+    Set<String> requestedIds = getRequestPropertyIds(request, predicate);
+    Set<Resource> resources = new HashSet<Resource>();
 
     for (Map<String, Object> properties : getPropertyMaps(predicate)) {
       String clusterName = (String) properties.get(REQUEST_CLUSTER_NAME_PROPERTY_ID);
@@ -130,13 +151,65 @@ class RequestResourceProvider extends AbstractControllerResourceProvider {
 
   // ----- utility methods --------------------------------------------------
 
+  // Get request to execute an action/command
+  private ExecuteActionRequest getActionRequest(Request request) {
+    Map<String, String> requestInfoProperties = request.getRequestInfoProperties();
+    Map<String, Object> propertyMap = request.getProperties().iterator().next();
+
+    Boolean isCommand = requestInfoProperties.containsKey(COMMAND_ID);
+    String commandName = null;
+    String actionName = null;
+    if (isCommand) {
+      if (requestInfoProperties.containsKey(ACTION_ID)) {
+        throw new UnsupportedOperationException("Both command and action cannot be specified.");
+      }
+      commandName = requestInfoProperties.get(COMMAND_ID);
+    } else {
+      if (!requestInfoProperties.containsKey(ACTION_ID)) {
+        throw new UnsupportedOperationException("Either command or action must be specified.");
+      }
+      actionName = requestInfoProperties.get(ACTION_ID);
+    }
+
+    String hostList = requestInfoProperties.get(HOSTS_ID);
+    List<String> hosts = new ArrayList<String>();
+    if (hostList != null && !hostList.isEmpty()) {
+      for (String hostname : hostList.split(",")) {
+        String trimmedName = hostname.trim();
+        if (!trimmedName.isEmpty()) {
+          hosts.add(hostname.trim());
+        }
+      }
+    }
+
+    String serviceName = requestInfoProperties.get(SERVICE_NAME_ID);
+    String componentName = requestInfoProperties.get(COMPONENT_NAME_ID);
+
+    Map<String, String> params = new HashMap<String, String>();
+    String keyPrefix = "/" + INPUTS_ID + "/";
+    for (String key : requestInfoProperties.keySet()) {
+      if (key.startsWith(keyPrefix)) {
+        params.put(key.substring(keyPrefix.length()), requestInfoProperties.get(key));
+      }
+    }
+
+    return new ExecuteActionRequest(
+        (String) propertyMap.get(REQUEST_CLUSTER_NAME_PROPERTY_ID),
+        commandName,
+        actionName,
+        serviceName,
+        componentName,
+        hosts,
+        params);
+  }
+
   // Get all of the request resources for the given properties
   private Set<Resource> getRequestResources(String clusterName,
                                             Long requestId,
                                             String requestStatus,
                                             Set<String> requestedPropertyIds) throws NoSuchResourceException {
 
-    Set<Resource> response      = new HashSet<Resource>();
+    Set<Resource> response = new HashSet<Resource>();
     ActionManager actionManager = getManagementController().getActionManager();
 
     if (requestId == null) {
@@ -170,15 +243,15 @@ class RequestResourceProvider extends AbstractControllerResourceProvider {
                                                    Set<String> requestedPropertyIds) {
 
     List<HostRoleCommand> hostRoleCommands = actionManager.getAllTasksByRequestIds(requestIds);
-    Map<Long, String>     requestContexts  = actionManager.getRequestContext(requestIds);
-    Map<Long, Resource>   resourceMap      = new HashMap<Long, Resource>();
+    Map<Long, String> requestContexts = actionManager.getRequestContext(requestIds);
+    Map<Long, Resource> resourceMap = new HashMap<Long, Resource>();
 
     // group by request id
     Map<Long, Set<HostRoleCommand>> commandMap = new HashMap<Long, Set<HostRoleCommand>>();
 
     for (HostRoleCommand hostRoleCommand : hostRoleCommands) {
-      Long                 requestId = hostRoleCommand.getRequestId();
-      Set<HostRoleCommand> commands  = commandMap.get(requestId);
+      Long requestId = hostRoleCommand.getRequestId();
+      Set<HostRoleCommand> commands = commandMap.get(requestId);
 
       if (commands == null) {
         commands = new HashSet<HostRoleCommand>();
@@ -188,9 +261,9 @@ class RequestResourceProvider extends AbstractControllerResourceProvider {
     }
 
     for (Map.Entry<Long, Set<HostRoleCommand>> entry : commandMap.entrySet()) {
-      Long                 requestId = entry.getKey();
-      Set<HostRoleCommand> commands  = entry.getValue();
-      String               context   = requestContexts.get(requestId);
+      Long requestId = entry.getKey();
+      Set<HostRoleCommand> commands = entry.getValue();
+      String context = requestContexts.get(requestId);
 
       resourceMap.put(requestId,
           getRequestResource(clusterName, requestId, context, commands, requestedPropertyIds));
@@ -210,13 +283,13 @@ class RequestResourceProvider extends AbstractControllerResourceProvider {
     setResourceProperty(resource, REQUEST_ID_PROPERTY_ID, requestId, requestedPropertyIds);
     setResourceProperty(resource, REQUEST_CONTEXT_ID, context, requestedPropertyIds);
 
-    int taskCount          = commands.size();
+    int taskCount = commands.size();
     int completedTaskCount = 0;
-    int queuedTaskCount    = 0;
-    int pendingTaskCount   = 0;
-    int failedTaskCount    = 0;
-    int abortedTaskCount   = 0;
-    int timedOutTaskCount  = 0;
+    int queuedTaskCount = 0;
+    int pendingTaskCount = 0;
+    int failedTaskCount = 0;
+    int abortedTaskCount = 0;
+    int timedOutTaskCount = 0;
 
     for (HostRoleCommand hostRoleCommand : commands) {
       HostRoleStatus status = hostRoleCommand.getStatus();
@@ -244,14 +317,14 @@ class RequestResourceProvider extends AbstractControllerResourceProvider {
     int inProgressTaskCount = taskCount - completedTaskCount - queuedTaskCount - pendingTaskCount;
 
     // determine request status
-    HostRoleStatus requestStatus = failedTaskCount > 0             ? HostRoleStatus.FAILED :
-                                   abortedTaskCount > 0            ? HostRoleStatus.ABORTED :
-                                   timedOutTaskCount > 0           ? HostRoleStatus.TIMEDOUT :
-                                   inProgressTaskCount > 0         ? HostRoleStatus.IN_PROGRESS :
-                                   completedTaskCount == taskCount ? HostRoleStatus.COMPLETED :
-                                                                     HostRoleStatus.PENDING;
+    HostRoleStatus requestStatus = failedTaskCount > 0 ? HostRoleStatus.FAILED :
+        abortedTaskCount > 0 ? HostRoleStatus.ABORTED :
+            timedOutTaskCount > 0 ? HostRoleStatus.TIMEDOUT :
+                inProgressTaskCount > 0 ? HostRoleStatus.IN_PROGRESS :
+                    completedTaskCount == taskCount ? HostRoleStatus.COMPLETED :
+                        HostRoleStatus.PENDING;
     double progressPercent =
-        ((queuedTaskCount * 0.09 + inProgressTaskCount * 0.35 + completedTaskCount)/(double) taskCount) * 100.0;
+        ((queuedTaskCount * 0.09 + inProgressTaskCount * 0.35 + completedTaskCount) / (double) taskCount) * 100.0;
 
     setResourceProperty(resource, REQUEST_STATUS_PROPERTY_ID, requestStatus.toString(), requestedPropertyIds);
     setResourceProperty(resource, REQUEST_TASK_CNT_ID, taskCount, requestedPropertyIds);

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

@@ -39,7 +39,7 @@ public interface Resource {
    * each key is the absolute category name and the corresponding
    * value is a map of properties(name/value pairs) for that category.
    *
-   * @return  resource properties map
+   * @return resource properties map
    */
   public Map<String, Map<String, Object>> getPropertiesMap();
 
@@ -54,7 +54,7 @@ public interface Resource {
   /**
    * Add an empty category to this resource.
    *
-   * @param id    the category id
+   * @param id the category id
    */
   public void addCategory(String id);
 

+ 71 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ActionDefinitionDAO.java

@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.dao;
+
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.persist.Transactional;
+import org.apache.ambari.server.orm.entities.ActionEntity;
+
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.TypedQuery;
+import java.util.List;
+
+public class ActionDefinitionDAO {
+
+  @Inject
+  Provider<EntityManager> entityManagerProvider;
+
+  @Transactional
+  public ActionEntity findByPK(String actionName) {
+    return entityManagerProvider.get().find(ActionEntity.class, actionName);
+  }
+
+  @Transactional
+  public List<ActionEntity> findAll() {
+    TypedQuery<ActionEntity> query = entityManagerProvider.get().createNamedQuery("allActions",
+        ActionEntity.class);
+    try {
+      return query.getResultList();
+    } catch (NoResultException ignored) {
+    }
+    return null;
+  }
+
+  @Transactional
+  public void create(ActionEntity actionDefinition) {
+    entityManagerProvider.get().persist(actionDefinition);
+  }
+
+  @Transactional
+  public ActionEntity merge(ActionEntity actionDefinition) {
+    return entityManagerProvider.get().merge(actionDefinition);
+  }
+
+  @Transactional
+  public void remove(ActionEntity actionDefinition) {
+    entityManagerProvider.get().remove(merge(actionDefinition));
+  }
+
+  @Transactional
+  public void removeByPK(String actionName) {
+    remove(findByPK(actionName));
+  }
+}

+ 6 - 6
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ExecutionCommandDAO.java

@@ -38,18 +38,18 @@ public class ExecutionCommandDAO {
   }
 
   @Transactional
-  public void create(ExecutionCommandEntity stageEntity) {
-    entityManagerProvider.get().persist(stageEntity);
+  public void create(ExecutionCommandEntity executionCommand) {
+    entityManagerProvider.get().persist(executionCommand);
   }
 
   @Transactional
-  public ExecutionCommandEntity merge(ExecutionCommandEntity stageEntity) {
-    return entityManagerProvider.get().merge(stageEntity);
+  public ExecutionCommandEntity merge(ExecutionCommandEntity executionCommand) {
+    return entityManagerProvider.get().merge(executionCommand);
   }
 
   @Transactional
-  public void remove(ExecutionCommandEntity stageEntity) {
-    entityManagerProvider.get().remove(merge(stageEntity));
+  public void remove(ExecutionCommandEntity executionCommand) {
+    entityManagerProvider.get().remove(merge(executionCommand));
   }
 
   @Transactional

+ 154 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ActionEntity.java

@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.orm.entities;
+
+import org.apache.ambari.server.actionmanager.ActionType;
+import org.apache.ambari.server.actionmanager.TargetHostType;
+
+import javax.persistence.*;
+
+@NamedQueries({
+    @NamedQuery(name = "allActions", query =
+        "SELECT actions " +
+            "FROM ActionEntity actions")
+})
+@Table(name = "action")
+@Entity
+public class ActionEntity {
+
+  @Id
+  @Column(name = "action_name")
+  private String actionName;
+
+  @Column(name = "action_type")
+  @Enumerated(EnumType.STRING)
+  private ActionType actionType = ActionType.DISABLED;
+
+  @Column(name = "inputs")
+  @Basic
+  private String inputs;
+
+  @Column(name = "target_service")
+  @Basic
+  private String targetService;
+
+  @Column(name = "target_component")
+  @Basic
+  private String targetComponent;
+
+  @Column(name = "description")
+  @Basic
+  private String description = "";
+
+  @Column(name = "target_type")
+  @Enumerated(EnumType.STRING)
+  private TargetHostType targetType = TargetHostType.ANY;
+
+  @Basic
+  @Column(name = "default_timeout", nullable = false)
+  private Short defaultTimeout = 600;
+
+  public String getActionName() {
+    return actionName;
+  }
+
+  public void setActionName(String actionName) {
+    this.actionName = actionName;
+  }
+
+  public ActionType getActionType() {
+    return actionType;
+  }
+
+  public void setActionType(ActionType actionType) {
+    this.actionType = actionType;
+  }
+
+  public String getInputs() {
+    return inputs;
+  }
+
+  public void setInputs(String inputs) {
+    this.inputs = inputs;
+  }
+
+  public String getDescription() {
+    return description;
+  }
+
+  public void setDescription(String description) {
+    this.description = description;
+  }
+
+  public TargetHostType getTargetType() {
+    return targetType;
+  }
+
+  public void setTargetType(TargetHostType targetType) {
+    this.targetType = targetType;
+  }
+
+  public String getTargetService() {
+    return targetService;
+  }
+
+  public void setTargetService(String targetService) {
+    this.targetService = targetService;
+  }
+
+  public String getTargetComponent() {
+    return targetComponent;
+  }
+
+  public void setTargetComponent(String targetComponent) {
+    this.targetComponent = targetComponent;
+  }
+
+  public Short getDefaultTimeout() {
+    return defaultTimeout;
+  }
+
+  public void setDefaultTimeout(Short defaultTimeout) {
+    this.defaultTimeout = defaultTimeout;
+  }
+
+  @Override
+  public boolean equals(Object o) {
+    if (this == o) {
+      return true;
+    }
+    if (o == null || getClass() != o.getClass()) {
+      return false;
+    }
+
+    ActionEntity that = (ActionEntity) o;
+
+    if (actionName != null ? !actionName.equals(that.actionName) : that.actionName != null) {
+      return false;
+    }
+
+    return true;
+  }
+
+  @Override
+  public int hashCode() {
+    int result = actionName != null ? actionName.hashCode() : 0;
+    return result;
+  }
+}

+ 3 - 0
ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql

@@ -119,6 +119,9 @@ GRANT ALL PRIVILEGES ON TABLE ambari.configgroupclusterconfigmapping TO :usernam
 CREATE TABLE ambari.configgrouphostmapping (config_group_id BIGINT NOT NULL, host_name VARCHAR(255) NOT NULL, PRIMARY KEY(config_group_id, host_name));
 GRANT ALL PRIVILEGES ON TABLE ambari.configgrouphostmapping TO :username;
 
+CREATE TABLE ambari.action (action_name VARCHAR(255) NOT NULL, action_type VARCHAR(32) NOT NULL, inputs VARCHAR(1000),
+target_service VARCHAR(255), target_component VARCHAR(255), default_timeout SMALLINT NOT NULL, description VARCHAR(1000), target_type VARCHAR(32), PRIMARY KEY (action_name));
+GRANT ALL PRIVILEGES ON TABLE ambari.action TO :username;
 
 --------altering tables by creating foreign keys----------
 ALTER TABLE ambari.clusterconfig ADD CONSTRAINT FK_clusterconfig_cluster_id FOREIGN KEY (cluster_id) REFERENCES ambari.clusters (cluster_id);

+ 2 - 0
ambari-server/src/main/resources/META-INF/persistence.xml

@@ -41,6 +41,7 @@
     <class>org.apache.ambari.server.orm.entities.ConfigGroupEntity</class>
     <class>org.apache.ambari.server.orm.entities.ConfigGroupConfigMappingEntity</class>
     <class>org.apache.ambari.server.orm.entities.ConfigGroupHostMappingEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ActionEntity</class>
 
     <properties>
       <!--<property name="javax.persistence.jdbc.url" value="jdbc:postgresql://localhost/ambari" />-->
@@ -77,6 +78,7 @@
     <!--<class>org.apache.ambari.server.orm.entities.ClusterConfigMappingEntity</class>-->
     <!--<class>org.apache.ambari.server.orm.entities.HostConfigMappingEntity</class>-->
     <!--<class>org.apache.ambari.server.orm.entities.MetainfoEntity</class>-->
+    <!--<class>org.apache.ambari.server.orm.entities.ActionEntity</class>-->
 
     <!--<properties>-->
       <!--<property name="javax.persistence.jdbc.url" value="jdbc:derby:memory:myDB/ambari;create=true" />-->

+ 103 - 105
ambari-server/src/main/resources/key_properties.json

@@ -1,108 +1,106 @@
 {
-  "Cluster":{
-    "Cluster":"Clusters/cluster_name"
-  },
-  "Service":{
-    "Cluster":"ServiceInfo/cluster_name",
-    "Service":"ServiceInfo/service_name"
-  },
-  "Host":{
-    "Cluster":"Hosts/cluster_name",
-    "Host":"Hosts/host_name"
-  },
-  "Component":{
-    "Cluster":"ServiceComponentInfo/cluster_name",
-    "Service":"ServiceComponentInfo/service_name",
-    "Component":"ServiceComponentInfo/component_name",
-    "HostComponent":"ServiceComponentInfo/component_name"
-  },
-  "HostComponent":{
-    "Cluster":"HostRoles/cluster_name",
-    "Host":"HostRoles/host_name",
-    "HostComponent":"HostRoles/component_name",
-    "Component":"HostRoles/component_name"
-  },
-  "Configuration":{
-    "Cluster":"Config/cluster_name",
-    "Configuration":"Config/type"
-  },
-  "Action":{
-    "Cluster":"Actions/cluster_name",
-    "Service":"Actions/service_name",
-    "Action":"Actions/action_name"
-  },
-  "Request":{
-    "Cluster":"Requests/cluster_name",
-    "Request":"Requests/id"
-  },
-  "Task":{
-    "Cluster":"Tasks/cluster_name",
-    "Request":"Tasks/request_id",
-    "Task":"Tasks/id"
-  },
-  "User":{
-    "User":"Users/user_name"
-  },
-  "Stack":{
-    "Stack":"Stacks/stack_name"
-  },
-  "StackVersion":{
-    "Stack":"Versions/stack_name",
-    "StackVersion":"Versions/stack_version"
-  },
-  "OperatingSystem":{
-    "Stack":"OperatingSystems/stack_name",
-    "StackVersion":"OperatingSystems/stack_version",
-    "OperatingSystem":"OperatingSystems/os_type"
-  },
-  "Repository":{
-    "Stack":"Repositories/stack_name",
-    "StackVersion":"Repositories/stack_version",
-    "OperatingSystem":"Repositories/os_type",
-    "Repository":"Repositories/repo_id"
-  },
-  "StackService":{
-    "Stack":"StackServices/stack_name",
-    "StackVersion":"StackServices/stack_version",
-    "StackService":"StackServices/service_name"
-  },
-  "StackConfiguration":{
-    "Stack":"StackConfigurations/stack_name",
-    "StackVersion":"StackConfigurations/stack_version",
-    "StackService":"StackConfigurations/service_name",
-    "StackConfiguration":"StackConfigurations/property_name"
-  },
-  "StackServiceComponent":{
-    "Stack":"StackServiceComponents/stack_name",
-    "StackVersion":"StackServiceComponents/stack_version",
-    "StackService":"StackServiceComponents/service_name",
-    "StackServiceComponent":"StackServiceComponents/component_name"
-  },
-  "DRFeed":{
-    "DRFeed":"Feed/name"
-  },
-  "DRTargetCluster":{
-    "DRTargetCluster":"Cluster/name"
-  },
-  "DRInstance":{
-    "DRFeed":"Instance/feedName",
-    "DRInstance":"Instance/id"
-  },
-  "RootService":{
-    "RootService":"RootService/service_name"
-  },
-  "RootServiceComponent":{
-    "RootService":"RootServiceComponents/service_name",
-    "RootServiceComponent":"RootServiceComponents/component_name"
-  },
-  "RootServiceHostComponent":{
-    "RootService":"RootServiceHostComponents/service_name",
-    "Host":"RootServiceHostComponents/host_name",
-    "RootServiceComponent":"RootServiceHostComponents/component_name",
-    "RootServiceHostComponent":"RootServiceHostComponents/component_name"
-  },
-  "ConfigGroup" : {
-      "Cluster" : "ConfigGroup/cluster_name",
-      "ConfigGroup" : "ConfigGroup/id"
+  "Cluster": {
+    "Cluster": "Clusters/cluster_name"
+  },
+  "Service": {
+    "Cluster": "ServiceInfo/cluster_name",
+    "Service": "ServiceInfo/service_name"
+  },
+  "Host": {
+    "Cluster": "Hosts/cluster_name",
+    "Host": "Hosts/host_name"
+  },
+  "Component": {
+    "Cluster": "ServiceComponentInfo/cluster_name",
+    "Service": "ServiceComponentInfo/service_name",
+    "Component": "ServiceComponentInfo/component_name",
+    "HostComponent": "ServiceComponentInfo/component_name"
+  },
+  "HostComponent": {
+    "Cluster": "HostRoles/cluster_name",
+    "Host": "HostRoles/host_name",
+    "HostComponent": "HostRoles/component_name",
+    "Component": "HostRoles/component_name"
+  },
+  "Configuration": {
+    "Cluster": "Config/cluster_name",
+    "Configuration": "Config/type"
+  },
+  "Action": {
+    "Action": "Actions/action_name"
+  },
+  "Request": {
+    "Cluster": "Requests/cluster_name",
+    "Request": "Requests/id"
+  },
+  "Task": {
+    "Cluster": "Tasks/cluster_name",
+    "Request": "Tasks/request_id",
+    "Task": "Tasks/id"
+  },
+  "User": {
+    "User": "Users/user_name"
+  },
+  "Stack": {
+    "Stack": "Stacks/stack_name"
+  },
+  "StackVersion": {
+    "Stack": "Versions/stack_name",
+    "StackVersion": "Versions/stack_version"
+  },
+  "OperatingSystem": {
+    "Stack": "OperatingSystems/stack_name",
+    "StackVersion": "OperatingSystems/stack_version",
+    "OperatingSystem": "OperatingSystems/os_type"
+  },
+  "Repository": {
+    "Stack": "Repositories/stack_name",
+    "StackVersion": "Repositories/stack_version",
+    "OperatingSystem": "Repositories/os_type",
+    "Repository": "Repositories/repo_id"
+  },
+  "StackService": {
+    "Stack": "StackServices/stack_name",
+    "StackVersion": "StackServices/stack_version",
+    "StackService": "StackServices/service_name"
+  },
+  "StackConfiguration": {
+    "Stack": "StackConfigurations/stack_name",
+    "StackVersion": "StackConfigurations/stack_version",
+    "StackService": "StackConfigurations/service_name",
+    "StackConfiguration": "StackConfigurations/property_name"
+  },
+  "StackServiceComponent": {
+    "Stack": "StackServiceComponents/stack_name",
+    "StackVersion": "StackServiceComponents/stack_version",
+    "StackService": "StackServiceComponents/service_name",
+    "StackServiceComponent": "StackServiceComponents/component_name"
+  },
+  "DRFeed": {
+    "DRFeed": "Feed/name"
+  },
+  "DRTargetCluster": {
+    "DRTargetCluster": "Cluster/name"
+  },
+  "DRInstance": {
+    "DRFeed": "Instance/feedName",
+    "DRInstance": "Instance/id"
+  },
+  "RootService": {
+    "RootService": "RootService/service_name"
+  },
+  "RootServiceComponent": {
+    "RootService": "RootServiceComponents/service_name",
+    "RootServiceComponent": "RootServiceComponents/component_name"
+  },
+  "RootServiceHostComponent": {
+    "RootService": "RootServiceHostComponents/service_name",
+    "Host": "RootServiceHostComponents/host_name",
+    "RootServiceComponent": "RootServiceHostComponents/component_name",
+    "RootServiceHostComponent": "RootServiceHostComponents/component_name"
+  },
+  "ConfigGroup": {
+    "Cluster": "ConfigGroup/cluster_name",
+    "ConfigGroup": "ConfigGroup/id"
   }
 }

+ 7 - 2
ambari-server/src/main/resources/properties.json

@@ -85,9 +85,14 @@
         "ConfigGroup/desired_configs"
     ],
     "Action":[
-        "Actions/cluster_name",
-        "Actions/service_name",
         "Actions/action_name",
+        "Actions/action_type",
+        "Actions/inputs",
+        "Actions/target_service",
+        "Actions/target_component",
+        "Actions/description",
+        "Actions/target_type",
+        "Actions/default_timeout",
         "_"
     ],
     "Request":[

+ 1 - 1
ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestActionDBAccessorImpl.java

@@ -80,7 +80,7 @@ public class TestActionDBAccessorImpl {
     db = injector.getInstance(ActionDBAccessorImpl.class);
     
     am = new ActionManager(5000, 1200000, new ActionQueue(), clusters, db,
-        new HostsMap((String) null), null, injector.getInstance(UnitOfWork.class));
+        new HostsMap((String) null), null, injector.getInstance(UnitOfWork.class), null);
   }
 
   @After

+ 3 - 3
ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestActionManager.java

@@ -86,7 +86,7 @@ public class TestActionManager {
   public void testActionResponse() {
     ActionDBAccessor db = injector.getInstance(ActionDBAccessorImpl.class);
     ActionManager am = new ActionManager(5000, 1200000, new ActionQueue(),
-        clusters, db, new HostsMap((String) null), null, unitOfWork);
+        clusters, db, new HostsMap((String) null), null, unitOfWork, null);
     populateActionDB(db, hostname);
     Stage stage = db.getAllStages(requestId).get(0);
     Assert.assertEquals(stageId, stage.getStageId());
@@ -122,7 +122,7 @@ public class TestActionManager {
   public void testLargeLogs() {
     ActionDBAccessor db = injector.getInstance(ActionDBAccessorImpl.class);
     ActionManager am = new ActionManager(5000, 1200000, new ActionQueue(),
-        clusters, db, new HostsMap((String) null), null, unitOfWork);
+        clusters, db, new HostsMap((String) null), null, unitOfWork, null);
     populateActionDB(db, hostname);
     Stage stage = db.getAllStages(requestId).get(0);
     Assert.assertEquals(stageId, stage.getStageId());
@@ -207,7 +207,7 @@ public class TestActionManager {
 
     replay(queue, db, clusters);
 
-    ActionManager manager = new ActionManager(0, 0, queue, clusters, db, null, null, unitOfWork);
+    ActionManager manager = new ActionManager(0, 0, queue, clusters, db, null, null, unitOfWork, null);
     assertSame(listStages, manager.getActions(requestId));
 
     verify(queue, db, clusters);

+ 3 - 3
ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestActionScheduler.java

@@ -323,7 +323,7 @@ public class TestActionScheduler {
     ActionScheduler scheduler = new ActionScheduler(100, 50, db, aq, fsm, 3,
         new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork);
     ActionManager am = new ActionManager(
-        2, 2, aq, fsm, db, new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork);
+        2, 2, aq, fsm, db, new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork, null);
 
     scheduler.doWork();
 
@@ -413,7 +413,7 @@ public class TestActionScheduler {
     ActionScheduler scheduler = new ActionScheduler(100, 10000, db, aq, fsm, 3,
         new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork);
     ActionManager am = new ActionManager(
-        2, 10000, aq, fsm, db, new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork);
+        2, 10000, aq, fsm, db, new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork, null);
 
     scheduler.doWork();
 
@@ -527,7 +527,7 @@ public class TestActionScheduler {
     ActionScheduler scheduler = new ActionScheduler(100, 50, db, aq, fsm, 3,
         new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork);
     ActionManager am = new ActionManager(
-        2, 2, aq, fsm, db, new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork);
+        2, 2, aq, fsm, db, new HostsMap((String) null), new ServerActionManagerImpl(fsm), unitOfWork, null);
 
     scheduler.doWork();
 

+ 124 - 0
ambari-server/src/test/java/org/apache/ambari/server/actionmanager/TestCustomActionDBAccessorImpl.java

@@ -0,0 +1,124 @@
+/**
+ * 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.actionmanager;
+
+import com.google.inject.Guice;
+import com.google.inject.Inject;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.dao.ActionDefinitionDAO;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.List;
+
+public class TestCustomActionDBAccessorImpl {
+  private static final Logger log = LoggerFactory.getLogger(TestCustomActionDBAccessorImpl.class);
+  CustomActionDBAccessor db;
+  private Injector injector;
+  @Inject
+  private ActionDefinitionDAO actions;
+
+  @Before
+  public void setup() throws AmbariException {
+    injector = Guice.createInjector(new InMemoryDefaultTestModule());
+    injector.getInstance(GuiceJpaInitializer.class);
+    injector.injectMembers(this);
+    db = injector.getInstance(CustomActionDBAccessorImpl.class);
+  }
+
+  @After
+  public void tearDown() throws AmbariException {
+    injector.getInstance(PersistService.class).stop();
+  }
+
+  @Test
+  public void testActionLifeCycle() throws Exception {
+    try {
+      db.createActionDefinition(
+          "a1", ActionType.SYSTEM, "a,b,,c", "desc", TargetHostType.ANY, "HDFS", "DATANODE", Short.parseShort("70"));
+      Assert.fail("createActionDefinition must throw exception.");
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Empty parameter cannot be specified as an input parameter"));
+    }
+
+    db.createActionDefinition(
+        "a1", ActionType.SYSTEM, "fileName", "desc", TargetHostType.ANY, "HDFS", "DATANODE", Short.parseShort("70"));
+    ActionDefinition ad = db.getActionDefinition("a1");
+    assertContent(
+        ad, "a1", ActionType.SYSTEM, "fileName", "desc", TargetHostType.ANY, "HDFS", "DATANODE", Short.parseShort("70"));
+
+    ad = db.getActionDefinition("a2");
+    Assert.assertNull(ad);
+
+    try {
+      db.createActionDefinition(
+          "a1", ActionType.SYSTEM, "fileName", "desc", TargetHostType.ANY, "HDFS", "DATANODE", Short.parseShort("70"));
+      Assert.fail("updateActionDefinition must throw exception.");
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Action definition a1 already exists"));
+    }
+
+    db.createActionDefinition(
+        "a2", ActionType.SYSTEM, "dirName", "desc2", TargetHostType.ANY, "HDFS", "DATANODE", Short.parseShort("70"));
+    ad = db.getActionDefinition("a2");
+    assertContent(ad, "a2", ActionType.SYSTEM, "dirName", "desc2", TargetHostType.ANY,
+        "HDFS", "DATANODE", Short.parseShort("70"));
+
+    db.updateActionDefinition("a2", ActionType.USER, "desc3", TargetHostType.ALL, Short.parseShort("100"));
+    ad = db.getActionDefinition("a2");
+    assertContent(ad, "a2", ActionType.USER, "dirName", "desc3", TargetHostType.ALL,
+        "HDFS", "DATANODE", Short.parseShort("100"));
+
+    List<ActionDefinition> ads = db.getActionDefinitions();
+    Assert.assertEquals(2, ads.size());
+
+    db.deleteActionDefinition("a1");
+    ads = db.getActionDefinitions();
+    Assert.assertEquals(1, ads.size());
+
+    db.deleteActionDefinition("a1");
+
+    try {
+      db.updateActionDefinition("a1", ActionType.USER, "desc3", TargetHostType.ALL, Short.parseShort("100"));
+      Assert.fail("updateActionDefinition must throw exception.");
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Action definition a1 does not exist"));
+    }
+  }
+
+  private void assertContent(ActionDefinition ad, String actionName, ActionType actionType, String inputs,
+                             String description, TargetHostType targetType, String serviceType, String componentType,
+                             Short defaultTimeout) {
+    Assert.assertEquals(actionName, ad.getActionName());
+    Assert.assertEquals(actionType, ad.getActionType());
+    Assert.assertEquals(description, ad.getDescription());
+    Assert.assertEquals(inputs, ad.getInputs());
+    Assert.assertEquals(serviceType, ad.getTargetService());
+    Assert.assertEquals(componentType, ad.getTargetComponent());
+    Assert.assertEquals(defaultTimeout, ad.getDefaultTimeout());
+    Assert.assertEquals(targetType, ad.getTargetType());
+  }
+}

+ 2 - 2
ambari-server/src/test/java/org/apache/ambari/server/agent/TestHeartbeatHandler.java

@@ -413,7 +413,7 @@ public class TestHeartbeatHandler {
     clusters.addCluster(DummyCluster);
     ActionDBAccessor db = injector.getInstance(ActionDBAccessorImpl.class);
     ActionManager am = new ActionManager(5000, 1200000, new ActionQueue(), clusters, db,
-        new HostsMap((String) null), null, unitOfWork);
+        new HostsMap((String) null), null, unitOfWork, null);
     populateActionDB(db, DummyHostname1);
     Stage stage = db.getAllStages(requestId).get(0);
     Assert.assertEquals(stageId, stage.getStageId());
@@ -1310,7 +1310,7 @@ public class TestHeartbeatHandler {
 
   private ActionManager getMockActionManager() {
     return new ActionManager(0, 0, null, null,
-              new ActionDBInMemoryImpl(), new HostsMap((String) null), null, unitOfWork);
+              new ActionDBInMemoryImpl(), new HostsMap((String) null), null, unitOfWork, null);
   }
 
 

+ 41 - 33
ambari-server/src/test/java/org/apache/ambari/server/api/services/ActionServiceTest.java

@@ -15,65 +15,73 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package org.apache.ambari.server.api.services;
-
-import static org.junit.Assert.assertEquals;
 
-import javax.ws.rs.core.HttpHeaders;
-import javax.ws.rs.core.UriInfo;
+package org.apache.ambari.server.api.services;
 
 import org.apache.ambari.server.api.resources.ResourceInstance;
 import org.apache.ambari.server.api.services.parsers.RequestBodyParser;
 import org.apache.ambari.server.api.services.serializers.ResultSerializer;
 
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.UriInfo;
+
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.List;
 
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Unit tests for ServiceService.
+ */
 public class ActionServiceTest extends BaseServiceTest {
 
   public List<ServiceTestInvocation> getTestInvocations() throws Exception {
     List<ServiceTestInvocation> listInvocations = new ArrayList<ServiceTestInvocation>();
 
-    //getActions
-    ActionService componentService = new TestActionService("clusterName", "serviceName", null);
-    Method m = componentService.getClass().getMethod("getActions", HttpHeaders.class, UriInfo.class);
-    Object[] args = new Object[] {getHttpHeaders(), getUriInfo()};
-    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, componentService, m, args, null));
+    //getActionDefinition
+    ActionService service = new TestActionDefinitionService("actionName");
+    Method m = service.getClass().getMethod("getActionDefinition", HttpHeaders.class, UriInfo.class, String.class);
+    Object[] args = new Object[] {getHttpHeaders(), getUriInfo(), "actionName"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    //getActionDefinitions
+    service = new TestActionDefinitionService(null);
+    m = service.getClass().getMethod("getActionDefinitions", HttpHeaders.class, UriInfo.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo()};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.GET, service, m, args, null));
+
+    //createActionDefinition
+    service = new TestActionDefinitionService("actionName");
+    m = service.getClass().getMethod("createActionDefinition", String.class, HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {"body", getHttpHeaders(), getUriInfo(), "actionName"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, service, m, args, "body"));
 
-    //createAction
-    componentService = new TestActionService("clusterName", "serviceName", "actionName");
-    m = componentService.getClass().getMethod("createAction", String.class, HttpHeaders.class, UriInfo.class, String.class);
+    //updateActionDefinition
+    service = new TestActionDefinitionService("actionName");
+    m = service.getClass().getMethod("updateActionDefinition", String.class, HttpHeaders.class, UriInfo.class, String.class);
     args = new Object[] {"body", getHttpHeaders(), getUriInfo(), "actionName"};
-    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, componentService, m, args, "body"));
+    listInvocations.add(new ServiceTestInvocation(Request.Type.PUT, service, m, args, "body"));
 
-    //createActions
-    componentService = new TestActionService("clusterName", "serviceName", null);
-    m = componentService.getClass().getMethod("createActions", String.class, HttpHeaders.class, UriInfo.class);
-    args = new Object[] {"body", getHttpHeaders(), getUriInfo()};
-    listInvocations.add(new ServiceTestInvocation(Request.Type.POST, componentService, m, args, "body"));
+    //deleteActionDefinition
+    service = new TestActionDefinitionService("actionName");
+    m = service.getClass().getMethod("deleteActionDefinition", HttpHeaders.class, UriInfo.class, String.class);
+    args = new Object[] {getHttpHeaders(), getUriInfo(), "actionName"};
+    listInvocations.add(new ServiceTestInvocation(Request.Type.DELETE, service, m, args, null));
 
     return listInvocations;
   }
 
-  private class TestActionService extends ActionService {
-    private String m_clusterId;
-    private String m_serviceId;
-    private String m_actionId;
-
-    public TestActionService(String clusterName, String serviceName, String actionName) {
+  private class TestActionDefinitionService extends ActionService {
+    private String m_actionName;
 
-      super(clusterName, serviceName);
-      m_clusterId = clusterName;
-      m_serviceId = serviceName;
-      m_actionId  = actionName;
+    private TestActionDefinitionService(String actionName) {
+      m_actionName = actionName;
     }
 
     @Override
-    ResourceInstance createActionResource(String clusterName, String serviceName, String actionName) {
-      assertEquals(m_clusterId, clusterName);
-      assertEquals(m_serviceId, serviceName);
-      assertEquals(m_actionId, actionName);
+    ResourceInstance createActionDefinitionResource(String actionName) {
+      assertEquals(m_actionName, actionName);
       return getTestResource();
     }
 

+ 53 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/ActionRequestTest.java

@@ -0,0 +1,53 @@
+/**
+ * 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;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+public class ActionRequestTest {
+
+  @Test
+  public void testBasicGetAndSet() {
+    ActionRequest adr1 =
+        new ActionRequest("a1", "SYSTEM", "fileName", "HDFS", "DATANODE", "Desc1", "Any", "100");
+
+    Assert.assertEquals("a1", adr1.getActionName());
+    Assert.assertEquals("SYSTEM", adr1.getActionType());
+    Assert.assertEquals("fileName", adr1.getInputs());
+    Assert.assertEquals("HDFS", adr1.getTargetService());
+    Assert.assertEquals("DATANODE", adr1.getTargetComponent());
+    Assert.assertEquals("Desc1", adr1.getDescription());
+    Assert.assertEquals("Any", adr1.getTargetType());
+    Assert.assertEquals("100", adr1.getDefaultTimeout());
+
+    adr1.setDescription("Desc2");
+    adr1.setActionType("USER");
+
+    Assert.assertEquals("Desc2", adr1.getDescription());
+    Assert.assertEquals("USER", adr1.getActionType());
+  }
+
+  @Test
+  public void testToString() {
+    ActionRequest r1 = ActionRequest.getAllRequest();
+    r1.toString();
+  }
+
+}

+ 46 - 0
ambari-server/src/test/java/org/apache/ambari/server/controller/ActionResponseTest.java

@@ -0,0 +1,46 @@
+package org.apache.ambari.server.controller;
+
+/**
+ * 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 org.junit.Assert;
+import org.junit.Test;
+
+public class ActionResponseTest {
+
+  @Test
+  public void testBasicGetAndSet() {
+    ActionResponse r1 =
+        new ActionResponse("a1", "SYSTEM", "fileName", "HDFS", "DATANODE", "Desc1", "Any", "100");
+    
+    Assert.assertEquals("a1", r1.getActionName());
+    Assert.assertEquals("SYSTEM", r1.getActionType());
+    Assert.assertEquals("fileName", r1.getInputs());
+    Assert.assertEquals("HDFS", r1.getTargetService());
+    Assert.assertEquals("DATANODE", r1.getTargetComponent());
+    Assert.assertEquals("Desc1", r1.getDescription());
+    Assert.assertEquals("Any", r1.getTargetType());
+    Assert.assertEquals("100", r1.getDefaultTimeout());
+  }
+
+  @Test
+  public void testToString() {
+    ActionResponse r = new ActionResponse(null, null, null, null, null, null, null, null);
+    r.toString();
+  }
+}

+ 220 - 35
ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java

@@ -43,6 +43,7 @@ import org.apache.ambari.server.actionmanager.Stage;
 import org.apache.ambari.server.agent.ExecutionCommand;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.configuration.Configuration;
+import org.apache.ambari.server.controller.internal.ActionResourceProviderTest;
 import org.apache.ambari.server.controller.internal.ComponentResourceProviderTest;
 import org.apache.ambari.server.controller.internal.HostResourceProviderTest;
 import org.apache.ambari.server.controller.internal.ServiceResourceProviderTest;
@@ -3442,16 +3443,104 @@ public class AmbariManagementControllerTest {
 
   @SuppressWarnings("serial")
   @Test
-  public void testGetActions() throws Exception {
-    Set<ActionResponse> responses = controller.getActions(
-        new HashSet<ActionRequest>() {{
-          add(new ActionRequest(null, "HDFS", null, null));
-    }});
+  public void testCreateActionsFailures() throws Exception {
+    clusters.addCluster("c1");
+    clusters.getCluster("c1").setDesiredStackVersion(new StackId("HDP-0.1"));
+    clusters.addHost("h1");
+    clusters.getHost("h1").setOsType("centos5");
+    clusters.getHost("h1").persist();
+    Set<String> hostNames = new HashSet<String>(){{
+      add("h1");
+    }};
+
+    clusters.mapHostsToCluster(hostNames, "c1");
+
+    Cluster cluster = clusters.getCluster("c1");
+    cluster.setDesiredStackVersion(new StackId("HDP-0.1"));
+    cluster.setCurrentStackVersion(new StackId("HDP-0.1"));
+
+    ConfigFactory cf = injector.getInstance(ConfigFactory.class);
+    Config config1 = cf.createNew(cluster, "global",
+        new HashMap<String, String>(){{ put("key1", "value1"); }});
+    config1.setVersionTag("version1");
+
+    Config config2 = cf.createNew(cluster, "core-site",
+        new HashMap<String, String>(){{ put("key1", "value1"); }});
+    config2.setVersionTag("version1");
+
+    cluster.addConfig(config1);
+    cluster.addConfig(config2);
+    cluster.addDesiredConfig("_test", config1);
+    cluster.addDesiredConfig("_test", config2);
+
+    Service hdfs = cluster.addService("HDFS");
+    hdfs.persist();
+
+    hdfs.addServiceComponent(Role.HDFS_CLIENT.name()).persist();
+    hdfs.addServiceComponent(Role.NAMENODE.name()).persist();
+    hdfs.addServiceComponent(Role.DATANODE.name()).persist();
+
+    hdfs.getServiceComponent(Role.HDFS_CLIENT.name()).addServiceComponentHost("h1").persist();
+    hdfs.getServiceComponent(Role.NAMENODE.name()).addServiceComponentHost("h1").persist();
+    hdfs.getServiceComponent(Role.DATANODE.name()).addServiceComponentHost("h1").persist();
+
+    Map<String, String> params = new HashMap<String, String>() {{
+      put("test", "test");
+    }};
+    ExecuteActionRequest actionRequest = new ExecuteActionRequest("c1", "NON_EXISTENT_CHECK", "HDFS", params);
+
+    Map<String, String> requestProperties = new HashMap<String, String>();
+    requestProperties.put(REQUEST_CONTEXT_PROPERTY, "Called from a test");
+
+    try {
+      controller.createAction(actionRequest, requestProperties);
+      Assert.fail("createAction should fail for NON_EXISTENT_CHECK");
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Unsupported action"));
+    }
+
+    actionRequest = new ExecuteActionRequest("c1", "NON_EXISTENT_SERVICE_CHECK", "HDFS", params);
+    try {
+      controller.createAction(actionRequest, requestProperties);
+      Assert.fail("createAction should fail for NON_EXISTENT_SERVICE_CHECK");
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Unsupported action"));
+    }
 
-    assertFalse(responses.isEmpty());
-    assertEquals(1, responses.size());
-    ActionResponse response = responses.iterator().next();
-    assertEquals(Role.HDFS_SERVICE_CHECK.name(), response.getActionName());
+    actionRequest = new ExecuteActionRequest("c1", "DECOMMISSION_DATANODE", "MAPREDUCE", params);
+    try {
+      controller.createAction(actionRequest, requestProperties);
+      Assert.fail("createAction should fail for DECOMMISSION_DATANODE on MAPREDUCE");
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Unsupported action DECOMMISSION_DATANODE for MAPREDUCE"));
+    }
+
+    actionRequest = new ExecuteActionRequest("c1", "DECOMMISSION_DATANODE", "HDFS", params);
+    try {
+      controller.createAction(actionRequest, requestProperties);
+      Assert.fail("createAction should fail for DECOMMISSION_DATANODE on HDFS - no excludeFileTag");
+    } catch (IllegalArgumentException ex) {
+      Assert.assertTrue(ex.getMessage().contains("No exclude file specified when decommissioning datanodes"));
+    }
+
+    params.put("excludeFileTag", "tag1");
+    actionRequest = new ExecuteActionRequest("c1", "DECOMMISSION_DATANODE", "HDFS", params);
+    try {
+      controller.createAction(actionRequest, requestProperties);
+      Assert.fail("createAction should fail for DECOMMISSION_DATANODE on HDFS - no config type hdfs-exclude-file");
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Decommissioning datanodes requires the cluster"));
+    }
+
+    actionRequest = new ExecuteActionRequest("c1", null, "DECOMMISSION_DATANODE", "HDFS", null, null, params);
+    try {
+      RequestStatusResponse response = controller.createAction(actionRequest, requestProperties);
+      if (response != null) {
+        Assert.fail("createAction should fail for action DECOMMISSION_DATANODE");
+      }
+    } catch (AmbariException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Invalid action request"));
+    }
   }
 
   @SuppressWarnings("serial")
@@ -3501,17 +3590,15 @@ public class AmbariManagementControllerTest {
     hdfs.getServiceComponent(Role.HDFS_CLIENT.name()).addServiceComponentHost("h1").persist();
     mapReduce.getServiceComponent(Role.MAPREDUCE_CLIENT.name()).addServiceComponentHost("h2").persist();
 
-    Set<ActionRequest> actionRequests = new HashSet<ActionRequest>();
-    Map<String, String> params = new HashMap<String, String>(){{
+    Map<String, String> params = new HashMap<String, String>() {{
       put("test", "test");
     }};
-    ActionRequest actionRequest = new ActionRequest("c1", "HDFS", Role.HDFS_SERVICE_CHECK.name(), params);
-    actionRequests.add(actionRequest);
-    
+    ExecuteActionRequest actionRequest = new ExecuteActionRequest("c1", Role.HDFS_SERVICE_CHECK.name(), "HDFS", params);
+
     Map<String, String> requestProperties = new HashMap<String, String>();
     requestProperties.put(REQUEST_CONTEXT_PROPERTY, "Called from a test");
 
-    RequestStatusResponse response = controller.createActions(actionRequests, requestProperties);
+    RequestStatusResponse response = controller.createAction(actionRequest, requestProperties);
     
     assertEquals(1, response.getTasks().size());
     ShortTaskStatus task = response.getTasks().get(0);
@@ -3534,26 +3621,26 @@ public class AmbariManagementControllerTest {
     assertEquals(task.getTaskId(), hostRoleCommand.getTaskId());
     assertEquals(actionRequest.getServiceName(), hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getServiceName());
     assertEquals(actionRequest.getClusterName(), hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getClusterName());
-    assertEquals(actionRequest.getActionName(), hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getRole());
+    assertEquals(actionRequest.getCommandName(), hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getRole());
     assertEquals(Role.HDFS_CLIENT.name(), hostRoleCommand.getEvent().getEvent().getServiceComponentName());
     assertEquals(actionRequest.getParameters(), hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getRoleParams());
     assertNotNull(hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getConfigurations());
     assertEquals(2, hostRoleCommand.getExecutionCommandWrapper().getExecutionCommand().getConfigurations().size());
     assertEquals(requestProperties.get(REQUEST_CONTEXT_PROPERTY), stage.getRequestContext());
-    actionRequests.add(new ActionRequest("c1", "MAPREDUCE", Role.MAPREDUCE_SERVICE_CHECK.name(), null));
+    actionRequest = new ExecuteActionRequest("c1", Role.MAPREDUCE_SERVICE_CHECK.name(), "MAPREDUCE", null);
 
-    response = controller.createActions(actionRequests, requestProperties);
+    response = controller.createAction(actionRequest, requestProperties);
 
-    assertEquals(2, response.getTasks().size());
+    assertEquals(1, response.getTasks().size());
 
     List<HostRoleCommand> tasks = actionDB.getRequestTasks(response.getRequestId());
 
-    assertEquals(2, tasks.size());
+    assertEquals(1, tasks.size());
 
     requestProperties.put(REQUEST_CONTEXT_PROPERTY, null);
-    response = controller.createActions(actionRequests, requestProperties);
+    response = controller.createAction(actionRequest, requestProperties);
 
-    assertEquals(2, response.getTasks().size());
+    assertEquals(1, response.getTasks().size());
   }
 
   private void createUser(String userName) throws Exception {
@@ -5038,12 +5125,11 @@ public class AmbariManagementControllerTest {
     }
     Assert.assertEquals("Expect only one service check.", 1, commandCount);
 
-    Set<ActionRequest> actionRequests = new HashSet<ActionRequest>();
-    ActionRequest actionRequest = new ActionRequest("foo1", "HDFS", Role.HDFS_SERVICE_CHECK.name(), null);
-    actionRequests.add(actionRequest);
+    ExecuteActionRequest actionRequest = new ExecuteActionRequest("foo1", Role.HDFS_SERVICE_CHECK.name(),
+        null, "HDFS", null, null, null);
     Map<String, String> requestProperties = new HashMap<String, String>();
 
-    RequestStatusResponse response = controller.createActions(actionRequests, requestProperties);
+    RequestStatusResponse response = controller.createAction(actionRequest, requestProperties);
     commands = actionDB.getRequestTasks(response.getRequestId());
     commandCount = 0;
     for(HostRoleCommand command : commands) {
@@ -5058,7 +5144,7 @@ public class AmbariManagementControllerTest {
     // When both are unhealthy then just pick one
     clusters.getHost("h3").setState(HostState.WAITING_FOR_HOST_STATUS_UPDATES);
     clusters.getHost("h2").setState(HostState.INIT);
-    response = controller.createActions(actionRequests, requestProperties);
+    response = controller.createAction(actionRequest, requestProperties);
     commands = actionDB.getRequestTasks(response.getRequestId());
     commandCount = 0;
     for(HostRoleCommand command : commands) {
@@ -5436,19 +5522,16 @@ public class AmbariManagementControllerTest {
     Service s = cluster.getService(serviceName);
     Assert.assertEquals(State.STARTED, s.getDesiredState());
 
-    Set<ActionRequest> requests = new HashSet<ActionRequest>();
     Map<String, String> params = new HashMap<String, String>(){{
       put("test", "test");
+      put("excludeFileTag", "tag1");
     }};
-    ActionRequest request = new ActionRequest(clusterName, "HDFS",
-      Role.DECOMMISSION_DATANODE.name(), params);
-    params.put("excludeFileTag", "tag1");
-    requests.add(request);
+    ExecuteActionRequest request = new ExecuteActionRequest(clusterName, Role.DECOMMISSION_DATANODE.name(), "HDFS", params);
 
     Map<String, String> requestProperties = new HashMap<String, String>();
     requestProperties.put(REQUEST_CONTEXT_PROPERTY, "Called from a test");
 
-    RequestStatusResponse response = controller.createActions(requests,
+    RequestStatusResponse response = controller.createAction(request,
       requestProperties);
 
     List<HostRoleCommand> storedTasks = actionDB.getRequestTasks(response.getRequestId());
@@ -7488,8 +7571,8 @@ public class AmbariManagementControllerTest {
 
       amc.createHostComponents(componentHostRequests);
 
-      ActionRequest ar = new ActionRequest(CLUSTER_NAME, "HDFS", Role.HDFS_SERVICE_CHECK.name(), new HashMap<String, String>());
-      amc.createActions(Collections.singleton(ar), null);
+      ExecuteActionRequest ar = new ExecuteActionRequest(CLUSTER_NAME, Role.HDFS_SERVICE_CHECK.name(), "HDFS", null);
+      amc.createAction(ar, null);
 
       // change mind, delete the cluster
       amc.deleteCluster(cr);
@@ -7805,6 +7888,108 @@ public class AmbariManagementControllerTest {
     }
   }
 
+ @Test
+ public void testActionDefinitionLifeCycle() throws Exception {
+   ActionRequest createRequest1 =
+       new ActionRequest("a1", "SYSTEM", "fileName", "HDFS", "DATANODE", "Does file exist", "ANY", "100");
+   ActionResourceProviderTest.createAction(controller, createRequest1);
+
+   ActionRequest getAllRequest = ActionRequest.getAllRequest();
+   Set<ActionResponse> responses = ActionResourceProviderTest.getActions(controller,
+       Collections.singleton(getAllRequest));
+   Assert.assertEquals(1, responses.size());
+
+   ActionRequest createRequest2 =
+       new ActionRequest("a2", "USER", "fileName", "YARN", "NODEMANAGER", "Does file exist", "ANY", "100");
+   ActionResourceProviderTest.createAction(controller, createRequest2);
+
+   responses = ActionResourceProviderTest.getActions(controller, Collections.singleton
+       (getAllRequest));
+   Assert.assertEquals(2, responses.size());
+
+   ActionRequest updateRequest =
+       new ActionRequest("a2", "USER", null, null, null, "Does file really exist", "ANY", "100");
+   RequestStatusResponse response = ActionResourceProviderTest.updateAction(controller,
+       Collections.singleton(updateRequest), null);
+   Assert.assertNull(response);
+
+   ActionRequest getOneRequest =
+       new ActionRequest("a2", null, null, null, null, null, null, null);
+   responses = ActionResourceProviderTest.getActions(controller, Collections.singleton
+       (getOneRequest));
+   Assert.assertEquals(1, responses.size());
+   Assert.assertEquals("Does file really exist", responses.iterator().next().getDescription());
+
+   ActionResourceProviderTest.deleteAction(controller, getOneRequest);
+   responses = ActionResourceProviderTest.getActions(controller, Collections.singleton
+       (getOneRequest));
+   Assert.assertEquals(0, responses.size());
+ }
+
+  @Test
+  public void testActionDefinitionErrors() throws Exception {
+    ActionRequest createRequest =
+        new ActionRequest(null, "SYSTEM", "fileName", "HDFS", "DATANODE", "Does file exist", "ANY", "100");
+    try {
+      ActionResourceProviderTest.createAction(controller, createRequest);
+      Assert.fail("Exception must be thrown");
+    } catch (Exception ex) {
+      LOG.info(ex.getMessage());
+      Assert.assertTrue(ex.getMessage().contains("Action name should be provided"));
+    }
+
+    createRequest =
+        new ActionRequest("a1", "SYSTEM", "fileName", "HDFS", "DATANODE", "Does file exist", "ANY", "10000");
+    try {
+      ActionResourceProviderTest.createAction(controller, createRequest);
+      Assert.fail("Exception must be thrown");
+    } catch (Exception ex) {
+      LOG.info(ex.getMessage());
+      Assert.assertTrue(ex.getMessage().contains("Default timeout should be between 60 and 600"));
+    }
+
+    createRequest =
+        new ActionRequest("a1", "Favorite", "", "HDFS", "", "Does file exist", "ANY", "100");
+    try {
+      ActionResourceProviderTest.createAction(controller, createRequest);
+      Assert.fail("Exception must be thrown");
+    } catch (Exception ex) {
+      LOG.info(ex.getMessage());
+      Assert.assertTrue(ex.getMessage().contains("No enum const class"));
+    }
+
+    createRequest =
+        new ActionRequest("a1", "SYSTEM", "", "HDFS", "", "", "ANY", "100");
+    try {
+      ActionResourceProviderTest.createAction(controller, createRequest);
+      Assert.fail("Exception must be thrown");
+    } catch (Exception ex) {
+      LOG.info(ex.getMessage());
+      Assert.assertTrue(ex.getMessage().contains("Action description cannot be empty"));
+    }
+
+    createRequest =
+        new ActionRequest("a1", "SYSTEM", "", "HDFS", "", "SS", "ANY", "100");
+    try {
+      ActionResourceProviderTest.createAction(controller, createRequest);
+      ActionResourceProviderTest.createAction(controller, createRequest);
+      Assert.fail("Exception must be thrown");
+    } catch (Exception ex) {
+      LOG.info(ex.getMessage());
+      Assert.assertTrue(ex.getMessage().contains("Action definition a1 already exists"));
+    }
+
+    createRequest =
+        new ActionRequest("a1", "SYSTEM", "", "HDFS", "", "SS", "Any", "100");
+    try {
+      ActionResourceProviderTest.createAction(controller, createRequest);
+      Assert.fail("Exception must be thrown");
+    } catch (Exception ex) {
+      LOG.info(ex.getMessage());
+      Assert.assertTrue(ex.getMessage().contains("No enum const class"));
+    }
+  }
+
   @Test
   public void testScheduleSmokeTest() throws Exception {
 

+ 0 - 45
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/AbstractResourceProviderTest.java

@@ -202,13 +202,6 @@ public class AbstractResourceProviderTest {
       return null;
     }
 
-    public static Set<ActionRequest> getActionRequestSet(String clusterName, String serviceName, String actionName)
-    {
-      EasyMock.reportMatcher(new ActionRequestSetMatcher(clusterName, serviceName, actionName));
-      return null;
-    }
-
-
     public static Set<HostRequest> getHostRequestSet(String hostname, String clusterName,
                                                      Map<String, String> hostAttributes)
     {
@@ -329,44 +322,6 @@ public class AbstractResourceProviderTest {
     }
   }
 
-  /**
-   * Matcher for a ActionRequest set containing a single request.
-   */
-  public static class ActionRequestSetMatcher extends HashSet<ActionRequest> implements IArgumentMatcher {
-
-    private final ActionRequest actionRequest;
-
-    public ActionRequestSetMatcher(String clusterName, String serviceName, String actionName) {
-      this.actionRequest = new ActionRequest(clusterName, serviceName, actionName, null);
-      add(this.actionRequest);
-    }
-
-    @Override
-    public boolean matches(Object o) {
-      if (!(o instanceof Set)) {
-        return false;
-      }
-
-      Set set = (Set) o;
-
-      if (set.size() != 1) {
-        return false;
-      }
-
-      Object request = set.iterator().next();
-
-      return request instanceof ActionRequest &&
-          eq(((ActionRequest) request).getClusterName(), actionRequest.getClusterName()) &&
-          eq(((ActionRequest) request).getServiceName(), actionRequest.getServiceName()) &&
-          eq(((ActionRequest) request).getActionName(), actionRequest.getActionName());
-    }
-
-    @Override
-    public void appendTo(StringBuffer stringBuffer) {
-      stringBuffer.append("ActionRequestSetMatcher(").append(actionRequest).append(")");
-    }
-  }
-
 
   /**
    * Matcher for a HostRequest set containing a single request.

+ 210 - 111
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ActionResourceProviderTest.java

@@ -16,8 +16,19 @@
  * limitations under the License.
  */
 
+
 package org.apache.ambari.server.controller.internal;
 
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.actionmanager.ActionDefinition;
+import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.actionmanager.ActionType;
+import org.apache.ambari.server.actionmanager.TargetHostType;
+import org.apache.ambari.server.controller.ActionResponse;
+import org.apache.ambari.server.controller.ActionRequest;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.RequestStatusResponse;
 import org.apache.ambari.server.controller.spi.Predicate;
@@ -26,43 +37,79 @@ import org.apache.ambari.server.controller.spi.Resource;
 import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.junit.After;
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 
-import java.util.Collections;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
 import static org.easymock.EasyMock.createMock;
 import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
 import static org.easymock.EasyMock.replay;
 import static org.easymock.EasyMock.verify;
-import static org.easymock.EasyMock.eq;
 
-/**
- * ActionResourceProvider tests.
- */
 public class ActionResourceProviderTest {
+
+  private Injector injector;
+
+  @Before
+  public void setup() throws Exception {
+    InMemoryDefaultTestModule module = new InMemoryDefaultTestModule();
+    injector = Guice.createInjector(module);
+    injector.getInstance(GuiceJpaInitializer.class);
+  }
+
+  @After
+  public void teardown() {
+    injector.getInstance(PersistService.class).stop();
+  }
+
   @Test
-  public void testCreateResources() throws Exception {
+  public void testGetResources() throws Exception {
     Resource.Type type = Resource.Type.Action;
+    ActionManager am = createNiceMock(ActionManager.class);
+    AmbariManagementController managementController = createNiceMock(AmbariManagementController.class);
+    expect(managementController.getActionManager()).andReturn(am).anyTimes();
+
+    List<ActionDefinition> allDefinition = new ArrayList<ActionDefinition>();
+    allDefinition.add(new ActionDefinition(
+        "a1", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
+        Short.valueOf("100")));
+    allDefinition.add(new ActionDefinition(
+        "a2", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
+        Short.valueOf("100")));
+    allDefinition.add(new ActionDefinition(
+        "a3", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
+        Short.valueOf("100")));
+
+    Set<ActionResponse> allResponse = new HashSet<ActionResponse>();
+    for (ActionDefinition definition : allDefinition) {
+      allResponse.add(new ActionResponse(definition));
+    }
 
-    AmbariManagementController managementController = createMock(AmbariManagementController.class);
-    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
-    
-    Map<String, String> requestProperties = new HashMap<String, String>();
-    requestProperties.put("context", "Called from a test");
+    ActionDefinition namedDefinition = new ActionDefinition(
+        "a1", ActionType.SYSTEM, "fileName", "HDFS", "DATANODE", "Does file exist", TargetHostType.ANY,
+        Short.valueOf("100"));
 
-    expect(managementController.createActions(AbstractResourceProviderTest.Matcher.getActionRequestSet(
-        "Cluster100", "Service100", "Action100"), eq(requestProperties))).andReturn(response);
+    Set<ActionResponse> nameResponse = new HashSet<ActionResponse>();
+    nameResponse.add(new ActionResponse(namedDefinition));
 
-    // replay
-    replay(managementController, response);
+    expect(am.getAllActionDefinition()).andReturn(allDefinition).once();
+    expect(am.getActionDefinition("a1")).andReturn(namedDefinition).once();
+
+    replay(managementController, am);
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
         type,
@@ -70,172 +117,224 @@ public class ActionResourceProviderTest {
         PropertyHelper.getKeyPropertyIds(type),
         managementController);
 
-    // add the property map to a set for the request.  add more maps for multiple creates
-    Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>();
-
-    // Service 1: create a map of properties for the request
-    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+    Set<String> propertyIds = new HashSet<String>();
 
-    // add properties to the request map
-    properties.put(ActionResourceProvider.ACTION_CLUSTER_NAME_PROPERTY_ID, "Cluster100");
-    properties.put(ActionResourceProvider.ACTION_SERVICE_NAME_PROPERTY_ID, "Service100");
-    properties.put(ActionResourceProvider.ACTION_ACTION_NAME_PROPERTY_ID, "Action100");
+    propertyIds.add(ActionResourceProvider.ACTION_NAME_PROPERTY_ID);
+    propertyIds.add(ActionResourceProvider.ACTION_TYPE_PROPERTY_ID);
+    propertyIds.add(ActionResourceProvider.DEFAULT_TIMEOUT_PROPERTY_ID);
+    propertyIds.add(ActionResourceProvider.DESCRIPTION_PROPERTY_ID);
+    propertyIds.add(ActionResourceProvider.INPUTS_PROPERTY_ID);
+    propertyIds.add(ActionResourceProvider.TARGET_COMPONENT_PROPERTY_ID);
+    propertyIds.add(ActionResourceProvider.TARGET_HOST_PROPERTY_ID);
+    propertyIds.add(ActionResourceProvider.TARGET_SERVICE_PROPERTY_ID);
 
-    propertySet.add(properties);
 
     // create the request
-    Request request = PropertyHelper.getCreateRequest(propertySet, requestProperties);
+    Request request = PropertyHelper.getReadRequest(propertyIds);
+
+    // get all ... no predicate
+    Set<Resource> resources = provider.getResources(request, null);
+
+    Assert.assertEquals(allResponse.size(), resources.size());
+    for (Resource resource : resources) {
+      String actionName = (String) resource.getPropertyValue(ActionResourceProvider.ACTION_NAME_PROPERTY_ID);
+      String actionType = (String) resource.getPropertyValue(ActionResourceProvider.ACTION_TYPE_PROPERTY_ID);
+      String defaultTimeout = (String) resource.getPropertyValue(ActionResourceProvider.DEFAULT_TIMEOUT_PROPERTY_ID);
+      String description = (String) resource.getPropertyValue(ActionResourceProvider.DESCRIPTION_PROPERTY_ID);
+      String inputs = (String) resource.getPropertyValue(ActionResourceProvider.INPUTS_PROPERTY_ID);
+      String comp = (String) resource.getPropertyValue(ActionResourceProvider.TARGET_COMPONENT_PROPERTY_ID);
+      String svc = (String) resource.getPropertyValue(ActionResourceProvider.TARGET_SERVICE_PROPERTY_ID);
+      String host = (String) resource.getPropertyValue(ActionResourceProvider.TARGET_HOST_PROPERTY_ID);
+      Assert.assertTrue(allResponse.contains(new ActionResponse(actionName, actionType,
+          inputs, svc, comp, description, host, defaultTimeout)));
+    }
+
+    // get actions named a1
+    Predicate predicate =
+        new PredicateBuilder().property(ActionResourceProvider.ACTION_NAME_PROPERTY_ID).
+            equals("a1").toPredicate();
+    resources = provider.getResources(request, predicate);
+
+    Assert.assertEquals(1, resources.size());
+    Assert.assertEquals("a1", resources.iterator().next().
+        getPropertyValue(ActionResourceProvider.ACTION_NAME_PROPERTY_ID));
 
-    provider.createResources(request);
 
     // verify
-    verify(managementController, response);
+    verify(managementController);
   }
 
   @Test
-  public void testGetResources() throws Exception {
+  public void testCreateResources() throws Exception {
     Resource.Type type = Resource.Type.Action;
 
-    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    AmbariManagementController managementController = createNiceMock(AmbariManagementController.class);
+    ActionManager am = createMock(ActionManager.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+    expect(managementController.getActionManager()).andReturn(am).anyTimes();
 
+    am.createActionDefinition(eq("a1"), eq(ActionType.SYSTEM), eq("fileName"), eq("desc"), eq((String)null),
+        eq((String)null), eq(TargetHostType.ANY), eq(Short.valueOf("60")));
     // replay
-    replay(managementController);
+    replay(managementController, am, response);
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
         type,
         PropertyHelper.getPropertyIds(type),
         PropertyHelper.getKeyPropertyIds(type),
         managementController);
+    ((ActionResourceProvider) provider).setEnableExperimental(true);
 
-    Set<String> propertyIds = new HashSet<String>();
+    AbstractResourceProviderTest.TestObserver observer = new AbstractResourceProviderTest.TestObserver();
+
+    ((ObservableResourceProvider) provider).addObserver(observer);
+
+    // add the property map to a set for the request.  add more maps for multiple creates
+    Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>();
 
-    propertyIds.add(ActionResourceProvider.ACTION_CLUSTER_NAME_PROPERTY_ID);
-    propertyIds.add(ActionResourceProvider.ACTION_SERVICE_NAME_PROPERTY_ID);
-    propertyIds.add(ActionResourceProvider.ACTION_ACTION_NAME_PROPERTY_ID);
+    // Cluster 1: create a map of properties for the request
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(ActionResourceProvider.ACTION_NAME_PROPERTY_ID, "a1");
+    properties.put(ActionResourceProvider.ACTION_TYPE_PROPERTY_ID, "SYSTEM");
+    properties.put(ActionResourceProvider.TARGET_HOST_PROPERTY_ID, "ANY");
+    properties.put(ActionResourceProvider.DESCRIPTION_PROPERTY_ID, "desc");
+    properties.put(ActionResourceProvider.INPUTS_PROPERTY_ID, "fileName");
+
+    propertySet.add(properties);
 
     // create the request
-    Request request = PropertyHelper.getReadRequest(propertyIds);
+    Request request = PropertyHelper.getCreateRequest(propertySet, null);
 
-    // get all ... no predicate
-    try {
-      provider.getResources(request, null);
-      Assert.fail("Expected an UnsupportedOperationException");
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
+    provider.createResources(request);
+
+    ResourceProviderEvent lastEvent = observer.getLastEvent();
+    Assert.assertNotNull(lastEvent);
+    Assert.assertEquals(Resource.Type.Action, lastEvent.getResourceType());
+    Assert.assertEquals(ResourceProviderEvent.Type.Create, lastEvent.getType());
+    Assert.assertEquals(request, lastEvent.getRequest());
+    Assert.assertNull(lastEvent.getPredicate());
 
     // verify
-    verify(managementController);
+    verify(managementController, response);
   }
 
-
   @Test
   public void testUpdateResources() throws Exception {
     Resource.Type type = Resource.Type.Action;
 
-    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    AmbariManagementController managementController = createNiceMock(AmbariManagementController.class);
+    ActionManager am = createMock(ActionManager.class);
     RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+    expect(managementController.getActionManager()).andReturn(am).anyTimes();
+    am.updateActionDefinition(eq("a2"), eq((ActionType) null), eq("Updated description"),
+        eq((TargetHostType) null), eq((Short)null));
+
+    Map<String, String> mapRequestProps = new HashMap<String, String>();
+    mapRequestProps.put("context", "Called from a test");
 
     // replay
-    replay(managementController, response);
+    replay(managementController, response, am);
 
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
         type,
         PropertyHelper.getPropertyIds(type),
         PropertyHelper.getKeyPropertyIds(type),
         managementController);
+    ((ActionResourceProvider) provider).setEnableExperimental(true);
 
-    // add the property map to a set for the request.
     Map<String, Object> properties = new LinkedHashMap<String, Object>();
 
+    properties.put(ActionResourceProvider.DESCRIPTION_PROPERTY_ID, "Updated description");
+
     // create the request
-    Request request = PropertyHelper.getUpdateRequest(properties, null);
+    Request request = PropertyHelper.getUpdateRequest(properties, mapRequestProps);
 
-    Predicate predicate =
-        new PredicateBuilder().property(ActionResourceProvider.ACTION_CLUSTER_NAME_PROPERTY_ID).equals("Cluster100").
-        and().property(ActionResourceProvider.ACTION_SERVICE_NAME_PROPERTY_ID).equals("Service102").
-        and().property(ActionResourceProvider.ACTION_ACTION_NAME_PROPERTY_ID).equals("Action100").toPredicate();
-    try {
-      provider.updateResources(request, predicate);
-      Assert.fail("Expected an UnsupportedOperationException");
-    } catch (UnsupportedOperationException e) {
-      // expected
-    }
+    // update the action named a2
+    Predicate predicate = new PredicateBuilder().property(
+        ActionResourceProvider.ACTION_NAME_PROPERTY_ID).equals("a2").toPredicate();
+    provider.updateResources(request, predicate);
 
     // verify
-    verify(managementController, response);
+    verify(managementController, response, am);
   }
 
   @Test
-  public void testDeleteResources() throws Exception {
+  public void testEnsureLockedOperations() throws Exception {
     Resource.Type type = Resource.Type.Action;
 
     AmbariManagementController managementController = createMock(AmbariManagementController.class);
-    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
-
-    // replay
-    replay(managementController, response);
-
     ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
         type,
         PropertyHelper.getPropertyIds(type),
         PropertyHelper.getKeyPropertyIds(type),
         managementController);
 
-    Predicate  predicate =
-        new PredicateBuilder().property(ActionResourceProvider.ACTION_ACTION_NAME_PROPERTY_ID).equals("Action100").
-            toPredicate();
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+    Map<String, String> mapRequestProps = new HashMap<String, String>();
+    mapRequestProps.put("context", "Called from a test");
+    // create the request
+    Request request = PropertyHelper.getUpdateRequest(properties, mapRequestProps);
+
+    Predicate predicate = new PredicateBuilder().property(
+        ActionResourceProvider.ACTION_NAME_PROPERTY_ID).equals("a2").toPredicate();
     try {
-      provider.deleteResources(predicate);
-      Assert.fail("Expected an UnsupportedOperationException");
-    } catch (UnsupportedOperationException e) {
-      // expected
+      provider.updateResources(request, predicate);
+      Assert.fail("Update call must fail.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Not currently supported"));
     }
 
-    // verify
-    verify(managementController, response);
-  }
-
-  @Test
-  public void testCheckPropertyIds() throws Exception {
-    Set<String> propertyIds = new HashSet<String>();
-    propertyIds.add("foo");
-    propertyIds.add("cat1/foo");
-    propertyIds.add("cat2/bar");
-    propertyIds.add("cat2/baz");
-    propertyIds.add("cat3/sub1/bam");
-    propertyIds.add("cat4/sub2/sub3/bat");
-    propertyIds.add("cat5/subcat5/map");
-
-    Map<Resource.Type, String> keyPropertyIds = new HashMap<Resource.Type, String>();
-
-    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    try {
+      provider.createResources(request);
+      Assert.fail("Create call must fail.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Not currently supported"));
+    }
 
-    AbstractResourceProvider provider =
-        (AbstractResourceProvider) AbstractControllerResourceProvider.getResourceProvider(
-            Resource.Type.Action,
-            propertyIds,
-            keyPropertyIds,
-            managementController);
+    try {
+      provider.deleteResources(predicate);
+      Assert.fail("Delete call must fail.");
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Not currently supported"));
+    }
+  }
 
-    Set<String> unsupported = provider.checkPropertyIds(Collections.singleton("foo"));
-    Assert.assertTrue(unsupported.isEmpty());
+  public static ActionResourceProvider getActionDefinitionResourceProvider(
+      AmbariManagementController managementController) {
+    Resource.Type type = Resource.Type.Action;
 
-    // note that key is not in the set of known property ids.  We allow it if its parent is a known property.
-    // this allows for Map type properties where we want to treat the entries as individual properties
-    Assert.assertTrue(provider.checkPropertyIds(Collections.singleton("cat5/subcat5/map/key")).isEmpty());
+    return (ActionResourceProvider) AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+  }
 
-    unsupported = provider.checkPropertyIds(Collections.singleton("bar"));
-    Assert.assertEquals(1, unsupported.size());
-    Assert.assertTrue(unsupported.contains("bar"));
+  public static void createAction(AmbariManagementController controller, ActionRequest request)
+      throws AmbariException {
+    ActionResourceProvider provider = getActionDefinitionResourceProvider(controller);
+    provider.createActionDefinition(request);
+  }
 
-    unsupported = provider.checkPropertyIds(Collections.singleton("cat1/foo"));
-    Assert.assertTrue(unsupported.isEmpty());
+  public static Set<ActionResponse> getActions(AmbariManagementController controller,
+                                                         Set<ActionRequest> requests)
+      throws AmbariException {
+    ActionResourceProvider provider = getActionDefinitionResourceProvider(controller);
+    return provider.getActionDefinitions(requests);
+  }
 
-    unsupported = provider.checkPropertyIds(Collections.singleton("cat1"));
-    Assert.assertTrue(unsupported.isEmpty());
+  public static RequestStatusResponse updateAction(AmbariManagementController controller,
+                                                   Set<ActionRequest> requests,
+                                                   Map<String, String> requestProperties)
+      throws AmbariException {
+    ActionResourceProvider provider = getActionDefinitionResourceProvider(controller);
+    return provider.updateActionDefinitions(requests, requestProperties);
+  }
 
-    unsupported = provider.checkPropertyIds(Collections.singleton("parameters/unknown_property"));
-    Assert.assertTrue(unsupported.isEmpty());
+  public static void deleteAction(AmbariManagementController controller, ActionRequest request)
+      throws AmbariException {
+    ActionResourceProvider provider = getActionDefinitionResourceProvider(controller);
+    provider.deleteActionDefinition(request);
   }
 }

+ 7 - 2
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestImplTest.java

@@ -149,9 +149,14 @@ public class RequestImplTest {
 
     //Action resource properties
     Assert.assertFalse(validPropertyIds.contains("Action/unsupported_property_id"));
-    Assert.assertTrue(validPropertyIds.contains("Actions/cluster_name"));
-    Assert.assertTrue(validPropertyIds.contains("Actions/service_name"));
     Assert.assertTrue(validPropertyIds.contains("Actions/action_name"));
+    Assert.assertTrue(validPropertyIds.contains("Actions/action_type"));
+    Assert.assertTrue(validPropertyIds.contains("Actions/inputs"));
+    Assert.assertTrue(validPropertyIds.contains("Actions/target_service"));
+    Assert.assertTrue(validPropertyIds.contains("Actions/target_component"));
+    Assert.assertTrue(validPropertyIds.contains("Actions/description"));
+    Assert.assertTrue(validPropertyIds.contains("Actions/target_type"));
+    Assert.assertTrue(validPropertyIds.contains("Actions/default_timeout"));
 
     request = PropertyHelper.getReadRequest(PropertyHelper.getPropertyIds(Resource.Type.Request));
     validPropertyIds = request.getPropertyIds();

+ 134 - 8
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/RequestResourceProviderTest.java

@@ -21,6 +21,7 @@ package org.apache.ambari.server.controller.internal;
 import org.apache.ambari.server.actionmanager.ActionManager;
 import org.apache.ambari.server.actionmanager.HostRoleCommand;
 import org.apache.ambari.server.actionmanager.HostRoleStatus;
+import org.apache.ambari.server.controller.ExecuteActionRequest;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.RequestStatusResponse;
 import org.apache.ambari.server.controller.spi.Predicate;
@@ -108,7 +109,7 @@ public class RequestResourceProviderTest {
     Capture<Collection<Long>> requestIdsCapture = new Capture<Collection<Long>>();
     Capture<List<Long>> requestIdListCapture = new Capture<List<Long>>();
 
-    Map<Long, String> requestContexts  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts = new HashMap<Long, String>();
     requestContexts.put(100L, "this is a context");
 
     // set expectations
@@ -161,7 +162,7 @@ public class RequestResourceProviderTest {
     Capture<Collection<Long>> requestIdsCapture = new Capture<Collection<Long>>();
     Capture<List<Long>> requestIdListCapture = new Capture<List<Long>>();
 
-    Map<Long, String> requestContexts  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts = new HashMap<Long, String>();
     requestContexts.put(100L, "this is a context");
 
     // set expectations
@@ -223,10 +224,10 @@ public class RequestResourceProviderTest {
     Capture<Collection<Long>> requestIdsCapture = new Capture<Collection<Long>>();
     Capture<List<Long>> requestIdListCapture = new Capture<List<Long>>();
 
-    Map<Long, String> requestContexts0  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts0 = new HashMap<Long, String>();
     requestContexts0.put(100L, "this is a context");
 
-    Map<Long, String> requestContexts1  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts1 = new HashMap<Long, String>();
     requestContexts1.put(101L, "this is a context");
 
     // set expectations
@@ -305,10 +306,10 @@ public class RequestResourceProviderTest {
     Capture<Collection<Long>> requestIdsCapture = new Capture<Collection<Long>>();
     Capture<List<Long>> requestIdListCapture = new Capture<List<Long>>();
 
-    Map<Long, String> requestContexts0  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts0 = new HashMap<Long, String>();
     requestContexts0.put(100L, "this is a context");
 
-    Map<Long, String> requestContexts1  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts1 = new HashMap<Long, String>();
     requestContexts1.put(101L, "this is a context");
 
     // set expectations
@@ -396,10 +397,10 @@ public class RequestResourceProviderTest {
     Capture<Collection<Long>> requestIdsCapture = new Capture<Collection<Long>>();
     Capture<List<Long>> requestIdListCapture = new Capture<List<Long>>();
 
-    Map<Long, String> requestContexts0  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts0 = new HashMap<Long, String>();
     requestContexts0.put(100L, "this is a context");
 
-    Map<Long, String> requestContexts1  = new HashMap<Long, String>();
+    Map<Long, String> requestContexts1 = new HashMap<Long, String>();
     requestContexts1.put(101L, "this is a context");
 
     // set expectations
@@ -529,4 +530,129 @@ public class RequestResourceProviderTest {
     // verify
     verify(managementController);
   }
+
+  @Test
+  public void testCreateResourcesForCommands() throws Exception {
+    Resource.Type type = Resource.Type.Request;
+
+    Capture<ExecuteActionRequest> actionRequest = new Capture<ExecuteActionRequest>();
+    Capture<HashMap<String, String>> propertyMap = new Capture<HashMap<String, String>>();
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    expect(managementController.createAction(capture(actionRequest), capture(propertyMap)))
+        .andReturn(response).anyTimes();
+
+    // replay
+    replay(managementController);
+
+    // add the property map to a set for the request.  add more maps for multiple creates
+    Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>();
+
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(RequestResourceProvider.REQUEST_CLUSTER_NAME_PROPERTY_ID, "c1");
+
+    propertySet.add(properties);
+
+    Map<String, String> requestInfoProperties = new HashMap<String, String>();
+    requestInfoProperties.put(RequestResourceProvider.SERVICE_NAME_ID, "HDFS");
+    requestInfoProperties.put(RequestResourceProvider.COMMAND_ID, "HDFS_SERVICE_CHECK");
+
+    // create the request
+    Request request = PropertyHelper.getCreateRequest(propertySet, requestInfoProperties);
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+    provider.createResources(request);
+    Assert.assertTrue(actionRequest.hasCaptured());
+    Assert.assertTrue(actionRequest.getValue().isCommand());
+    Assert.assertEquals(null, actionRequest.getValue().getActionName());
+    Assert.assertEquals("HDFS_SERVICE_CHECK", actionRequest.getValue().getCommandName());
+    Assert.assertEquals("HDFS", actionRequest.getValue().getServiceName());
+    Assert.assertEquals(null, actionRequest.getValue().getComponentName());
+    Assert.assertNotNull(actionRequest.getValue().getHosts());
+    Assert.assertEquals(0, actionRequest.getValue().getHosts().size());
+    Assert.assertEquals(0, actionRequest.getValue().getParameters().size());
+  }
+
+  @Test
+  public void testCreateResourcesForCommandsWithParams() throws Exception {
+    Resource.Type type = Resource.Type.Request;
+
+    Capture<ExecuteActionRequest> actionRequest = new Capture<ExecuteActionRequest>();
+    Capture<HashMap<String, String>> propertyMap = new Capture<HashMap<String, String>>();
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+
+    expect(managementController.createAction(capture(actionRequest), capture(propertyMap)))
+        .andReturn(response).anyTimes();
+
+    // replay
+    replay(managementController);
+
+    // add the property map to a set for the request.  add more maps for multiple creates
+    Set<Map<String, Object>> propertySet = new LinkedHashSet<Map<String, Object>>();
+
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+
+    properties.put(RequestResourceProvider.REQUEST_CLUSTER_NAME_PROPERTY_ID, "c1");
+
+    propertySet.add(properties);
+
+    Map<String, String> requestInfoProperties = new HashMap<String, String>();
+    requestInfoProperties.put(RequestResourceProvider.SERVICE_NAME_ID, "HDFS");
+    requestInfoProperties.put("/parameters/param1", "value1");
+    requestInfoProperties.put("/parameters/param2", "value2");
+    requestInfoProperties.put(RequestResourceProvider.HOSTS_ID, "host1 ,host2, host3 ");
+
+    String[] expectedHosts = new String[]{"host1", "host2", "host3"};
+    Map<String, String> expectedParams = new HashMap<String, String>() {{
+      put("param1", "value1");
+      put("param2", "value2");
+    }};
+
+    // create the request
+    Request request = PropertyHelper.getCreateRequest(propertySet, requestInfoProperties);
+    ResourceProvider provider = AbstractControllerResourceProvider.getResourceProvider(
+        type,
+        PropertyHelper.getPropertyIds(type),
+        PropertyHelper.getKeyPropertyIds(type),
+        managementController);
+
+    // Neither action nor commands are specified
+    try {
+      provider.createResources(request);
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Either command or action must be specified"));
+    }
+
+    // Both action and command are specified
+    requestInfoProperties.put(RequestResourceProvider.COMMAND_ID, "HDFS_SERVICE_CHECK");
+    requestInfoProperties.put(RequestResourceProvider.ACTION_ID, "a1");
+    try {
+      provider.createResources(request);
+    } catch (UnsupportedOperationException ex) {
+      Assert.assertTrue(ex.getMessage().contains("Both command and action cannot be specified"));
+    }
+    requestInfoProperties.remove(RequestResourceProvider.ACTION_ID);
+
+    provider.createResources(request);
+    Assert.assertTrue(actionRequest.hasCaptured());
+    Assert.assertTrue(actionRequest.getValue().isCommand());
+    Assert.assertEquals(null, actionRequest.getValue().getActionName());
+    Assert.assertEquals("HDFS_SERVICE_CHECK", actionRequest.getValue().getCommandName());
+    Assert.assertEquals("HDFS", actionRequest.getValue().getServiceName());
+    Assert.assertEquals(null, actionRequest.getValue().getComponentName());
+    Assert.assertEquals(3, actionRequest.getValue().getHosts().size());
+    Assert.assertArrayEquals(expectedHosts, actionRequest.getValue().getHosts().toArray());
+    Assert.assertEquals(2, actionRequest.getValue().getParameters().size());
+    for(String key : expectedParams.keySet()) {
+      Assert.assertEquals(expectedParams.get(key), actionRequest.getValue().getParameters().get(key));
+    }
+  }
 }

+ 137 - 0
ambari-server/src/test/java/org/apache/ambari/server/orm/dao/ActionDefinitionDAOTest.java

@@ -0,0 +1,137 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.dao;
+
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
+import junit.framework.Assert;
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.Role;
+import org.apache.ambari.server.actionmanager.ActionType;
+import org.apache.ambari.server.actionmanager.TargetHostType;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.entities.ActionEntity;
+import org.apache.ambari.server.state.Service;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import javax.persistence.RollbackException;
+import java.util.List;
+
+public class ActionDefinitionDAOTest {
+  private Injector injector;
+  private ActionDefinitionDAO actionDefinitionDAO;
+
+  @Before
+  public void setup() throws Exception {
+    injector = Guice.createInjector(new InMemoryDefaultTestModule());
+    injector.getInstance(GuiceJpaInitializer.class);
+
+    actionDefinitionDAO = injector.getInstance(ActionDefinitionDAO.class);
+  }
+
+  @After
+  public void teardown() throws AmbariException {
+    injector.getInstance(PersistService.class).stop();
+  }
+
+  private ActionEntity createActionDefinition(
+      String actionName, ActionType actionType, String inputs, String targetService, Role targetComponent,
+      Short defaultTimeout, String description, TargetHostType targetType, Boolean addToDAO) throws Exception {
+    ActionEntity actionDefinitionEntity = new ActionEntity();
+
+    actionDefinitionEntity.setActionName(actionName);
+    actionDefinitionEntity.setActionType(actionType);
+    actionDefinitionEntity.setDefaultTimeout(defaultTimeout);
+    actionDefinitionEntity.setDescription(description);
+    actionDefinitionEntity.setInputs(inputs);
+    actionDefinitionEntity.setTargetComponent(targetComponent.toString());
+    actionDefinitionEntity.setTargetService(targetService);
+    actionDefinitionEntity.setTargetType(targetType);
+
+    if (addToDAO) {
+      actionDefinitionDAO.create(actionDefinitionEntity);
+    }
+
+    return actionDefinitionEntity;
+  }
+
+  @Test
+  public void testFindAll() throws Exception {
+    createActionDefinition("a1", ActionType.SYSTEM, "fileName", Service.Type.HDFS.toString(), Role.DATANODE,
+        Short.parseShort("10"), "a1", TargetHostType.ANY, true);
+    createActionDefinition("a2", ActionType.SYSTEM, "fileName", Service.Type.HDFS.toString(), Role.DATANODE,
+        Short.parseShort("10"), "a2", TargetHostType.ANY, true);
+
+    List<ActionEntity> actionDefinitionEntities = actionDefinitionDAO.findAll();
+
+    Assert.assertNotNull(actionDefinitionEntities);
+    Assert.assertEquals(2, actionDefinitionEntities.size());
+  }
+
+  @Test
+  public void testFindByPK() throws Exception {
+    createActionDefinition("c1", ActionType.SYSTEM, "fileName", Service.Type.HDFS.toString(), Role.DATANODE,
+        Short.parseShort("10"), "a1", TargetHostType.ANY, true);
+    createActionDefinition("c2", ActionType.SYSTEM, "fileName", Service.Type.HDFS.toString(), Role.DATANODE,
+        Short.parseShort("10"), "a2", TargetHostType.ANY, true);
+
+    ActionEntity actionDefinitionEntity = actionDefinitionDAO.findByPK("c1");
+
+    Assert.assertNotNull(actionDefinitionEntity);
+    Assert.assertEquals("c1", actionDefinitionEntity.getActionName());
+  }
+
+  @Test
+  public void testDuplicate() throws Exception {
+    createActionDefinition("b1", ActionType.SYSTEM, "fileName", Service.Type.HDFS.toString(), Role.DATANODE,
+        Short.parseShort("10"), "a1", TargetHostType.ANY, true);
+    try {
+      createActionDefinition("b1", ActionType.SYSTEM, "fileName", Service.Type.HDFS.toString(), Role.DATANODE,
+          Short.parseShort("10"), "a1", TargetHostType.ANY, true);
+      Assert.fail("Should throw exception");
+    } catch (RollbackException rbe) {
+      Assert.assertTrue(rbe.getMessage().contains("duplicate"));
+    }
+  }
+
+  @Test
+  public void testUpdate() throws Exception {
+    createActionDefinition("d1", ActionType.SYSTEM, "fileName", Service.Type.HDFS.toString(), Role.DATANODE,
+        Short.parseShort("101"), "a1", TargetHostType.ANY, true);
+    ActionEntity newOne = createActionDefinition("d1", ActionType.SYSTEM, "fileValue", Service.Type.HDFS.toString(),
+        Role.DATANODE, Short.parseShort("101"), "a1", TargetHostType.ANY, false);
+    actionDefinitionDAO.merge(newOne);
+    ActionEntity actionDefinitionEntity = actionDefinitionDAO.findByPK("d1");
+    Assert.assertEquals("fileValue", actionDefinitionEntity.getInputs());
+
+    actionDefinitionDAO.remove(newOne);
+    actionDefinitionEntity = actionDefinitionDAO.findByPK("d1");
+    Assert.assertNull(actionDefinitionEntity);
+  }
+
+  @Test
+  public void testDeleteNonExistent() throws Exception {
+    ActionEntity newOne = createActionDefinition("d1", ActionType.SYSTEM, "fileValue", Service.Type.HDFS.toString(),
+        Role.DATANODE, Short.parseShort("101"), "a1", TargetHostType.ANY, false);
+    actionDefinitionDAO.remove(newOne);
+  }
+}

+ 4 - 3
ambari-web/app/controllers/main/host/details.js

@@ -592,11 +592,12 @@ App.MainHostDetailsController = Em.Controller.extend({
       data: JSON.stringify(configsData),
       timeout: App.timeout,
       success: function(){
-        var actionsUrl = clusterUrl + '/services/HDFS/actions/DECOMMISSION_DATANODE';
+        var actionsUrl = clusterUrl + '/requests';
         var actionsData = {
           RequestInfo: {
-            context: context},
-          Body: {
+            context: context,
+            command: 'DECOMMISSION_DATANODE',
+            service_name: 'HDFS',
             parameters: {
               excludeFileTag: invocationTag
             }

+ 4 - 2
ambari-web/app/utils/ajax.js

@@ -75,14 +75,16 @@ var urls = {
     }
   },
   'service.item.smoke': {
-    'real': '/clusters/{clusterName}/services/{serviceName}/actions/{actionName}',
+    'real': '/clusters/{clusterName}/requests',
     'mock': '/data/wizard/deploy/poll_1.json',
     'format': function (data) {
       return {
         'type': 'POST',
         data: JSON.stringify({
           RequestInfo: {
-            "context": data.displayName + " Smoke Test"
+            "context": data.displayName + " Smoke Test",
+            "command" : data.actionName,
+            "service_name" : data.serviceName
           }
         })
       };