Browse Source

AMBARI-8458. Add support for "add hosts" specifying host name, blueprint name and host group name
AMBARI-8437. Fix regression that prvented cluster creation via blueprints

John Speidel 10 năm trước cách đây
mục cha
commit
058dc168e6
16 tập tin đã thay đổi với 1239 bổ sung588 xóa
  1. 0 18
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
  2. 18 318
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
  3. 30 1
      ambari-server/src/main/java/org/apache/ambari/server/controller/HostRequest.java
  4. 1 1
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractResourceProvider.java
  5. 81 0
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessor.java
  6. 12 1
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java
  7. 2 44
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
  8. 20 11
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigGroupResourceProvider.java
  9. 523 21
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostComponentResourceProvider.java
  10. 250 74
      ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostResourceProvider.java
  11. 3 4
      ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java
  12. 1 1
      ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
  13. 75 70
      ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java
  14. 6 3
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterResourceProviderTest.java
  15. 204 16
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostComponentResourceProviderTest.java
  16. 13 5
      ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostResourceProviderTest.java

+ 0 - 18
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java

@@ -234,24 +234,6 @@ public interface AmbariManagementController {
                                               Map<String, String> requestProperties)
       throws AmbariException;
 
-  /**
-   * Update the host component identified by the given request object with the
-   * values carried by the given request object.
-   *
-   *
-   *
-   * @param requests           the request object which defines which host component to
-   *                           update and the values to set
-   * @param requestProperties  the request properties
-   * @param runSmokeTest       indicates whether or not to run a smoke test
-   *
-   * @return a track action response
-   *
-   * @throws AmbariException thrown if the resource cannot be updated
-   */
-  public RequestStatusResponse updateHostComponents(
-      Set<ServiceComponentHostRequest> requests, Map<String, String> requestProperties, boolean runSmokeTest) throws AmbariException;
-
   /**
    * Updates the users specified.
    *

+ 18 - 318
ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java

@@ -44,7 +44,6 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.EnumMap;
-import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashSet;
@@ -120,11 +119,8 @@ import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.state.State;
 import org.apache.ambari.server.state.configgroup.ConfigGroupFactory;
-import org.apache.ambari.server.state.fsm.InvalidStateTransitionException;
 import org.apache.ambari.server.state.scheduler.RequestExecutionFactory;
-import org.apache.ambari.server.state.svccomphost.ServiceComponentHostDisableEvent;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostInstallEvent;
-import org.apache.ambari.server.state.svccomphost.ServiceComponentHostRestoreEvent;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostStartEvent;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostStopEvent;
 import org.apache.ambari.server.state.svccomphost.ServiceComponentHostUpgradeEvent;
@@ -975,9 +971,8 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
           try {
             if (serviceComponentHostMap == null
                 || !serviceComponentHostMap.containsKey(request.getHostname())) {
-              ServiceComponentHostNotFoundException e = new ServiceComponentHostNotFoundException(cluster.getClusterName(),
+              throw new ServiceComponentHostNotFoundException(cluster.getClusterName(),
                 s.getName(), sc.getName(), request.getHostname());
-              throw e;
             }
 
             ServiceComponentHost sch = serviceComponentHostMap.get(request.getHostname());
@@ -1309,8 +1304,8 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
 
   /**
    * Save cluster update results to retrieve later
-   * @param clusterRequest
-   * @param clusterResponse
+   * @param clusterRequest   cluster request info
+   * @param clusterResponse  cluster response info
    */
   public void saveClusterUpdate(ClusterRequest clusterRequest, ClusterResponse clusterResponse) {
     clusterUpdateCache.put(clusterRequest, clusterResponse);
@@ -1705,7 +1700,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
   private List<ServiceOsSpecific> getOSSpecificsByFamily(Map<String, ServiceOsSpecific> osSpecifics, String osFamily) {
     List<ServiceOsSpecific> foundedOSSpecifics = new ArrayList<ServiceOsSpecific>();
     for (Entry<String, ServiceOsSpecific> osSpecific : osSpecifics.entrySet()) {
-      if (osSpecific.getKey().indexOf(osFamily) != -1) {
+      if (osSpecific.getKey().contains(osFamily)) {
         foundedOSSpecifics.add(osSpecific.getValue());
       }
     }
@@ -1833,8 +1828,8 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
                       nowTimestamp,
                       scHost.getDesiredStackVersion().getStackId());
                 } else if (oldSchState == State.STARTED
-// TODO: oldSchState == State.INSTALLED is always false, looks like a bug
-//                    || oldSchState == State.INSTALLED
+                      // TODO: oldSchState == State.INSTALLED is always false, looks like a bug
+                      //|| oldSchState == State.INSTALLED
                     || oldSchState == State.STOPPING) {
                   roleCommand = RoleCommand.STOP;
                   event = new ServiceComponentHostStopEvent(
@@ -2105,272 +2100,9 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     return requestStages;
   }
 
-  @Override
-  public synchronized RequestStatusResponse updateHostComponents(Set<ServiceComponentHostRequest> requests,
-                                                                 Map<String, String> requestProperties, boolean runSmokeTest)
-                                                                 throws AmbariException {
-
-    if (requests.isEmpty()) {
-      LOG.warn("Received an empty requests set");
-      return null;
-    }
-
-    Map<String, Map<State, List<ServiceComponentHost>>> changedScHosts =
-        new HashMap<String, Map<State, List<ServiceComponentHost>>>();
-    Collection<ServiceComponentHost> ignoredScHosts =
-        new ArrayList<ServiceComponentHost>();
-
-    Set<String> clusterNames = new HashSet<String>();
-    Map<String, Map<String, Map<String, Set<String>>>> hostComponentNames =
-        new HashMap<String, Map<String, Map<String, Set<String>>>>();
-    Set<State> seenNewStates = new HashSet<State>();
-    Map<ServiceComponentHost, State> directTransitionScHosts = new HashMap<ServiceComponentHost, State>();
-
-    // We don't expect batch requests for different clusters, that's why
-    // nothing bad should happen if value is overwritten few times
-    String maintenanceCluster = null;
-
-    // Determine operation level
-    Resource.Type reqOpLvl;
-    if (requestProperties.containsKey(RequestOperationLevel.OPERATION_LEVEL_ID)) {
-      RequestOperationLevel operationLevel = new RequestOperationLevel(requestProperties);
-      reqOpLvl = operationLevel.getLevel();
-    } else {
-      String message = "Can not determine request operation level. " +
-              "Operation level property should " +
-              "be specified for this request.";
-      LOG.warn(message);
-      reqOpLvl = Resource.Type.Cluster;
-    }
-
-    for (ServiceComponentHostRequest request : requests) {
-      validateServiceComponentHostRequest(request);
-
-      Cluster cluster = clusters.getCluster(request.getClusterName());
-
-      if (StringUtils.isEmpty(request.getServiceName())) {
-        request.setServiceName(findServiceName(cluster, request.getComponentName()));
-      }
-
-      LOG.info("Received a updateHostComponent request"
-          + ", clusterName=" + request.getClusterName()
-          + ", serviceName=" + request.getServiceName()
-          + ", componentName=" + request.getComponentName()
-          + ", hostname=" + request.getHostname()
-          + ", request=" + request);
-
-      clusterNames.add(request.getClusterName());
-
-      if (clusterNames.size() > 1) {
-        throw new IllegalArgumentException("Updates to multiple clusters is not"
-            + " supported");
-      }
-
-      if (!hostComponentNames.containsKey(request.getClusterName())) {
-        hostComponentNames.put(request.getClusterName(),
-            new HashMap<String, Map<String, Set<String>>>());
-      }
-      if (!hostComponentNames.get(request.getClusterName())
-          .containsKey(request.getServiceName())) {
-        hostComponentNames.get(request.getClusterName()).put(
-            request.getServiceName(), new HashMap<String, Set<String>>());
-      }
-      if (!hostComponentNames.get(request.getClusterName())
-          .get(request.getServiceName())
-          .containsKey(request.getComponentName())) {
-        hostComponentNames.get(request.getClusterName())
-            .get(request.getServiceName()).put(request.getComponentName(),
-            new HashSet<String>());
-      }
-      if (hostComponentNames.get(request.getClusterName())
-          .get(request.getServiceName()).get(request.getComponentName())
-          .contains(request.getHostname())) {
-        throw new IllegalArgumentException("Invalid request contains duplicate"
-            + " hostcomponents");
-      }
-      hostComponentNames.get(request.getClusterName())
-          .get(request.getServiceName()).get(request.getComponentName())
-          .add(request.getHostname());
-
-      Service s = cluster.getService(request.getServiceName());
-      ServiceComponent sc = s.getServiceComponent(
-          request.getComponentName());
-      ServiceComponentHost sch = sc.getServiceComponentHost(
-          request.getHostname());
-      State oldState = sch.getState();
-      State newState = null;
-      if (request.getDesiredState() != null) {
-        newState = State.valueOf(request.getDesiredState());
-        if (!newState.isValidDesiredState()) {
-          throw new IllegalArgumentException("Invalid arguments, invalid"
-              + " desired state, desiredState=" + newState.toString());
-        }
-      }
-
-      // Setting Maintenance state for host component
-      if (null != request.getMaintenanceState()) {
-        MaintenanceStateHelper psh = injector.getInstance(MaintenanceStateHelper.class);
-
-        MaintenanceState newMaint = MaintenanceState.valueOf(request.getMaintenanceState());
-        MaintenanceState oldMaint = psh.getEffectiveState(sch);
-
-        if (newMaint != oldMaint) {
-          if (sc.isClientComponent()) {
-            throw new IllegalArgumentException("Invalid arguments, cannot set " +
-              "maintenance state on a client component");
-          } else if (newMaint.equals(MaintenanceState.IMPLIED_FROM_HOST)
-              || newMaint.equals(MaintenanceState.IMPLIED_FROM_SERVICE)) {
-            throw new IllegalArgumentException("Invalid arguments, can only set " +
-              "maintenance state to one of " + EnumSet.of(MaintenanceState.OFF, MaintenanceState.ON));
-          } else {
-            sch.setMaintenanceState(newMaint);
-            maintenanceCluster = sch.getClusterName();
-          }
-        }
-      }
-
-      if (newState == null) {
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Nothing to do for new updateServiceComponentHost request"
-              + ", clusterName=" + request.getClusterName()
-              + ", serviceName=" + request.getServiceName()
-              + ", componentName=" + request.getComponentName()
-              + ", hostname=" + request.getHostname()
-              + ", oldState=" + oldState
-              + ", newDesiredState=null");
-        }
-        continue;
-      }
-
-      if (sc.isClientComponent() &&
-          !newState.isValidClientComponentState()) {
-        throw new IllegalArgumentException("Invalid desired state for a client"
-            + " component");
-      }
-
-      seenNewStates.add(newState);
-
-      State oldSchState = sch.getState();
-      // Client component reinstall allowed
-      if (newState == oldSchState &&
-          !sc.isClientComponent() &&
-          !requestProperties.containsKey(sch.getServiceComponentName().toLowerCase())) {
-
-        ignoredScHosts.add(sch);
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Ignoring ServiceComponentHost"
-              + ", clusterName=" + request.getClusterName()
-              + ", serviceName=" + s.getName()
-              + ", componentName=" + sc.getName()
-              + ", hostname=" + sch.getHostName()
-              + ", currentState=" + oldSchState
-              + ", newDesiredState=" + newState);
-        }
-        continue;
-      }
-
-      if (! maintenanceStateHelper.isOperationAllowed(reqOpLvl, sch)) {
-        ignoredScHosts.add(sch);
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Ignoring ServiceComponentHost"
-                  + ", clusterName=" + request.getClusterName()
-                  + ", serviceName=" + s.getName()
-                  + ", componentName=" + sc.getName()
-                  + ", hostname=" + sch.getHostName());
-        }
-        continue;
-      }
-
-      if (!State.isValidStateTransition(oldSchState, newState)) {
-        throw new AmbariException("Invalid transition for"
-            + " servicecomponenthost"
-            + ", clusterName=" + cluster.getClusterName()
-            + ", clusterId=" + cluster.getClusterId()
-            + ", serviceName=" + sch.getServiceName()
-            + ", componentName=" + sch.getServiceComponentName()
-            + ", hostname=" + sch.getHostName()
-            + ", currentState=" + oldSchState
-            + ", newDesiredState=" + newState);
-      }
-
-      if (isDirectTransition(oldSchState, newState)) {
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Handling direct transition update to ServiceComponentHost"
-              + ", clusterName=" + request.getClusterName()
-              + ", serviceName=" + s.getName()
-              + ", componentName=" + sc.getName()
-              + ", hostname=" + sch.getHostName()
-              + ", currentState=" + oldSchState
-              + ", newDesiredState=" + newState);
-        }
-        directTransitionScHosts.put(sch, newState);
-      } else {
-        if (!changedScHosts.containsKey(sc.getName())) {
-          changedScHosts.put(sc.getName(),
-              new EnumMap<State, List<ServiceComponentHost>>(State.class));
-        }
-        if (!changedScHosts.get(sc.getName()).containsKey(newState)) {
-          changedScHosts.get(sc.getName()).put(newState,
-              new ArrayList<ServiceComponentHost>());
-        }
-        if (LOG.isDebugEnabled()) {
-          LOG.debug("Handling update to ServiceComponentHost"
-              + ", clusterName=" + request.getClusterName()
-              + ", serviceName=" + s.getName()
-              + ", componentName=" + sc.getName()
-              + ", hostname=" + sch.getHostName()
-              + ", currentState=" + oldSchState
-              + ", newDesiredState=" + newState);
-        }
-        changedScHosts.get(sc.getName()).get(newState).add(sch);
-      }
-    }
-
-    if (seenNewStates.size() > 1) {
-      // FIXME should we handle this scenario
-      throw new IllegalArgumentException("Cannot handle different desired"
-          + " state changes for a set of service components at the same time");
-    }
-
-    // Perform direct transitions (without task generation)
-    for (Entry<ServiceComponentHost, State> entry : directTransitionScHosts.entrySet()) {
-      ServiceComponentHost componentHost = entry.getKey();
-      State newState = entry.getValue();
-      long timestamp = System.currentTimeMillis();
-      ServiceComponentHostEvent event;
-      componentHost.setDesiredState(newState);
-      switch (newState) {
-        case DISABLED:
-          event = new ServiceComponentHostDisableEvent(
-              componentHost.getServiceComponentName(),
-              componentHost.getHostName(),
-              timestamp);
-          break;
-        case INSTALLED:
-          event = new ServiceComponentHostRestoreEvent(
-              componentHost.getServiceComponentName(),
-              componentHost.getHostName(),
-              timestamp);
-          break;
-        default:
-          throw new AmbariException("Direct transition from " + componentHost.getState() + " to " + newState + " not supported");
-      }
-      try {
-        componentHost.handleEvent(event);
-      } catch (InvalidStateTransitionException e) {
-        //Should not occur, must be covered by previous checks
-        throw new AmbariException("Internal error - not supported transition", e);
-      }
-    }
-
-    Cluster cluster = clusters.getCluster(clusterNames.iterator().next());
-
-    return createAndPersistStages(cluster, requestProperties, null, null, null,
-      changedScHosts, ignoredScHosts, runSmokeTest, false);
-  }
-
-
-  private void validateServiceComponentHostRequest(ServiceComponentHostRequest request) {
+  //todo: for now made this public since is is still used by createHostComponents
+  //todo: delete after all host component logic is in HostComponentResourceProvider
+  public void validateServiceComponentHostRequest(ServiceComponentHostRequest request) {
     if (request.getClusterName() == null
         || request.getClusterName().isEmpty()
         || request.getComponentName() == null
@@ -2435,31 +2167,6 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     return serviceName;
   }
 
-
-  /**
-   * Checks if assigning new state does not require performing
-   * any additional actions
-   */
-  private boolean isDirectTransition(State oldState, State newState) {
-    switch (newState) {
-      case INSTALLED:
-        if (oldState == State.DISABLED) {
-          return true;
-        }
-        break;
-      case DISABLED:
-        if (oldState == State.INSTALLED ||
-          oldState == State.INSTALL_FAILED ||
-          oldState == State.UNKNOWN) {
-          return true;
-        }
-        break;
-      default:
-        break;
-    }
-    return false;
-  }
-
   @Override
   public synchronized void updateUsers(Set<UserRequest> requests) throws AmbariException {
     for (UserRequest request : requests) {
@@ -2935,13 +2642,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
 
   @Override
   public void updateGroups(Set<GroupRequest> requests) throws AmbariException {
-    for (GroupRequest request: requests) {
-      final Group group = users.getGroup(request.getGroupName());
-      if (group == null) {
-        continue;
-      }
-      // currently no group updates are supported
-    }
+    // currently no group updates are supported
   }
 
   protected String getClientHostForRunningAction(Cluster cluster, Service service, ServiceComponent serviceComponent)
@@ -3239,16 +2940,16 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
           String errorMessage = null;
 
           String[] suffixes = configs.getRepoValidationSuffixes(rr.getOsType());
-          for (int i = 0; i < suffixes.length; i++) {
-            String suffix = String.format(suffixes[i], repoName);
+          for (String suffix : suffixes) {
+            String formatted_suffix = String.format(suffix, repoName);
             String spec = rr.getBaseUrl();
 
-            if (spec.charAt(spec.length()-1) != '/' && suffix.charAt(0) != '/') {
-              spec = rr.getBaseUrl() + "/" + suffix;
-            } else if (spec.charAt(spec.length()-1) == '/' && suffix.charAt(0) == '/') {
-              spec = rr.getBaseUrl() + suffix.substring(1);
+            if (spec.charAt(spec.length() - 1) != '/' && formatted_suffix.charAt(0) != '/') {
+              spec = rr.getBaseUrl() + "/" + formatted_suffix;
+            } else if (spec.charAt(spec.length() - 1) == '/' && formatted_suffix.charAt(0) == '/') {
+              spec = rr.getBaseUrl() + formatted_suffix.substring(1);
             } else {
-              spec = rr.getBaseUrl() + suffix;
+              spec = rr.getBaseUrl() + formatted_suffix;
             }
 
             try {
@@ -3257,8 +2958,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
               errorMessage = "Could not access base url . " + rr.getBaseUrl() + " . ";
               if (LOG.isDebugEnabled()) {
                 errorMessage += ioe;
-              }
-              else {
+              } else {
                 errorMessage += ioe.getMessage();
               }
               bFound = false;

+ 30 - 1
ambari-server/src/main/java/org/apache/ambari/server/controller/HostRequest.java

@@ -31,6 +31,9 @@ public class HostRequest {
   private String rackInfo;
   private List<ConfigurationRequest> desiredConfigs; // UPDATE
   private String maintenanceState; // UPDATE
+  private String blueprint;
+  private String hostgroup;
+  private String hostToClone;
 
   public HostRequest(String hostname, String clusterName, Map<String, String> hostAttributes) {
     this.hostname = hostname;
@@ -94,6 +97,30 @@ public class HostRequest {
     return maintenanceState;
   }
 
+  public void setBlueprintName(String blueprintName) {
+    blueprint = blueprintName;
+  }
+
+  public String getBlueprintName() {
+    return blueprint;
+  }
+
+  public void setHostGroupName(String hostgroupName) {
+    hostgroup = hostgroupName;
+  }
+
+  public String getHostGroupName() {
+    return hostgroup;
+  }
+
+  public void setHostToClone(String hostname) {
+    hostToClone = hostname;
+  }
+
+  public String getHostToClone() {
+    return hostToClone;
+  }
+
   public String toString() {
     StringBuilder sb = new StringBuilder();
     sb.append("{ hostname=").append(hostname).append(", clusterName=").append(clusterName);
@@ -105,7 +132,9 @@ public class HostRequest {
           sb.append(",");
         }
         ++i;
-        sb.append(attr.getKey() + "=" + attr.getValue());
+        sb.append(attr.getKey());
+        sb.append("=");
+        sb.append(attr.getValue());
       }
       sb.append(']');
     }

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

@@ -380,7 +380,7 @@ public abstract class AbstractResourceProvider extends BaseProvider implements R
       String absCategory = PropertyHelper.getPropertyCategory(entry.getKey());
       String propName = PropertyHelper.getPropertyName(entry.getKey());
 
-      if (absCategory.startsWith(desiredConfigKey)) {
+      if (absCategory != null && absCategory.startsWith(desiredConfigKey)) {
         config = (null == config) ? new ConfigurationRequest() : config;
 
         parseProperties(config, absCategory, propName, entry.getValue().toString());

+ 81 - 0
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessor.java

@@ -24,8 +24,12 @@ import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.StackAccessException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.ResourceProvider;
 import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
 import org.apache.ambari.server.orm.dao.BlueprintDAO;
 import org.apache.ambari.server.orm.entities.BlueprintConfigEntity;
 import org.apache.ambari.server.orm.entities.BlueprintEntity;
@@ -37,6 +41,7 @@ import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.DependencyInfo;
 
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -442,6 +447,82 @@ public abstract class BaseBlueprintProcessor extends AbstractControllerResourceP
     throw new IllegalArgumentException(msg);
   }
 
+  /**
+   * Create host and host_component resources.
+   *
+   * @param blueprintHostGroups  host groups specified in blueprint
+   * @param clusterName          cluster name
+   *
+   * @throws SystemException                an unexpected exception occurred
+   * @throws UnsupportedPropertyException   an invalid property was specified
+   * @throws ResourceAlreadyExistsException attempt to create a host or host_component which already exists
+   * @throws NoSuchParentResourceException  a required parent resource is missing
+   */
+  protected void createHostAndComponentResources(Map<String, HostGroupImpl> blueprintHostGroups, String clusterName)
+      throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
+
+    createHostAndComponentResources(blueprintHostGroups, clusterName, getResourceProvider(Resource.Type.Host));
+  }
+
+  /**
+   * Create host and host_component resources via the specified host resource provider.
+   *
+   * @param blueprintHostGroups  host groups specified in blueprint
+   * @param clusterName          cluster name
+   * @param hostProvider         host resource provider
+   *
+   * @throws SystemException                an unexpected exception occurred
+   * @throws UnsupportedPropertyException   an invalid property was specified
+   * @throws ResourceAlreadyExistsException attempt to create a host or host_component which already exists
+   * @throws NoSuchParentResourceException  a required parent resource is missing
+   */
+  protected void createHostAndComponentResources(Map<String, HostGroupImpl> blueprintHostGroups,
+                                                 String clusterName,
+                                                 ResourceProvider hostProvider)
+                                                 throws SystemException,
+                                                        UnsupportedPropertyException,
+                                                        ResourceAlreadyExistsException,
+                                                        NoSuchParentResourceException {
+
+    ResourceProvider hostComponentProvider = getResourceProvider(Resource.Type.HostComponent);
+    for (HostGroupImpl group : blueprintHostGroups.values()) {
+      for (String host : group.getHostInfo()) {
+        Map<String, Object> hostProperties = new HashMap<String, Object>();
+        hostProperties.put("Hosts/cluster_name", clusterName);
+        hostProperties.put("Hosts/host_name", host);
+
+        hostProvider.createResources(new RequestImpl(
+            null, Collections.singleton(hostProperties), null, null));
+
+        // create clusters/hosts/host_components
+        Set<Map<String, Object>> setHostComponentRequestProps = new HashSet<Map<String, Object>>();
+        for (String hostComponent : group.getComponents()) {
+          // AMBARI_SERVER is not recognized by Ambari as a component
+          if (! hostComponent.equals("AMBARI_SERVER")) {
+            Map<String, Object> hostComponentProperties = new HashMap<String, Object>();
+            hostComponentProperties.put("HostRoles/cluster_name", clusterName);
+            hostComponentProperties.put("HostRoles/host_name", host);
+            hostComponentProperties.put("HostRoles/component_name", hostComponent);
+            setHostComponentRequestProps.add(hostComponentProperties);
+          }
+        }
+        hostComponentProvider.createResources(new RequestImpl(
+            null, setHostComponentRequestProps, null, null));
+      }
+    }
+  }
+
+  /**
+   * Get a config group name based on a bp and host group.
+   *
+   * @param bpName         blueprint name
+   * @param hostGroupName  host group name
+   * @return  config group name
+   */
+  protected String getConfigurationGroupName(String bpName, String hostGroupName) {
+    return String.format("%s:%s", bpName, hostGroupName);
+  }
+
 
   // ----- Inner Classes -----------------------------------------------------
 

+ 12 - 1
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java

@@ -339,9 +339,20 @@ public class ClusterControllerImpl implements ClusterController {
     return null;
   }
 
+
+  /**
+   * Provides a non-wrapped resource provider..
+   *
+   * @param type  type of resource provider to obtain
+   * @return a non-wrapped resource provider
+   */
   @Override
   public ResourceProvider ensureResourceProvider(Type type) {
-    return ensureResourceProviderWrapper(type);
+    //todo: in some cases it is necessary to down cast the returned resource provider
+    //todo: to a concrete type.  Perhaps we can provided a 'T getDelegate()' method
+    //todo: on the wrapper so no casting would be necessary.
+    ExtendedResourceProviderWrapper providerWrapper = ensureResourceProviderWrapper(type);
+    return providerWrapper == null ? null : providerWrapper.resourceProvider;
   }
 
 

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

@@ -598,49 +598,6 @@ public class ClusterResourceProvider extends BaseBlueprintProcessor {
     return clusterProperties;
   }
 
-  /**
-   * Create host and host_component resources.
-   *
-   * @param blueprintHostGroups  host groups specified in blueprint
-   * @param clusterName          cluster name
-   *
-   * @throws SystemException                an unexpected exception occurred
-   * @throws UnsupportedPropertyException   an invalid property was specified
-   * @throws ResourceAlreadyExistsException attempt to create a host or host_component which already exists
-   * @throws NoSuchParentResourceException  a required parent resource is missing
-   */
-  private void createHostAndComponentResources(Map<String, HostGroupImpl> blueprintHostGroups, String clusterName)
-      throws SystemException, UnsupportedPropertyException, ResourceAlreadyExistsException, NoSuchParentResourceException {
-
-    ResourceProvider hostProvider = getResourceProvider(Resource.Type.Host);
-    ResourceProvider hostComponentProvider = getResourceProvider(Resource.Type.HostComponent);
-    for (HostGroupImpl group : blueprintHostGroups.values()) {
-      for (String host : group.getHostInfo()) {
-        Map<String, Object> hostProperties = new HashMap<String, Object>();
-        hostProperties.put("Hosts/cluster_name", clusterName);
-        hostProperties.put("Hosts/host_name", host);
-
-        hostProvider.createResources(new RequestImpl(
-            null, Collections.singleton(hostProperties), null, null));
-
-        // create clusters/hosts/host_components
-        Set<Map<String, Object>> setHostComponentRequestProps = new HashSet<Map<String, Object>>();
-        for (String hostComponent : group.getComponents()) {
-          // AMBARI_SERVER is not recognized by Ambari as a component
-          if (! hostComponent.equals("AMBARI_SERVER")) {
-            Map<String, Object> hostComponentProperties = new HashMap<String, Object>();
-            hostComponentProperties.put("HostRoles/cluster_name", clusterName);
-            hostComponentProperties.put("HostRoles/host_name", host);
-            hostComponentProperties.put("HostRoles/component_name", hostComponent);
-            setHostComponentRequestProps.add(hostComponentProperties);
-          }
-        }
-        hostComponentProvider.createResources(new RequestImpl(
-            null, setHostComponentRequestProps, null, null));
-      }
-    }
-  }
-
   /**
    * Create component resources.
    *
@@ -1158,8 +1115,9 @@ public class ClusterResourceProvider extends BaseBlueprintProcessor {
       for (Map.Entry<String, Map<String, Config>> entry : groupConfigs.entrySet()) {
         String service = entry.getKey();
         Map<String, Config> serviceConfigs = entry.getValue();
+        String hostGroupName = getConfigurationGroupName(entity.getBlueprintName(), entity.getName());
         ConfigGroupRequest request = new ConfigGroupRequest(
-            null, clusterName, entity.getName(), service, "Host Group Configuration",
+            null, clusterName, hostGroupName, service, "Host Group Configuration",
             new HashSet<String>(group.getHostInfo()), serviceConfigs);
 
         ((ConfigGroupResourceProvider) getResourceProvider(Resource.Type.ConfigGroup)).

+ 20 - 11
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigGroupResourceProvider.java

@@ -17,8 +17,6 @@
  */
 package org.apache.ambari.server.controller.internal;
 
-import com.google.common.collect.MapDifference;
-import com.google.common.collect.Maps;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.ClusterNotFoundException;
 import org.apache.ambari.server.ConfigGroupNotFoundException;
@@ -180,19 +178,13 @@ public class ConfigGroupResourceProvider extends
       for (Map<String, Object> propertyMap : getPropertyMaps(iterator.next(), predicate)) {
         requests.add(getConfigGroupRequest(propertyMap));
       }
-
-      modifyResources(new Command<Void>() {
-        @Override
-        public Void invoke() throws AmbariException {
-          updateConfigGroups(requests);
-          return null;
-        }
-      });
     }
 
+    RequestStatus status = updateResources(requests);
+
     notifyUpdate(Resource.Type.ConfigGroup, request, predicate);
 
-    return getRequestStatus(null);
+    return status;
   }
 
   @Override
@@ -265,6 +257,23 @@ public class ConfigGroupResourceProvider extends
     return getRequestStatus(null, associatedResources);
   }
 
+  public RequestStatus updateResources(final Set<ConfigGroupRequest> requests)
+      throws SystemException,
+      UnsupportedPropertyException,
+      NoSuchResourceException,
+      NoSuchParentResourceException {
+
+    modifyResources(new Command<Void>() {
+      @Override
+      public Void invoke() throws AmbariException {
+        updateConfigGroups(requests);
+        return null;
+      }
+    });
+
+    return getRequestStatus(null);
+  }
+
   private synchronized  Set<ConfigGroupResponse> getConfigGroups
     (Set<ConfigGroupRequest> requests) throws AmbariException {
     Set<ConfigGroupResponse> responses = new HashSet<ConfigGroupResponse>();

+ 523 - 21
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostComponentResourceProvider.java

@@ -17,20 +17,31 @@
  */
 package org.apache.ambari.server.controller.internal;
 
+import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumMap;
+import java.util.EnumSet;
 import java.util.HashMap;
 import java.util.HashSet;
-import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import com.google.inject.Inject;
 import com.google.inject.Injector;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.configuration.ComponentSSLConfiguration;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.MaintenanceStateHelper;
 import org.apache.ambari.server.controller.RequestStatusResponse;
 import org.apache.ambari.server.controller.ServiceComponentHostRequest;
 import org.apache.ambari.server.controller.ServiceComponentHostResponse;
+import org.apache.ambari.server.controller.predicate.AndPredicate;
+import org.apache.ambari.server.controller.predicate.EqualsPredicate;
+import org.apache.ambari.server.controller.predicate.NotPredicate;
+import org.apache.ambari.server.controller.predicate.OrPredicate;
 import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
 import org.apache.ambari.server.controller.spi.NoSuchResourceException;
 import org.apache.ambari.server.controller.spi.Predicate;
@@ -45,7 +56,17 @@ import org.apache.ambari.server.controller.utilities.PropertyHelper;
 
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
-import com.google.inject.persist.Transactional;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.MaintenanceState;
+import org.apache.ambari.server.state.ServiceComponent;
+import org.apache.ambari.server.state.ServiceComponentHost;
+import org.apache.ambari.server.state.ServiceComponentHostEvent;
+import org.apache.ambari.server.state.State;
+import org.apache.ambari.server.state.fsm.InvalidStateTransitionException;
+import org.apache.ambari.server.state.svccomphost.ServiceComponentHostDisableEvent;
+import org.apache.ambari.server.state.svccomphost.ServiceComponentHostRestoreEvent;
+import org.apache.commons.lang.StringUtils;
 
 /**
  * Resource provider for host component resources.
@@ -79,7 +100,7 @@ public class HostComponentResourceProvider extends AbstractControllerResourcePro
       = PropertyHelper.getPropertyId("HostRoles", "desired_admin_state");
   protected static final String HOST_COMPONENT_MAINTENANCE_STATE_PROPERTY_ID
       = "HostRoles/maintenance_state";
-  
+
   //Component name mappings
   private final Map<String, PropertyProvider> HOST_COMPONENT_PROPERTIES_PROVIDER = new HashMap<String, PropertyProvider>();
   private static final int HOST_COMPONENT_HTTP_PROPERTY_REQUEST_CONNECT_TIMEOUT = 1500;   //milliseconds
@@ -95,6 +116,13 @@ public class HostComponentResourceProvider extends AbstractControllerResourcePro
           HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID,
           HOST_COMPONENT_HOST_NAME_PROPERTY_ID}));
 
+  /**
+   * maintenance state helper
+   */
+  @Inject
+  private MaintenanceStateHelper maintenanceStateHelper;
+
+
   // ----- Constructors ----------------------------------------------------
 
   /**
@@ -153,7 +181,6 @@ public class HostComponentResourceProvider extends AbstractControllerResourcePro
   }
 
   @Override
-  @Transactional
   public Set<Resource> getResources(Request request, Predicate predicate)
       throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
 
@@ -194,7 +221,7 @@ public class HostComponentResourceProvider extends AbstractControllerResourcePro
       setResourceProperty(resource, HOST_COMPONENT_ACTUAL_CONFIGS_PROPERTY_ID,
           response.getActualConfigs(), requestedIds);
       setResourceProperty(resource, HOST_COMPONENT_STALE_CONFIGS_PROPERTY_ID,
-          Boolean.valueOf(response.isStaleConfig()), requestedIds);
+          response.isStaleConfig(), requestedIds);
       
       if (response.getAdminState() != null) {
         setResourceProperty(resource, HOST_COMPONENT_DESIRED_ADMIN_STATE_PROPERTY_ID,
@@ -222,26 +249,24 @@ public class HostComponentResourceProvider extends AbstractControllerResourcePro
   @Override
   public RequestStatus updateResources(final Request request, Predicate predicate)
       throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
-    final Set<ServiceComponentHostRequest> requests = new HashSet<ServiceComponentHostRequest>();
-    RequestStatusResponse response = null;
 
-    final boolean runSmokeTest = "true".equals(getQueryParameterValue(
-        QUERY_PARAMETERS_RUN_SMOKE_TEST_ID, predicate)) ? true : false;
+    if (request.getProperties().isEmpty()) {
+      throw new IllegalArgumentException("Received an update request with no properties");
+    }
 
-    Iterator<Map<String, Object>> iterator = request.getProperties().iterator();
-    if (iterator.hasNext()) {
-      for (Map<String, Object> propertyMap : getPropertyMaps(request.getProperties().iterator().next(), predicate)) {
-        requests.add(getRequest(propertyMap));
-      }
-      response = modifyResources(new Command<RequestStatusResponse>() {
-        @Override
-        public RequestStatusResponse invoke() throws AmbariException {
-          return getManagementController().updateHostComponents(requests, request.getRequestInfoProperties(), runSmokeTest);
-        }
-      });
+    RequestStageContainer requestStages = doUpdateResources(null, request, predicate);
 
+    RequestStatusResponse response = null;
+    if (requestStages != null) {
+      try {
+        requestStages.persist();
+      } catch (AmbariException e) {
+        throw new SystemException(e.getMessage(), e);
+      }
+      response = requestStages.getRequestStatusResponse();
       notifyUpdate(Resource.Type.HostComponent, request, predicate);
     }
+
     return getRequestStatus(response);
   }
 
@@ -284,14 +309,247 @@ public class HostComponentResourceProvider extends AbstractControllerResourcePro
     return unsupportedProperties;
   }
 
+  RequestStatusResponse installAndStart(String cluster, Collection<String> hosts) throws  SystemException,
+      UnsupportedPropertyException, NoSuchParentResourceException {
 
-  // ----- utility methods -------------------------------------------------
+    final RequestStageContainer requestStages;
+    Map<String, Object> installProperties = new HashMap<String, Object>();
+
+    installProperties.put(HOST_COMPONENT_STATE_PROPERTY_ID, "INSTALLED");
+    Map<String, String> requestInfo = new HashMap<String, String>();
+    requestInfo.put("context", "Install and start components on added hosts");
+    Request installRequest = PropertyHelper.getUpdateRequest(installProperties, requestInfo);
+
+    Collection<EqualsPredicate> hostPredicates = new ArrayList<EqualsPredicate>();
+    for (String host : hosts) {
+      hostPredicates.add(new EqualsPredicate<String>(HOST_COMPONENT_HOST_NAME_PROPERTY_ID, host));
+    }
+
+    Predicate statePredicate = new EqualsPredicate<String>(HOST_COMPONENT_STATE_PROPERTY_ID, "INIT");
+    Predicate clusterPredicate = new EqualsPredicate<String>(HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID, cluster);
+    Predicate hostPredicate = new OrPredicate(hostPredicates.toArray(new Predicate[hostPredicates.size()]));
+    Predicate hostAndStatePredicate = new AndPredicate(statePredicate, hostPredicate);
+    Predicate installPredicate = new AndPredicate(hostAndStatePredicate, clusterPredicate);
+
+    try {
+      LOG.info("Installing all components on added hosts");
+      requestStages = doUpdateResources(null, installRequest, installPredicate);
+      notifyUpdate(Resource.Type.HostComponent, installRequest, installPredicate);
+
+      Map<String, Object> startProperties = new HashMap<String, Object>();
+      startProperties.put(HOST_COMPONENT_STATE_PROPERTY_ID, "STARTED");
+      Request startRequest = PropertyHelper.getUpdateRequest(startProperties, requestInfo);
+      // Important to query against desired_state as this has been updated when install stage was created
+      // If I query against state, then the getRequest compares predicate prop against desired_state and then when the predicate
+      // is later applied explicitly, it gets compared to live_state. Since live_state == INSTALLED == INIT at this point and
+      // desired_state == INSTALLED, we will always get 0 matches since both comparisons can't be true :(
+      Predicate installedStatePredicate = new EqualsPredicate<String>(HOST_COMPONENT_DESIRED_STATE_PROPERTY_ID, "INSTALLED");
+      Predicate notClientPredicate = new NotPredicate(new ClientComponentPredicate());
+      Predicate clusterAndClientPredicate = new AndPredicate(clusterPredicate, notClientPredicate);
+      hostAndStatePredicate = new AndPredicate(installedStatePredicate, hostPredicate);
+      Predicate startPredicate = new AndPredicate(clusterAndClientPredicate, hostAndStatePredicate);
+
+      LOG.info("Starting all non-client components on added hosts");
+      //todo: if a host in in state HEARTBEAT_LOST, no stage will be created, so if this occurs during INSTALL
+      //todo: then no INSTALL stage will exist which will result in invalid state transition INIT->STARTED
+      doUpdateResources(requestStages, startRequest, startPredicate);
+      notifyUpdate(Resource.Type.HostComponent, startRequest, startPredicate);
+      try {
+        requestStages.persist();
+      } catch (AmbariException e) {
+        throw new SystemException(e.getMessage(), e);
+      }
+      return requestStages.getRequestStatusResponse();
+    } catch (NoSuchResourceException e) {
+      // shouldn't encounter this exception here
+      throw new SystemException("An unexpected exception occurred while processing add hosts",  e);
+    }
+  }
+
+
+  /**
+   * Update the host component identified by the given request object with the
+   * values carried by the given request object.
+   *
+   * @param stages             stages of the associated request
+   * @param requests           the request object which defines which host component to
+   *                           update and the values to set
+   * @param requestProperties  the request properties
+   * @param runSmokeTest       indicates whether or not to run a smoke test
+   *
+   * @return a track action response
+   *
+   * @throws AmbariException thrown if the resource cannot be updated
+   */
+  //todo: This was moved from AmbariManagementController and needs a lot of refactoring.
+  //todo: Look into using the predicate instead of Set<ServiceComponentHostRequest>
+  //todo: change to private access when all AMC tests have been moved.
+  protected synchronized RequestStageContainer updateHostComponents(RequestStageContainer stages,
+                                                                    Set<ServiceComponentHostRequest> requests,
+                                                                    Map<String, String> requestProperties,
+                                                                    boolean runSmokeTest) throws AmbariException {
+
+    Clusters clusters = getManagementController().getClusters();
+
+
+    Map<String, Map<State, List<ServiceComponentHost>>> changedScHosts = new HashMap<String, Map<State, List<ServiceComponentHost>>>();
+    Collection<ServiceComponentHost> ignoredScHosts = new ArrayList<ServiceComponentHost>();
+    Set<String> clusterNames = new HashSet<String>();
+    Map<String, Map<String, Map<String, Set<String>>>> requestClusters = new HashMap<String, Map<String, Map<String, Set<String>>>>();
+    Map<ServiceComponentHost, State> directTransitionScHosts = new HashMap<ServiceComponentHost, State>();
+
+    Resource.Type reqOpLvl = determineOperationLevel(requestProperties);
+
+
+    for (ServiceComponentHostRequest request : requests) {
+      validateServiceComponentHostRequest(request);
+
+      Cluster cluster = clusters.getCluster(request.getClusterName());
+
+      if (StringUtils.isEmpty(request.getServiceName())) {
+        request.setServiceName(getManagementController().findServiceName(cluster, request.getComponentName()));
+      }
+
+      ServiceComponent sc = getServiceComponent(
+          request.getClusterName(), request.getServiceName(), request.getComponentName());
+
+      logRequestInfo("Received a updateHostComponent request", request);
+
+      clusterNames.add(request.getClusterName());
+
+      if (clusterNames.size() > 1) {
+        throw new IllegalArgumentException("Updates to multiple clusters is not"
+            + " supported");
+      }
+
+      // maps of cluster->services, services->components, components->hosts
+      Map<String, Map<String, Set<String>>> clusterServices = requestClusters.get(request.getClusterName());
+      if (clusterServices == null) {
+        clusterServices = new HashMap<String, Map<String, Set<String>>>();
+        requestClusters.put(request.getClusterName(), clusterServices);
+      }
+
+      Map<String, Set<String>> serviceComponents = clusterServices.get(request.getServiceName());
+      if (serviceComponents == null) {
+        serviceComponents = new HashMap<String, Set<String>>();
+        clusterServices.put(request.getServiceName(), serviceComponents);
+      }
+
+      Set<String> componentHosts = serviceComponents.get(request.getComponentName());
+      if (componentHosts == null) {
+        componentHosts = new HashSet<String>();
+        serviceComponents.put(request.getComponentName(), componentHosts) ;
+      }
+
+      if (componentHosts.contains(request.getHostname())) {
+        throw new IllegalArgumentException("Invalid request contains duplicate hostcomponents");
+      }
+
+      componentHosts.add(request.getHostname());
+
+
+      ServiceComponentHost sch = sc.getServiceComponentHost(request.getHostname());
+      State oldState = sch.getState();
+      State newState = null;
+      if (request.getDesiredState() != null) {
+        // set desired state on host component
+        newState = State.valueOf(request.getDesiredState());
+        // throw exception if desired state isn't a valid desired state (static check)
+        if (!newState.isValidDesiredState()) {
+          throw new IllegalArgumentException("Invalid arguments, invalid"
+              + " desired state, desiredState=" + newState.toString());
+        }
+      }
+
+      // Setting Maintenance state for host component
+      if (null != request.getMaintenanceState()) {
+        MaintenanceState newMaint = MaintenanceState.valueOf(request.getMaintenanceState());
+        MaintenanceState oldMaint = maintenanceStateHelper.getEffectiveState(sch);
+
+        if (newMaint != oldMaint) {
+          if (sc.isClientComponent()) {
+            throw new IllegalArgumentException("Invalid arguments, cannot set maintenance state on a client component");
+          } else if (newMaint.equals(MaintenanceState.IMPLIED_FROM_HOST)  || newMaint.equals(MaintenanceState.IMPLIED_FROM_SERVICE)) {
+            throw new IllegalArgumentException("Invalid arguments, can only set maintenance state to one of " +
+                EnumSet.of(MaintenanceState.OFF, MaintenanceState.ON));
+          } else {
+            sch.setMaintenanceState(newMaint);
+          }
+        }
+      }
+
+      if (newState == null) {
+        logComponentInfo("Nothing to do for new updateServiceComponentHost", request, oldState, null);
+        continue;
+      }
+
+      if (sc.isClientComponent() &&
+          !newState.isValidClientComponentState()) {
+        throw new IllegalArgumentException("Invalid desired state for a client"
+            + " component");
+      }
+
+      State oldSchState = sch.getState();
+      // Client component reinstall allowed
+      if (newState == oldSchState && !sc.isClientComponent() &&
+          !requestProperties.containsKey(sch.getServiceComponentName().toLowerCase())) {
+
+        ignoredScHosts.add(sch);
+        logComponentInfo("Ignoring ServiceComponentHost", request, oldState, newState);
+        continue;
+      }
+
+      if (! maintenanceStateHelper.isOperationAllowed(reqOpLvl, sch)) {
+        ignoredScHosts.add(sch);
+        logComponentInfo("Ignoring ServiceComponentHost", request, oldState, newState);
+        continue;
+      }
+
+      if (! isValidStateTransition(stages, oldSchState, newState, sch)) {
+        throw new AmbariException("Invalid state transition for host component"
+            + ", clusterName=" + cluster.getClusterName()
+            + ", clusterId=" + cluster.getClusterId()
+            + ", serviceName=" + sch.getServiceName()
+            + ", componentName=" + sch.getServiceComponentName()
+            + ", hostname=" + sch.getHostName()
+            + ", currentState=" + oldSchState
+            + ", newDesiredState=" + newState);
+      }
+
+      if (isDirectTransition(oldSchState, newState)) {
+        logComponentInfo("Handling direct transition update to host component", request, oldState, newState);
+        directTransitionScHosts.put(sch, newState);
+      } else {
+        if (!changedScHosts.containsKey(sc.getName())) {
+          changedScHosts.put(sc.getName(),
+              new EnumMap<State, List<ServiceComponentHost>>(State.class));
+        }
+        if (!changedScHosts.get(sc.getName()).containsKey(newState)) {
+          changedScHosts.get(sc.getName()).put(newState,
+              new ArrayList<ServiceComponentHost>());
+        }
+        logComponentInfo("Handling update to host component", request, oldState, newState);
+        changedScHosts.get(sc.getName()).get(newState).add(sch);
+      }
+    }
+
+    doDirectTransitions(directTransitionScHosts);
+
+    // just getting the first cluster
+    Cluster cluster = clusters.getCluster(clusterNames.iterator().next());
+
+    return getManagementController().addStages(stages, cluster, requestProperties, null, null, null,
+        changedScHosts, ignoredScHosts, runSmokeTest, false);
+  }
 
   @Override
   protected Set<String> getPKPropertyIds() {
     return pkPropertyIds;
   }
 
+
+  // ----- utility methods -------------------------------------------------
+
   /**
    * Get a component request object from a map of property values.
    *
@@ -324,4 +582,248 @@ public class HostComponentResourceProvider extends AbstractControllerResourcePro
 
     return serviceComponentHostRequest;
   }
+
+  private RequestStageContainer doUpdateResources(final RequestStageContainer stages, final Request request, Predicate predicate)
+      throws UnsupportedPropertyException, SystemException, NoSuchResourceException, NoSuchParentResourceException {
+
+    final Set<ServiceComponentHostRequest> requests = new HashSet<ServiceComponentHostRequest>();
+
+    final boolean runSmokeTest = "true".equals(getQueryParameterValue(
+        QUERY_PARAMETERS_RUN_SMOKE_TEST_ID, predicate));
+
+    Set<String> queryIds = Collections.singleton(HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID);
+
+    Request queryRequest = PropertyHelper.getReadRequest(queryIds);
+    // will take care of 404 exception
+    Set<Resource> matchingResources = getResources(queryRequest, predicate);
+
+    for (Resource queryResource : matchingResources) {
+      if (predicate.evaluate(queryResource)) {
+        Map<String, Object> updateRequestProperties = new HashMap<String, Object>();
+
+        // add props from query resource
+        updateRequestProperties.putAll(PropertyHelper.getProperties(queryResource));
+
+        // add properties from update request
+        //todo: should we flag value size > 1?
+        if (request.getProperties() != null && request.getProperties().size() != 0) {
+          updateRequestProperties.putAll(request.getProperties().iterator().next());
+        }
+        requests.add(getRequest(updateRequestProperties));
+      }
+    }
+
+    RequestStageContainer requestStages = modifyResources(new Command<RequestStageContainer>() {
+      @Override
+      public RequestStageContainer invoke() throws AmbariException {
+        return updateHostComponents(stages, requests, request.getRequestInfoProperties(),
+            runSmokeTest);
+      }
+    });
+    notifyUpdate(Resource.Type.HostComponent, request, predicate);
+
+    return requestStages;
+  }
+
+  /**
+   * Determine whether a host component state change is valid.
+   * Looks at projected state from the current stages associated with the request.
+   *
+   *
+   * @param stages        request stages
+   * @param startState    host component start state
+   * @param desiredState  host component desired state
+   * @param host          host where state change is occurring
+   *
+   * @return whether the state transition is valid
+   */
+  private boolean isValidStateTransition(RequestStageContainer stages, State startState,
+                                         State desiredState, ServiceComponentHost host) {
+
+    if (stages != null) {
+      State projectedState = stages.getProjectedState(host.getHostName(), host.getServiceComponentName());
+      startState = projectedState == null ? startState : projectedState;
+    }
+
+    return State.isValidStateTransition(startState, desiredState);
+  }
+
+  /**
+   * Checks if assigning new state does not require performing
+   * any additional actions
+   */
+  public boolean isDirectTransition(State oldState, State newState) {
+    switch (newState) {
+      case INSTALLED:
+        if (oldState == State.DISABLED) {
+          return true;
+        }
+        break;
+      case DISABLED:
+        if (oldState == State.INSTALLED ||
+            oldState == State.INSTALL_FAILED ||
+            oldState == State.UNKNOWN) {
+          return true;
+        }
+        break;
+      default:
+        break;
+    }
+    return false;
+  }
+
+  private ServiceComponent getServiceComponent(String clusterName, String serviceName, String componentName)
+      throws AmbariException {
+
+    Clusters clusters = getManagementController().getClusters();
+    return clusters.getCluster(clusterName).getService(serviceName).getServiceComponent(componentName);
+  }
+
+  // Perform direct transitions (without task generation)
+  private void doDirectTransitions(Map<ServiceComponentHost, State> directTransitionScHosts) throws AmbariException {
+    for (Map.Entry<ServiceComponentHost, State> entry : directTransitionScHosts.entrySet()) {
+      ServiceComponentHost componentHost = entry.getKey();
+      State newState = entry.getValue();
+      long timestamp = System.currentTimeMillis();
+      ServiceComponentHostEvent event;
+      componentHost.setDesiredState(newState);
+      switch (newState) {
+        case DISABLED:
+          event = new ServiceComponentHostDisableEvent(
+              componentHost.getServiceComponentName(),
+              componentHost.getHostName(),
+              timestamp);
+          break;
+        case INSTALLED:
+          event = new ServiceComponentHostRestoreEvent(
+              componentHost.getServiceComponentName(),
+              componentHost.getHostName(),
+              timestamp);
+          break;
+        default:
+          throw new AmbariException("Direct transition from " + componentHost.getState() + " to " + newState + " not supported");
+      }
+      try {
+        componentHost.handleEvent(event);
+      } catch (InvalidStateTransitionException e) {
+        //Should not occur, must be covered by previous checks
+        throw new AmbariException("Internal error - not supported transition", e);
+      }
+    }
+  }
+
+  /**
+   * Logs request info.
+   *
+   * @param msg      base log msg
+   * @param request  the request to log
+   */
+  private void logRequestInfo(String msg, ServiceComponentHostRequest request) {
+    LOG.info("{}, clusterName={}, serviceName={}, componentName={}, hostname={}, request={}",
+        msg,
+        request.getClusterName(),
+        request.getServiceName(),
+        request.getComponentName(),
+        request.getHostname(),
+        request);
+  }
+
+  /**
+   * Logs component info.
+   *
+   * @param msg              base log msg
+   * @param request          the request to log
+   * @param oldState         current state
+   * @param newDesiredState  new desired state
+   */
+  private void logComponentInfo(String msg, ServiceComponentHostRequest request, State oldState, State newDesiredState) {
+    LOG.debug("{}, clusterName={}, serviceName={}, componentName={}, hostname={}, currentState={}, newDesiredState={}",
+        msg,
+        request.getClusterName(),
+        request.getServiceName(),
+        request.getComponentName(),
+        request.getHostname(),
+        oldState == null ? "null" : oldState,
+        newDesiredState == null ? "null" : newDesiredState);
+  }
+
+  /**
+   * Get the "operation level" from the request.
+   *
+   * @param requestProperties  request properties
+   * @return  the "operation level"
+   */
+  private Resource.Type determineOperationLevel(Map<String, String> requestProperties) {
+    // Determine operation level
+    Resource.Type reqOpLvl;
+    if (requestProperties.containsKey(RequestOperationLevel.OPERATION_LEVEL_ID)) {
+      reqOpLvl = new RequestOperationLevel(requestProperties).getLevel();
+    } else {
+      String message = "Can not determine request operation level. " +
+          "Operation level property should " +
+          "be specified for this request.";
+      LOG.warn(message);
+      reqOpLvl = Resource.Type.Cluster;
+    }
+    return reqOpLvl;
+  }
+
+  /**
+   * Validate a host component request.
+   *
+   * @param request  request to validate
+   * @throws IllegalArgumentException if the request is invalid
+   */
+  private void validateServiceComponentHostRequest(ServiceComponentHostRequest request) {
+    if (request.getClusterName() == null
+        || request.getClusterName().isEmpty()
+        || request.getComponentName() == null
+        || request.getComponentName().isEmpty()
+        || request.getHostname() == null
+        || request.getHostname().isEmpty()) {
+      throw new IllegalArgumentException("Invalid arguments"
+          + ", cluster name, component name and host name should be"
+          + " provided");
+    }
+
+    if (request.getAdminState() != null) {
+      throw new IllegalArgumentException("Property adminState cannot be modified through update. Use service " +
+          "specific DECOMMISSION action to decommision/recommission components.");
+    }
+  }
+
+
+  // ----- inner classes ---------------------------------------------------
+
+  /**
+   * Predicate that identifies client components.
+   */
+  private  class ClientComponentPredicate implements Predicate {
+    @Override
+    public boolean evaluate(Resource resource) {
+      boolean isClient = false;
+
+      String componentName = (String) resource.getPropertyValue(HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID);
+      try {
+        if (componentName != null && !componentName.isEmpty()) {
+          AmbariManagementController managementController = getManagementController();
+          String clusterName = (String) resource.getPropertyValue(HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID);
+          String serviceName = (String) resource.getPropertyValue(HOST_COMPONENT_SERVICE_NAME_PROPERTY_ID);
+          if (StringUtils.isEmpty(serviceName)) {
+            Cluster cluster = managementController.getClusters().getCluster(clusterName);
+            serviceName = managementController.findServiceName(cluster, componentName);
+          }
+
+          ServiceComponent sc = getServiceComponent((String) resource.getPropertyValue(
+              HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID), serviceName, componentName);
+          isClient = sc.isClientComponent();
+        }
+      } catch (AmbariException e) {
+        // this is really a system exception since cluster/service should have been already verified
+        throw new RuntimeException(
+            "An unexpected exception occurred while trying to determine if a component is a client", e);
+      }
+      return isClient;
+    }
+  }
 }

+ 250 - 74
ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostResourceProvider.java

@@ -38,6 +38,7 @@ import org.apache.ambari.server.controller.ConfigurationRequest;
 import org.apache.ambari.server.controller.HostRequest;
 import org.apache.ambari.server.controller.HostResponse;
 import org.apache.ambari.server.controller.MaintenanceStateHelper;
+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;
@@ -48,6 +49,7 @@ 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 org.apache.ambari.server.orm.entities.BlueprintEntity;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.Config;
@@ -55,19 +57,19 @@ import org.apache.ambari.server.state.DesiredConfig;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.MaintenanceState;
 import org.apache.ambari.server.state.ServiceComponentHost;
+import org.apache.ambari.server.state.configgroup.ConfigGroup;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import com.google.inject.Inject;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
-import com.google.inject.persist.Transactional;
 
 
 /**
  * Resource provider for host resources.
  */
-public class HostResourceProvider extends AbstractControllerResourceProvider {
+public class HostResourceProvider extends BaseBlueprintProcessor {
 
   // ----- Property ID constants ---------------------------------------------
 
@@ -114,6 +116,13 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
   protected static final String HOST_DESIRED_CONFIGS_PROPERTY_ID = 
       PropertyHelper.getPropertyId("Hosts", "desired_configs");
 
+  protected static final String BLUEPRINT_PROPERTY_ID =
+      PropertyHelper.getPropertyId(null, "blueprint");
+  protected static final String HOSTGROUP_PROPERTY_ID =
+      PropertyHelper.getPropertyId(null, "host_group");
+  protected static final String HOST_NAME_NO_CATEGORY_PROPERTY_ID =
+      PropertyHelper.getPropertyId(null, "host_name");
+
   private static Set<String> pkPropertyIds =
       new HashSet<String>(Arrays.asList(new String[]{
           HOST_NAME_PROPERTY_ID}));
@@ -140,31 +149,31 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
   // ----- ResourceProvider ------------------------------------------------
 
   @Override
-  public RequestStatus createResources(Request request)
+  public RequestStatus createResources(final Request request)
       throws SystemException,
           UnsupportedPropertyException,
           ResourceAlreadyExistsException,
           NoSuchParentResourceException {
 
-    final Set<HostRequest> requests = new HashSet<HostRequest>();
-    for (Map<String, Object> propertyMap : request.getProperties()) {
-      requests.add(getRequest(propertyMap));
+    RequestStatusResponse createResponse = null;
+    if (isHostGroupRequest(request)) {
+      createResponse = addHostsUsingHostgroup(request);
+    } else {
+      createResources(new Command<Void>() {
+        @Override
+        public Void invoke() throws AmbariException {
+          createHosts(request);
+          return null;
+        }
+      });
     }
-    createResources(new Command<Void>() {
-      @Override
-      public Void invoke() throws AmbariException {
-        createHosts(requests);
-        return null;
-      }
-    });
 
     notifyCreate(Resource.Type.Host, request);
 
-    return getRequestStatus(null);
+    return getRequestStatus(createResponse);
   }
 
   @Override
-  @Transactional
   public Set<Resource> getResources(Request request, Predicate predicate)
       throws SystemException, UnsupportedPropertyException, NoSuchResourceException, NoSuchParentResourceException {
 
@@ -256,7 +265,7 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
     modifyResources(new Command<Void>() {
       @Override
       public Void invoke() throws AmbariException {
-        updateHosts(requests, request.getRequestInfoProperties());
+        updateHosts(requests);
         return null;
       }
     });
@@ -292,6 +301,10 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
   public Set<String> checkPropertyIds(Set<String> propertyIds) {
     Set<String> baseUnsupported = super.checkPropertyIds(propertyIds);
 
+    baseUnsupported.remove(BLUEPRINT_PROPERTY_ID);
+    baseUnsupported.remove(HOSTGROUP_PROPERTY_ID);
+    baseUnsupported.remove(HOST_NAME_NO_CATEGORY_PROPERTY_ID);
+
     return checkConfigPropertyIds(baseUnsupported, "Hosts");
   }
 
@@ -306,6 +319,25 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
 
   // ----- utility methods ---------------------------------------------------
 
+  /**
+   * Determine if a request is a high level "add hosts" call or a simple lower level request
+   * to add a host resources.
+   *
+   * @param request  current request
+   * @return true if this is a high level "add hosts" request;
+   *         false if it is a simple create host resources request
+   */
+  private boolean isHostGroupRequest(Request request) {
+    boolean isHostGroupRequest = false;
+    Set<Map<String, Object>> properties = request.getProperties();
+    if (properties != null && ! properties.isEmpty()) {
+      //todo: for now, either all or none of the hosts need to specify a hg.  Unable to mix.
+      String hgName = (String) properties.iterator().next().get(HOSTGROUP_PROPERTY_ID);
+      isHostGroupRequest = hgName != null && ! hgName.isEmpty();
+    }
+    return isHostGroupRequest;
+  }
+
   /**
    * Get a host request object from a map of property values.
    *
@@ -320,11 +352,13 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
     }
 
     HostRequest hostRequest = new HostRequest(
-        (String) properties.get(HOST_NAME_PROPERTY_ID),
+        getHostNameFromProperties(properties),
         (String) properties.get(HOST_CLUSTER_NAME_PROPERTY_ID),
         null);
     hostRequest.setPublicHostName((String) properties.get(HOST_PUBLIC_NAME_PROPERTY_ID));
     hostRequest.setRackInfo((String) properties.get(HOST_RACK_INFO_PROPERTY_ID));
+    hostRequest.setBlueprintName((String) properties.get(BLUEPRINT_PROPERTY_ID));
+    hostRequest.setHostGroupName((String) properties.get(HOSTGROUP_PROPERTY_ID));
     
     Object o = properties.get(HOST_MAINTENANCE_STATE_PROPERTY_ID);
     if (null != o) {
@@ -342,14 +376,15 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
   /**
    * Accepts a request with registered hosts and if the request contains a cluster name then will map all of the
    * hosts onto that cluster.
-   * @param requests Request that must contain registered hosts, and optionally a cluster.
+   * @param request Request that must contain registered hosts, and optionally a cluster.
    * @throws AmbariException
    */
-  protected synchronized void createHosts(Set<HostRequest> requests)
+  protected synchronized void createHosts(Request request)
       throws AmbariException {
 
-    if (requests.isEmpty()) {
-      LOG.warn("Received an empty requests set");
+    Set<Map<String, Object>> propertySet = request.getProperties();
+    if (propertySet == null || propertySet.isEmpty()) {
+      LOG.warn("Received a create host request with no associated property sets");
       return;
     }
 
@@ -359,43 +394,14 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
     Set<String> duplicates = new HashSet<String>();
     Set<String> unknowns = new HashSet<String>();
     Set<String> allHosts = new HashSet<String>();
-    for (HostRequest request : requests) {
-      if (request.getHostname() == null
-          || request.getHostname().isEmpty()) {
-        throw new IllegalArgumentException("Invalid arguments, hostname"
-            + " cannot be null");
-      }
-
-      if (LOG.isDebugEnabled()) {
-        LOG.debug("Received a createHost request"
-            + ", hostname=" + request.getHostname()
-            + ", request=" + request);
-      }
 
-      if (allHosts.contains(request.getHostname())) {
-        // throw dup error later
-        duplicates.add(request.getHostname());
-        continue;
-      }
-      allHosts.add(request.getHostname());
 
-      try {
-        // ensure host is registered
-        clusters.getHost(request.getHostname());
-      }
-      catch (HostNotFoundException e) {
-        unknowns.add(request.getHostname());
-        continue;
-      }
-
-      if (request.getClusterName() != null) {
-        try {
-          // validate that cluster_name is valid
-          clusters.getCluster(request.getClusterName());
-        } catch (ClusterNotFoundException e) {
-          throw new ParentObjectNotFoundException("Attempted to add a host to a cluster which doesn't exist: "
-              + " clusterName=" + request.getClusterName());
-        }
+    Set<HostRequest> hostRequests = new HashSet<HostRequest>();
+    for (Map<String, Object> propertyMap : propertySet) {
+      HostRequest hostRequest = getRequest(propertyMap);
+      hostRequests.add(hostRequest);
+      if (! propertyMap.containsKey(HOSTGROUP_PROPERTY_ID)) {
+        createHostResource(clusters, duplicates, unknowns, allHosts, hostRequest);
       }
     }
 
@@ -431,22 +437,183 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
 
     Map<String, Set<String>> hostClustersMap = new HashMap<String, Set<String>>();
     Map<String, Map<String, String>> hostAttributes = new HashMap<String, Map<String, String>>();
-    for (HostRequest request : requests) {
-      if (request.getHostname() != null && !request.getHostname().isEmpty() && request.getClusterName() != null && !request.getClusterName().isEmpty()) {
+
+    for (HostRequest hostRequest : hostRequests) {
+      if (hostRequest.getHostname() != null &&
+          !hostRequest.getHostname().isEmpty() &&
+          hostRequest.getClusterName() != null &&
+          !hostRequest.getClusterName().isEmpty()){
+
         Set<String> clusterSet = new HashSet<String>();
-        clusterSet.add(request.getClusterName());
-        hostClustersMap.put(request.getHostname(), clusterSet);
-        if (request.getHostAttributes() != null) {
-          hostAttributes.put(request.getHostname(), request.getHostAttributes());
+        clusterSet.add(hostRequest.getClusterName());
+        hostClustersMap.put(hostRequest.getHostname(), clusterSet);
+        if (hostRequest.getHostAttributes() != null) {
+          hostAttributes.put(hostRequest.getHostname(), hostRequest.getHostAttributes());
         }
       }
     }
     clusters.updateHostWithClusterAndAttributes(hostClustersMap, hostAttributes);
   }
 
-
-  protected Set<HostResponse> getHosts(Set<HostRequest> requests)
+  private void createHostResource(Clusters clusters, Set<String> duplicates,
+                                  Set<String> unknowns, Set<String> allHosts,
+                                  HostRequest request)
       throws AmbariException {
+
+
+    if (request.getHostname() == null
+        || request.getHostname().isEmpty()) {
+      throw new IllegalArgumentException("Invalid arguments, hostname"
+          + " cannot be null");
+    }
+
+    if (LOG.isDebugEnabled()) {
+      LOG.debug("Received a createHost request"
+          + ", hostname=" + request.getHostname()
+          + ", request=" + request);
+    }
+
+    if (allHosts.contains(request.getHostname())) {
+      // throw dup error later
+      duplicates.add(request.getHostname());
+      return;
+    }
+    allHosts.add(request.getHostname());
+
+    try {
+      // ensure host is registered
+      clusters.getHost(request.getHostname());
+    }
+    catch (HostNotFoundException e) {
+      unknowns.add(request.getHostname());
+      return;
+    }
+
+    if (request.getClusterName() != null) {
+      try {
+        // validate that cluster_name is valid
+        clusters.getCluster(request.getClusterName());
+      } catch (ClusterNotFoundException e) {
+        throw new ParentObjectNotFoundException("Attempted to add a host to a cluster which doesn't exist: "
+            + " clusterName=" + request.getClusterName());
+      }
+    }
+  }
+
+
+  /**
+   * Add hosts based on a blueprint and hostgroup.  This will create the necessary resources and install/start all
+   * if the components on the hosts.
+   *
+   * @param request  add hosts request
+   * @return async request response
+   *
+   * @throws ResourceAlreadyExistsException  if an added host already exists in the cluster
+   * @throws SystemException                 in an unknown exception occurs
+   * @throws NoSuchParentResourceException   a parent resource doesnt exist
+   * @throws UnsupportedPropertyException    an unsupported property was specified for the request
+   */
+  private RequestStatusResponse addHostsUsingHostgroup(final Request request)
+      throws ResourceAlreadyExistsException,
+      SystemException,
+      NoSuchParentResourceException,
+      UnsupportedPropertyException {
+
+    //todo: idempotency of request.  Need to define failure models ...
+    Set<Map<String, Object>> propertySet = request.getProperties();
+    if (propertySet == null || propertySet.isEmpty()) {
+      LOG.warn("Received a create host request with no associated property sets");
+      return null;
+    }
+
+    Set<String> addedHosts = new HashSet<String>();
+    // all hosts will have same cluster
+    String clusterName = null;
+    for (Map<String, Object> properties : propertySet) {
+      clusterName = (String) properties.get(HOST_CLUSTER_NAME_PROPERTY_ID);
+      String bpName = (String) properties.get(BLUEPRINT_PROPERTY_ID);
+      String hgName = (String) properties.get(HOSTGROUP_PROPERTY_ID);
+      String hostname = getHostNameFromProperties(properties);
+
+      addedHosts.add(hostname);
+
+      String configGroupName = getConfigurationGroupName(bpName, hgName);
+      BlueprintEntity blueprint = getExistingBlueprint(bpName);
+      Stack stack = parseStack(blueprint);
+      Map<String, HostGroupImpl> blueprintHostGroups = parseBlueprintHostGroups(blueprint, stack);
+      addHostToHostgroup(hgName, hostname, blueprintHostGroups);
+      createHostAndComponentResources(blueprintHostGroups, clusterName, this);
+      //todo: optimize: update once per hostgroup with added hosts
+      addHostToExistingConfigGroups(configGroupName, clusterName, hostname);
+    }
+    return ((HostComponentResourceProvider) getResourceProvider(Resource.Type.HostComponent)).
+        installAndStart(clusterName, addedHosts);
+  }
+
+  /**
+   * Add the new host to an existing config group.
+   *
+   * @param configGroupName  name of the config group
+   * @param clusterName      cluster name
+   * @param hostName         host name
+   *
+   * @throws SystemException                an unknown exception occurred
+   * @throws UnsupportedPropertyException   an unsupported property was specified in the request
+   * @throws NoSuchParentResourceException  a parent resource doesn't exist
+   */
+  private void addHostToExistingConfigGroups(String configGroupName, String clusterName, String hostName)
+      throws SystemException,
+      UnsupportedPropertyException,
+      NoSuchParentResourceException {
+
+    Clusters clusters;
+    Cluster cluster;
+    try {
+      clusters = getManagementController().getClusters();
+      cluster = clusters.getCluster(clusterName);
+    } catch (AmbariException e) {
+      throw new IllegalArgumentException(
+          String.format("Attempt to add hosts to a non-existent cluster: '%s'", clusterName));
+    }
+    Map<Long, ConfigGroup> configGroups = cluster.getConfigGroups();
+    for (ConfigGroup group : configGroups.values()) {
+      if (group.getName().equals(configGroupName)) {
+        try {
+          group.addHost(clusters.getHost(hostName));
+          group.persist();
+        } catch (AmbariException e) {
+          // shouldn't occur, this host was just added to the cluster
+          throw new SystemException(String.format(
+              "Unable to obtain newly created host '%s' from cluster '%s'", hostName, clusterName));
+        }
+      }
+    }
+  }
+
+  /**
+   * Associate a host with a host group.
+   *
+   * @param hostGroupName        name of host group
+   * @param hostname             host name
+   * @param blueprintHostGroups  map of host group name to host group
+   *
+   * @throws IllegalArgumentException if the specified host group doesn't exist
+   */
+  private void addHostToHostgroup(String hostGroupName, String hostname, Map<String, HostGroupImpl> blueprintHostGroups)
+      throws IllegalArgumentException {
+
+    HostGroupImpl hostGroup = blueprintHostGroups.get(hostGroupName);
+    if (hostGroup == null) {
+      // this case should have been caught sooner
+      throw new IllegalArgumentException(String.format("Invalid host_group specified '%s'. " +
+          "All request host groups must have a corresponding host group in the specified blueprint", hostGroupName));
+    }
+
+    hostGroup.addHostInfo(hostname);
+  }
+
+
+  protected Set<HostResponse> getHosts(Set<HostRequest> requests) throws AmbariException {
     Set<HostResponse> response = new HashSet<HostResponse>();
 
     AmbariManagementController controller = getManagementController();
@@ -529,9 +696,7 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
     return response;
   }
 
-  protected synchronized void updateHosts(Set<HostRequest> requests,
-      Map<String, String> requestProperties)
-      throws AmbariException {
+  protected synchronized void updateHosts(Set<HostRequest> requests) throws AmbariException {
 
     if (requests.isEmpty()) {
       LOG.warn("Received an empty requests set");
@@ -541,20 +706,15 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
     AmbariManagementController controller = getManagementController();
     Clusters                   clusters   = controller.getClusters();
 
-    // We don't expect batch requests for different clusters, that's why
-    // nothing bad should happen if value is overwritten few times
-    String maintenanceCluster = null;
-
     for (HostRequest request : requests) {
       if (request.getHostname() == null || request.getHostname().isEmpty()) {
         throw new IllegalArgumentException("Invalid arguments, hostname should be provided");
       }
     }
 
-
     for (HostRequest request : requests) {
       if (LOG.isDebugEnabled()) {
-        LOG.debug("Received a updateHost request"
+        LOG.debug("Received an updateHost request"
             + ", hostname=" + request.getHostname()
             + ", request=" + request);
       }
@@ -592,7 +752,6 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
               "maintenance state to one of " + EnumSet.of(MaintenanceState.OFF, MaintenanceState.ON));
           } else {
             h.setMaintenanceState(c.getClusterId(), newState);
-            maintenanceCluster = c.getClusterName();
           }
         }
       }
@@ -710,4 +869,21 @@ public class HostResourceProvider extends AbstractControllerResourceProvider {
       }
     }
   }
+
+  /**
+   * Obtain the hostname from the request properties.  The hostname property name may differ
+   * depending on the request type.  For the low level host resource creation calls, it is always
+   * "Hosts/host_name".  For multi host "add host from hostgroup", the hostname property is a top level
+   * property "host_name".
+   *
+   * @param properties  request properties
+   *
+   * @return the host name for the host request
+   */
+  private String getHostNameFromProperties(Map<String, Object> properties) {
+    String hostname = (String) properties.get(HOST_NAME_PROPERTY_ID);
+
+    return hostname != null ? hostname :
+        (String) properties.get(HOST_NAME_NO_CATEGORY_PROPERTY_ID);
+  }
 }

+ 3 - 4
ambari-server/src/main/java/org/apache/ambari/server/state/Cluster.java

@@ -341,11 +341,10 @@ public interface Cluster {
   public void addConfigGroup(ConfigGroup configGroup) throws AmbariException;
 
   /**
-   * Get all config groups associated with this cluster
-   * @return
-   * @throws AmbariException
+   * Get config groups associated with this cluster
+   * @return unmodifiable map of config group id to config group.  Will not return null.
    */
-  public Map<Long, ConfigGroup> getConfigGroups() throws AmbariException;
+  public Map<Long, ConfigGroup> getConfigGroups();
 
   /**
    * Delete this config group identified by the config group id

+ 1 - 1
ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java

@@ -483,7 +483,7 @@ public class ClusterImpl implements Cluster {
   }
 
   @Override
-  public Map<Long, ConfigGroup> getConfigGroups() throws AmbariException {
+  public Map<Long, ConfigGroup> getConfigGroups() {
     loadConfigGroups();
     clusterGlobalLock.readLock().lock();
     try {

+ 75 - 70
ambari-server/src/test/java/org/apache/ambari/server/controller/AmbariManagementControllerTest.java

@@ -77,6 +77,7 @@ 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.ComponentResourceProviderTest;
+import org.apache.ambari.server.controller.internal.HostComponentResourceProviderTest;
 import org.apache.ambari.server.controller.internal.HostResourceProviderTest;
 import org.apache.ambari.server.controller.internal.RequestOperationLevel;
 import org.apache.ambari.server.controller.internal.RequestResourceFilter;
@@ -365,7 +366,7 @@ public class AmbariManagementControllerTest {
   }
 
   private long stopServiceComponentHosts(String clusterName,
-      String serviceName) throws AmbariException {
+      String serviceName) throws Exception {
     Cluster c = clusters.getCluster(clusterName);
     Service s = c.getService(serviceName);
     Set<ServiceComponentHostRequest> requests = new
@@ -380,8 +381,8 @@ public class AmbariManagementControllerTest {
     }
     Map<String, String> mapRequestProps = new HashMap<String, String>();
     mapRequestProps.put("context", "Called from a test");
-    RequestStatusResponse resp = controller.updateHostComponents(requests,
-      mapRequestProps, false);
+    RequestStatusResponse resp = HostComponentResourceProviderTest.updateHostComponents(controller, injector, requests,
+        mapRequestProps, false);
 
     // manually change live state to started as no running action manager
     for (ServiceComponent sc :
@@ -1505,16 +1506,11 @@ public class AmbariManagementControllerTest {
     clusters.getHost("h2").persist();
     clusters.getHost("h3").persist();
 
-    Map<String, String> hostAttrs =
-        new HashMap<String, String>();
-    hostAttrs.put("attr1", "val1");
-    hostAttrs.put("attr2", "val2");
-
-    String clusterName = "c1";
+   String clusterName = "c1";
 
     HostRequest r1 = new HostRequest("h1", clusterName, null);
-    HostRequest r2 = new HostRequest("h2", clusterName, hostAttrs);
-    HostRequest r3 = new HostRequest("h3", null, hostAttrs);
+    HostRequest r2 = new HostRequest("h2", clusterName, null);
+    HostRequest r3 = new HostRequest("h3", null, null);
 
     Set<HostRequest> set1 = new HashSet<HostRequest>();
     set1.add(r1);
@@ -1525,13 +1521,6 @@ public class AmbariManagementControllerTest {
     Assert.assertEquals(1, clusters.getClustersForHost("h1").size());
     Assert.assertEquals(1, clusters.getClustersForHost("h2").size());
     Assert.assertEquals(0, clusters.getClustersForHost("h3").size());
-
-    Assert.assertEquals(4, clusters.getHost("h2").getHostAttributes().size());
-    Assert.assertEquals(2, clusters.getHost("h3").getHostAttributes().size());
-    Assert.assertEquals("val1",
-        clusters.getHost("h2").getHostAttributes().get("attr1"));
-    Assert.assertEquals("val2",
-        clusters.getHost("h2").getHostAttributes().get("attr2"));
   }
 
   @Test
@@ -2334,7 +2323,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testServiceComponentHostsWithDecommissioned() throws AmbariException {
+  public void testServiceComponentHostsWithDecommissioned() throws Exception {
 
     final String host1 = "h1";
     final String host2 = "h2";
@@ -2408,7 +2397,7 @@ public class AmbariManagementControllerTest {
     r = new ServiceComponentHostRequest(clusterName, "HDFS", "DATANODE", host2, null);
     r.setAdminState("DECOMMISSIONED");
     try {
-      controller.updateHostComponents(Collections.singleton(r), new HashMap<String, String>(), false);
+      updateHostComponents(Collections.singleton(r), new HashMap<String, String>(), false);
       Assert.fail("Must throw exception when decommission attribute is updated.");
     } catch (IllegalArgumentException ex) {
       Assert.assertTrue(ex.getMessage().contains("Property adminState cannot be modified through update"));
@@ -3363,7 +3352,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testServiceComponentHostUpdateRecursive() throws AmbariException {
+  public void testServiceComponentHostUpdateRecursive() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     String serviceName1 = "HDFS";
@@ -3444,7 +3433,7 @@ public class AmbariManagementControllerTest {
           componentName1, host1,
           State.STARTED.toString());
       reqs.add(req1);
-      controller.updateHostComponents(reqs, Collections.<String, String>emptyMap(), true);
+      updateHostComponents(reqs, Collections.<String, String>emptyMap(), true);
       fail("Expected failure for invalid transition");
     } catch (Exception e) {
       // Expected
@@ -3472,7 +3461,7 @@ public class AmbariManagementControllerTest {
       reqs.add(req3);
       reqs.add(req4);
       reqs.add(req5);
-      controller.updateHostComponents(reqs, Collections.<String, String>emptyMap(), true);
+      updateHostComponents(reqs, Collections.<String, String>emptyMap(), true);
       fail("Expected failure for invalid states");
     } catch (Exception e) {
       // Expected
@@ -3494,7 +3483,7 @@ public class AmbariManagementControllerTest {
     reqs.add(req3);
     reqs.add(req4);
     reqs.add(req5);
-    RequestStatusResponse trackAction = controller.updateHostComponents(reqs,
+    RequestStatusResponse trackAction = updateHostComponents(reqs,
         Collections.<String, String>emptyMap(), true);
     Assert.assertNotNull(trackAction);
 
@@ -3527,14 +3516,14 @@ public class AmbariManagementControllerTest {
         State.INSTALLED.toString());
     reqs.add(req1);
     reqs.add(req2);
-    trackAction = controller.updateHostComponents(reqs, Collections.<String,
+    trackAction = updateHostComponents(reqs, Collections.<String,
         String>emptyMap(), true);
     Assert.assertNull(trackAction);
   }
 
   @Ignore
   @Test
-  public void testServiceComponentHostUpdateStackId() throws AmbariException {
+  public void testServiceComponentHostUpdateStackId() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     String serviceName1 = "HDFS";
@@ -3614,7 +3603,7 @@ public class AmbariManagementControllerTest {
     Map<String,String> mapRequestProps = new HashMap<String, String>();
     mapRequestProps.put("context", "testServiceComponentHostUpdateStackId");
 
-    RequestStatusResponse resp = controller.updateHostComponents(reqs, mapRequestProps, true);
+    RequestStatusResponse resp = updateHostComponents(reqs, mapRequestProps, true);
     List<Stage> stages = actionDB.getAllStages(resp.getRequestId());
     Assert.assertEquals(1, stages.size());
     Assert.assertEquals(2, stages.get(0).getOrderedHostRoleCommands().size());
@@ -3659,7 +3648,7 @@ public class AmbariManagementControllerTest {
     req3.setDesiredStackId("HDP-0.2");
     reqs.add(req3);
 
-    resp = controller.updateHostComponents(reqs, Collections.<String, String>emptyMap(), true);
+    resp = updateHostComponents(reqs, Collections.<String, String>emptyMap(), true);
     stages = actionDB.getAllStages(resp.getRequestId());
     Assert.assertEquals(2, stages.size());
     Assert.assertEquals(2, stages.get(0).getOrderedHostRoleCommands().size());
@@ -3687,7 +3676,7 @@ public class AmbariManagementControllerTest {
 
   @Ignore
   @Test
-  public void testServiceComponentHostUpdateStackIdError() throws AmbariException {
+  public void testServiceComponentHostUpdateStackIdError() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     String serviceName1 = "HDFS";
@@ -3818,7 +3807,7 @@ public class AmbariManagementControllerTest {
     req1.setDesiredStackId("HDP-0.2");
     reqs.add(req1);
 
-    RequestStatusResponse resp = controller.updateHostComponents(reqs,
+    RequestStatusResponse resp = updateHostComponents(reqs,
         Collections.<String,String>emptyMap(), true);
     Assert.assertNull(resp);
 
@@ -3831,14 +3820,14 @@ public class AmbariManagementControllerTest {
         State.INSTALLED.toString());
     req1.setDesiredStackId("HDP-0.2");
     reqs.add(req1);
-    resp = controller.updateHostComponents(reqs, Collections.<String,String>emptyMap(), true);
+    resp = updateHostComponents(reqs, Collections.<String,String>emptyMap(), true);
     Assert.assertNull(resp);
   }
 
   private void updateHostAndCompareExpectedFailure(Set<ServiceComponentHostRequest> reqs,
                                                    String expectedMessage) {
     try {
-      controller.updateHostComponents(reqs, Collections.<String,String>emptyMap(), true);
+      updateHostComponents(reqs, Collections.<String,String>emptyMap(), true);
       fail("Expected failure: " + expectedMessage);
     } catch (Exception e) {
       LOG.info("Actual exception message: " + e.getMessage());
@@ -4758,7 +4747,7 @@ public class AmbariManagementControllerTest {
     schReqs.clear();
     schReqs.add(new ServiceComponentHostRequest(clusterName, serviceName,
             componentName1, host1, null));
-    Assert.assertNull(controller.updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
+    Assert.assertNull(updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
 
     configVersions.clear();
     configVersions.put("typeC", "v1");
@@ -4785,7 +4774,7 @@ public class AmbariManagementControllerTest {
     schReqs.clear();
     schReqs.add(new ServiceComponentHostRequest(clusterName, serviceName,
             componentName1, host1, null));
-    Assert.assertNull(controller.updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
+    Assert.assertNull(updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
 
     // update configs at SC level
     configVersions.clear();
@@ -4799,7 +4788,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testConfigUpdates() throws AmbariException {
+  public void testConfigUpdates() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     clusters.getCluster(clusterName)
@@ -4906,7 +4895,7 @@ public class AmbariManagementControllerTest {
     schReqs.clear();
     schReqs.add(new ServiceComponentHostRequest(clusterName, serviceName,
         componentName1, host1, null));
-    Assert.assertNull(controller.updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
+    Assert.assertNull(updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
 
     configVersions.clear();
     configVersions.put("typeC", "v1");
@@ -4933,7 +4922,7 @@ public class AmbariManagementControllerTest {
     schReqs.clear();
     schReqs.add(new ServiceComponentHostRequest(clusterName, serviceName,
         componentName1, host1, null));
-    Assert.assertNull(controller.updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
+    Assert.assertNull(updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
 
     // update configs at SC level
     configVersions.clear();
@@ -5042,7 +5031,7 @@ public class AmbariManagementControllerTest {
     schReqs.clear();
     schReqs.add(new ServiceComponentHostRequest(clusterName, serviceName,
       componentName1, host1, null));
-    Assert.assertNull(controller.updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
+    Assert.assertNull(updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
 
     // Reconfigure SCH level
     configVersions.clear();
@@ -5050,7 +5039,7 @@ public class AmbariManagementControllerTest {
     schReqs.clear();
     schReqs.add(new ServiceComponentHostRequest(clusterName, serviceName,
       componentName1, host1, null));
-    Assert.assertNull(controller.updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
+    Assert.assertNull(updateHostComponents(schReqs, Collections.<String, String>emptyMap(), true));
 
     // Clear Entity Manager
     entityManager.clear();
@@ -5284,8 +5273,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testReconfigureClientWithServiceStarted() throws
-    AmbariException {
+  public void testReconfigureClientWithServiceStarted() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     clusters.getCluster(clusterName)
@@ -5774,7 +5762,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testReInstallClientComponent() throws AmbariException {
+  public void testReInstallClientComponent() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     clusters.getCluster(clusterName)
@@ -5818,7 +5806,7 @@ public class AmbariManagementControllerTest {
     Set<ServiceComponentHostRequest> setReqs = new
       HashSet<ServiceComponentHostRequest>();
     setReqs.add(schr);
-    RequestStatusResponse resp = controller.updateHostComponents(setReqs,
+    RequestStatusResponse resp = updateHostComponents(setReqs,
       Collections.<String, String>emptyMap(), false);
 
     Assert.assertNotNull(resp);
@@ -5960,7 +5948,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testDecommissonDatanodeAction() throws AmbariException {
+  public void testDecommissonDatanodeAction() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     clusters.getCluster(clusterName)
@@ -6087,13 +6075,13 @@ public class AmbariManagementControllerTest {
         componentName1, host2, State.INSTALLED.toString());
     Set<ServiceComponentHostRequest> requests = new HashSet<ServiceComponentHostRequest>();
     requests.add(r);
-    controller.updateHostComponents(requests, Collections.<String, String>emptyMap(), true);
+    updateHostComponents(requests, Collections.<String, String>emptyMap(), true);
     s.getServiceComponent(componentName1).getServiceComponentHost(host2).setState(State.INSTALLED);
     r = new ServiceComponentHostRequest(clusterName, serviceName,
         componentName1, host2, State.STARTED.toString());
     requests.clear();
     requests.add(r);
-    controller.updateHostComponents(requests, Collections.<String, String>emptyMap(), true);
+    updateHostComponents(requests, Collections.<String, String>emptyMap(), true);
     s.getServiceComponent(componentName1).getServiceComponentHost(host2).setState(State.STARTED);
 
     params = new HashMap<String, String>(){{
@@ -7616,7 +7604,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testServiceStopWhileStopping() throws AmbariException {
+  public void testServiceStopWhileStopping() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     clusters.getCluster(clusterName)
@@ -7733,7 +7721,7 @@ public class AmbariManagementControllerTest {
           Set<ServiceComponentHostRequest> reqs1 = new
             HashSet<ServiceComponentHostRequest>();
           reqs1.add(r1);
-          controller.updateHostComponents(reqs1, Collections.<String, String>emptyMap(), true);
+          updateHostComponents(reqs1, Collections.<String, String>emptyMap(), true);
           Assert.assertEquals(State.INSTALLED, sch.getDesiredState());
         }
       }
@@ -7890,7 +7878,7 @@ public class AmbariManagementControllerTest {
   }
 
   @Test
-  public void testUpdateHostComponentsBadState() throws AmbariException {
+  public void testUpdateHostComponentsBadState() throws Exception {
     String clusterName = "foo1";
     createCluster(clusterName);
     clusters.getCluster(clusterName)
@@ -7971,7 +7959,7 @@ public class AmbariManagementControllerTest {
     Map<String, String> requestProps = new HashMap<String, String>();
     requestProps.put("datanode", "dn_value");
     requestProps.put("namenode", "nn_value");
-    RequestStatusResponse rsr = controller.updateHostComponents(Collections.singleton(schr), requestProps, false);
+    RequestStatusResponse rsr = updateHostComponents(Collections.singleton(schr), requestProps, false);
 
     List<Stage> stages = actionDB.getAllStages(rsr.getRequestId());
     Assert.assertEquals(1, stages.size());
@@ -7988,7 +7976,6 @@ public class AmbariManagementControllerTest {
     for (ServiceComponentHost sch : clusters.getCluster(clusterName).getServiceComponentHosts(host2)) {
       Assert.assertEquals(State.UNKNOWN, sch.getState());
     }
-
   }
 
   @Test
@@ -8452,7 +8439,7 @@ public class AmbariManagementControllerTest {
     // disable HC for non-clients
     schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName1, host1, "DISABLED"));
     schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName2, host1, "DISABLED"));
-    controller.updateHostComponents(schRequests, new HashMap<String,String>(), false);
+    updateHostComponents(schRequests, new HashMap<String,String>(), false);
 
     // delete HC
     schRequests.clear();
@@ -8635,7 +8622,7 @@ public class AmbariManagementControllerTest {
     schRequests.clear();
     // disable HC, DN was already stopped
     schRequests.add(new ServiceComponentHostRequest(clusterName, serviceName, componentName1, host1, "DISABLED"));
-    controller.updateHostComponents(schRequests, new HashMap<String,String>(), false);
+    updateHostComponents(schRequests, new HashMap<String,String>(), false);
 
     // delete HC
     schRequests.clear();
@@ -9003,21 +8990,21 @@ public class AmbariManagementControllerTest {
       componentHostRequests.clear();
       componentHostRequests.add(new ServiceComponentHostRequest("c1", null, "NAMENODE", "host1", "DISABLED"));
 
-      amc.updateHostComponents(componentHostRequests, mapRequestProps, true);
+      updateHostComponents(amc, componentHostRequests, mapRequestProps, true);
 
       Assert.assertEquals(State.DISABLED, componentHost.getState());
 
       componentHostRequests.clear();
       componentHostRequests.add(new ServiceComponentHostRequest("c1", null, "NAMENODE", "host1", "INSTALLED"));
 
-      amc.updateHostComponents(componentHostRequests, mapRequestProps, true);
+      updateHostComponents(amc, componentHostRequests, mapRequestProps, true);
 
       Assert.assertEquals(State.INSTALLED, componentHost.getState());
 
       componentHostRequests.clear();
       componentHostRequests.add(new ServiceComponentHostRequest("c1", null, "NAMENODE", "host1", "DISABLED"));
 
-      amc.updateHostComponents(componentHostRequests, mapRequestProps, true);
+      updateHostComponents(amc, componentHostRequests, mapRequestProps, true);
 
       Assert.assertEquals(State.DISABLED, componentHost.getState());
 
@@ -9029,7 +9016,7 @@ public class AmbariManagementControllerTest {
       componentHostRequests.clear();
       componentHostRequests.add(new ServiceComponentHostRequest("c1", null, "NAMENODE", "host2", "INSTALLED"));
 
-      amc.updateHostComponents(componentHostRequests, mapRequestProps, true);
+      updateHostComponents(amc, componentHostRequests, mapRequestProps, true);
 
       namenodes = cluster.getService("HDFS").getServiceComponent("NAMENODE").getServiceComponentHosts();
       Assert.assertEquals(2, namenodes.size());
@@ -9071,7 +9058,7 @@ public class AmbariManagementControllerTest {
       componentHost.handleEvent(new ServiceComponentHostOpSucceededEvent(componentHost.getServiceComponentName(), componentHost.getHostName(), System.currentTimeMillis()));
       componentHostRequests.clear();
       componentHostRequests.add(new ServiceComponentHostRequest("c1", null, "NAMENODE", "host1", "INSTALLED"));
-      amc.updateHostComponents(componentHostRequests, mapRequestProps, true);
+      updateHostComponents(amc, componentHostRequests, mapRequestProps, true);
       assertEquals(State.INSTALLED, namenodes.get("host1").getState());
 
       // make unknown
@@ -9087,7 +9074,7 @@ public class AmbariManagementControllerTest {
       // make disabled
       componentHostRequests.clear();
       componentHostRequests.add(new ServiceComponentHostRequest("c1", null, "DATANODE", "host2", "DISABLED"));
-      amc.updateHostComponents(componentHostRequests, mapRequestProps, false);
+      updateHostComponents(amc, componentHostRequests, mapRequestProps, false);
       org.junit.Assert.assertEquals(State.DISABLED, sch.getState());
 
       // ServiceComponentHost remains in disabled after service stop
@@ -9770,8 +9757,8 @@ public class AmbariManagementControllerTest {
     // passivate a host
     HostRequest hr = new HostRequest(host1, clusterName, requestProperties);
     hr.setMaintenanceState(MaintenanceState.ON.name());
-    HostResourceProviderTest.updateHosts(controller, Collections.singleton(hr),
-        new HashMap<String, String>());
+    HostResourceProviderTest.updateHosts(controller, Collections.singleton(hr)
+    );
 
     Host host = hosts.get(host1);
     Assert.assertEquals(MaintenanceState.ON,
@@ -9793,8 +9780,8 @@ public class AmbariManagementControllerTest {
 
     // reset
     hr.setMaintenanceState(MaintenanceState.OFF.name());
-    HostResourceProviderTest.updateHosts(controller, Collections.singleton(hr),
-        new HashMap<String, String>());
+    HostResourceProviderTest.updateHosts(controller, Collections.singleton(hr)
+    );
 
     host = hosts.get(host1);
     Assert.assertEquals(MaintenanceState.OFF,
@@ -9817,8 +9804,8 @@ public class AmbariManagementControllerTest {
     Set<HostRequest> set = new HashSet<HostRequest>();
     set.add(hr1);
     set.add(hr2);
-    HostResourceProviderTest.updateHosts(controller, set,
-        new HashMap<String, String>());
+    HostResourceProviderTest.updateHosts(controller, set
+    );
 
     host = hosts.get(host1);
     Assert.assertEquals(MaintenanceState.ON,
@@ -9836,8 +9823,8 @@ public class AmbariManagementControllerTest {
     set.add(hr1);
     set.add(hr2);
 
-    HostResourceProviderTest.updateHosts(controller, set,
-        new HashMap<String, String>());
+    HostResourceProviderTest.updateHosts(controller, set
+    );
     host = hosts.get(host1);
     Assert.assertEquals(MaintenanceState.OFF,
         host.getMaintenanceState(cluster.getClusterId()));
@@ -10334,7 +10321,7 @@ public class AmbariManagementControllerTest {
 
     Map<String, String> requestProperties = new HashMap<String, String>();
     requestProperties.put("namenode", "p1");
-    RequestStatusResponse resp = controller.updateHostComponents(Collections.singleton(req), requestProperties, false);
+    RequestStatusResponse resp = updateHostComponents(Collections.singleton(req), requestProperties, false);
 
     // succeed in creating a task
     assertNotNull(resp);
@@ -10346,15 +10333,33 @@ public class AmbariManagementControllerTest {
     }
 
     // no new commands since no targeted info
-    resp = controller.updateHostComponents(Collections.singleton(req), new HashMap<String, String>(), false);
+    resp = updateHostComponents(Collections.singleton(req), new HashMap<String, String>(), false);
     assertNull(resp);
 
     // role commands added for targeted command
-    resp = controller.updateHostComponents(Collections.singleton(req), requestProperties, false);
+    resp = updateHostComponents(Collections.singleton(req), requestProperties, false);
     assertNotNull(resp);
 
   }
 
+  // this is a temporary measure as a result of moving updateHostComponents from AmbariManagementController
+  // to HostComponentResourceProvider.  Eventually the tests should be moved out of this class.
+  private RequestStatusResponse updateHostComponents(Set<ServiceComponentHostRequest> requests,
+                                                     Map<String, String> requestProperties,
+                                                     boolean runSmokeTest) throws Exception {
+
+    return updateHostComponents(controller, requests, requestProperties, runSmokeTest);
+  }
+
+  private RequestStatusResponse updateHostComponents(AmbariManagementController controller,
+                                                     Set<ServiceComponentHostRequest> requests,
+                                                     Map<String, String> requestProperties,
+                                                     boolean runSmokeTest) throws Exception {
+
+    return HostComponentResourceProviderTest.updateHostComponents(
+        controller, injector, requests, requestProperties, runSmokeTest);
+  }
+
 
 }
 

+ 6 - 3
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/ClusterResourceProviderTest.java

@@ -420,6 +420,7 @@ public class ClusterResourceProviderTest {
     expect(blueprint.getHostGroups()).andReturn(Collections.singleton(hostGroup)).anyTimes();
     expect(hostGroup.getName()).andReturn("group1").anyTimes();
     expect(hostGroup.getComponents()).andReturn(hostGroupComponents).anyTimes();
+    expect(hostGroup.getBlueprintName()).andReturn(blueprintName).anyTimes();
     expect(hostGroupComponent1.getName()).andReturn("component1").anyTimes();
     expect(hostGroupComponent2.getName()).andReturn("component2").anyTimes();
     expect(hostGroupComponent3.getName()).andReturn("component3").anyTimes();
@@ -634,7 +635,7 @@ public class ClusterResourceProviderTest {
     assertEquals(1, configGroupRequests.size());
     ConfigGroupRequest configGroupRequest = configGroupRequests.iterator().next();
     assertEquals(clusterName, configGroupRequest.getClusterName());
-    assertEquals("group1", configGroupRequest.getGroupName());
+    assertEquals(blueprintName + ":group1", configGroupRequest.getGroupName());
     assertEquals("service1", configGroupRequest.getTag());
     assertEquals("Host Group Configuration", configGroupRequest.getDescription());
     Set<String> hosts = configGroupRequest.getHosts();
@@ -1688,6 +1689,7 @@ public class ClusterResourceProviderTest {
 
     expect(blueprint.getHostGroups()).andReturn(Collections.singleton(hostGroup)).anyTimes();
     expect(hostGroup.getName()).andReturn("group1").anyTimes();
+    expect(hostGroup.getBlueprintName()).andReturn(blueprintName).anyTimes();
     expect(hostGroup.getComponents()).andReturn(hostGroupComponents).anyTimes();
     expect(hostGroupComponent1.getName()).andReturn("component1").anyTimes();
     expect(hostGroupComponent2.getName()).andReturn("component2").anyTimes();
@@ -1886,7 +1888,7 @@ public class ClusterResourceProviderTest {
     assertEquals(1, configGroupRequests.size());
     ConfigGroupRequest configGroupRequest = configGroupRequests.iterator().next();
     assertEquals(clusterName, configGroupRequest.getClusterName());
-    assertEquals("group1", configGroupRequest.getGroupName());
+    assertEquals(blueprintName + ":group1", configGroupRequest.getGroupName());
     assertEquals("service1", configGroupRequest.getTag());
     assertEquals("Host Group Configuration", configGroupRequest.getDescription());
     Set<String> hosts = configGroupRequest.getHosts();
@@ -2392,6 +2394,7 @@ public class ClusterResourceProviderTest {
 
     expect(blueprint.getHostGroups()).andReturn(Collections.singleton(hostGroup)).anyTimes();
     expect(hostGroup.getName()).andReturn("group1").anyTimes();
+    expect(hostGroup.getBlueprintName()).andReturn(blueprintName).anyTimes();
     expect(hostGroup.getComponents()).andReturn(hostGroupComponents).anyTimes();
     expect(hostGroupComponent1.getName()).andReturn("component1").anyTimes();
     expect(hostGroupComponent2.getName()).andReturn("component2").anyTimes();
@@ -2616,7 +2619,7 @@ public class ClusterResourceProviderTest {
     assertEquals(1, configGroupRequests.size());
     ConfigGroupRequest configGroupRequest = configGroupRequests.iterator().next();
     assertEquals(clusterName, configGroupRequest.getClusterName());
-    assertEquals("group1", configGroupRequest.getGroupName());
+    assertEquals(blueprintName + ":group1", configGroupRequest.getGroupName());
     assertEquals("service1", configGroupRequest.getTag());
     assertEquals("Host Group Configuration", configGroupRequest.getDescription());
     Set<String> hosts = configGroupRequest.getHosts();

+ 204 - 16
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostComponentResourceProviderTest.java

@@ -19,30 +19,40 @@
 package org.apache.ambari.server.controller.internal;
 
 import com.google.inject.Injector;
+import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.MaintenanceStateHelper;
 import org.apache.ambari.server.controller.RequestStatusResponse;
 import org.apache.ambari.server.controller.ResourceProviderFactory;
 import org.apache.ambari.server.controller.ServiceComponentHostRequest;
 import org.apache.ambari.server.controller.ServiceComponentHostResponse;
 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.ResourceProvider;
 import org.apache.ambari.server.controller.utilities.PredicateBuilder;
 import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.Service;
+import org.apache.ambari.server.state.ServiceComponent;
+import org.apache.ambari.server.state.ServiceComponentHost;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.State;
-import org.apache.ambari.server.state.cluster.ClustersImpl;
 import org.easymock.EasyMock;
 import org.junit.Assert;
 import org.junit.Test;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 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;
 
@@ -53,6 +63,9 @@ 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.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
 
 /**
  * HostComponentResourceProvider tests.
@@ -241,6 +254,14 @@ public class HostComponentResourceProviderTest {
     RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
     ResourceProviderFactory resourceProviderFactory = createNiceMock(ResourceProviderFactory.class);
     Injector injector = createNiceMock(Injector.class);
+    Clusters clusters = createNiceMock(Clusters.class);
+    Cluster cluster = createNiceMock(Cluster.class);
+    Service service = createNiceMock(Service.class);
+    ServiceComponent component = createNiceMock(ServiceComponent.class);
+    ServiceComponentHost componentHost = createNiceMock(ServiceComponentHost.class);
+    RequestStageContainer stageContainer = createNiceMock(RequestStageContainer.class);
+    MaintenanceStateHelper maintenanceStateHelper = createNiceMock(MaintenanceStateHelper.class);
+
 
     Map<String, String> mapRequestProps = new HashMap<String, String>();
     mapRequestProps.put("context", "Called from a test");
@@ -248,27 +269,49 @@ public class HostComponentResourceProviderTest {
     Set<ServiceComponentHostResponse> nameResponse = new HashSet<ServiceComponentHostResponse>();
     nameResponse.add(new ServiceComponentHostResponse(
         "Cluster102", "Service100", "Component100", "Host100", "STARTED", "", "", "", null));
-    
-    HostComponentResourceProvider provider = 
-        new HostComponentResourceProvider(PropertyHelper.getPropertyIds(type),
-        PropertyHelper.getKeyPropertyIds(type),
-        managementController, injector);
 
     // set expectations
+    expect(managementController.getClusters()).andReturn(clusters).anyTimes();
+    expect(managementController.findServiceName(cluster, "Component100")).andReturn("Service100").anyTimes();
+    expect(clusters.getCluster("Cluster102")).andReturn(cluster).anyTimes();
+    expect(cluster.getService("Service100")).andReturn(service).anyTimes();
+    expect(service.getServiceComponent("Component100")).andReturn(component).anyTimes();
+    expect(component.getServiceComponentHost("Host100")).andReturn(componentHost).anyTimes();
+    expect(component.getName()).andReturn("Component100").anyTimes();
+    expect(componentHost.getState()).andReturn(State.INSTALLED).anyTimes();
+    expect(response.getMessage()).andReturn("response msg").anyTimes();
+    expect(response.getRequestId()).andReturn(1000L);
+
+    //Cluster is default type.  Maintenance mode is not being tested here so the default is returned.
+    expect(maintenanceStateHelper.isOperationAllowed(Resource.Type.Cluster, componentHost)).andReturn(true).anyTimes();
+
     expect(managementController.getHostComponents(
         EasyMock.<Set<ServiceComponentHostRequest>>anyObject())).andReturn(nameResponse).once();
-    expect(managementController.updateHostComponents(
-        AbstractResourceProviderTest.Matcher.getHostComponentRequestSet(
-            "Cluster102", "Service100", "Component100", "Host100", null, "STARTED"),
-            eq(mapRequestProps), eq(false))).andReturn(response).once();
-    
+
+    Map<String, Map<State, List<ServiceComponentHost>>> changedHosts = new HashMap<String, Map<State, List<ServiceComponentHost>>>();
+    List<ServiceComponentHost> changedComponentHosts = new ArrayList<ServiceComponentHost>();
+    changedComponentHosts.add(componentHost);
+    changedHosts.put("Component100", Collections.singletonMap(State.STARTED, changedComponentHosts));
+
+    expect(managementController.addStages(null, cluster, mapRequestProps, null, null, null, changedHosts,
+        Collections.<ServiceComponentHost>emptyList(), false, false)).andReturn(stageContainer).once();
+
+    stageContainer.persist();
+    expect(stageContainer.getRequestStatusResponse()).andReturn(response).once();
+
+    HostComponentResourceProvider provider =
+        new TestHostComponentResourceProvider(PropertyHelper.getPropertyIds(type),
+            PropertyHelper.getKeyPropertyIds(type),
+            managementController, injector, maintenanceStateHelper);
+
     expect(resourceProviderFactory.getHostComponentResourceProvider(anyObject(Set.class),
         anyObject(Map.class),
         eq(managementController))).
         andReturn(provider).anyTimes();
 
     // replay
-    replay(managementController, response, resourceProviderFactory);
+    replay(managementController, response, resourceProviderFactory, clusters, cluster, service,
+        component, componentHost, stageContainer, maintenanceStateHelper);
 
     Map<String, Object> properties = new LinkedHashMap<String, Object>();
 
@@ -279,11 +322,116 @@ public class HostComponentResourceProviderTest {
 
     // update the cluster named Cluster102
     Predicate predicate = new PredicateBuilder().property(
-        HostComponentResourceProvider.HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID).equals("Cluster102").toPredicate();
-    provider.updateResources(request, predicate);
+        HostComponentResourceProvider.HOST_COMPONENT_CLUSTER_NAME_PROPERTY_ID).equals("Cluster102").and().
+        property(HostComponentResourceProvider.HOST_COMPONENT_HOST_NAME_PROPERTY_ID).equals("Host100").and().
+        property(HostComponentResourceProvider.HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID).equals("Component100").toPredicate();
+    RequestStatus requestStatus = provider.updateResources(request, predicate);
+    Resource responseResource = requestStatus.getRequestResource();
+    assertEquals("response msg", responseResource.getPropertyValue(PropertyHelper.getPropertyId("Requests", "message")));
+    assertEquals(1000L, responseResource.getPropertyValue(PropertyHelper.getPropertyId("Requests", "id")));
+    assertEquals("InProgress", responseResource.getPropertyValue(PropertyHelper.getPropertyId("Requests", "status")));
+    assertTrue(requestStatus.getAssociatedResources().isEmpty());
 
     // verify
-    verify(managementController, response, resourceProviderFactory);
+    verify(managementController, response, resourceProviderFactory, stageContainer);
+  }
+
+  @Test
+  public void testInstallAndStart() throws Exception {
+    Resource.Type type = Resource.Type.HostComponent;
+
+    AmbariManagementController managementController = createMock(AmbariManagementController.class);
+    RequestStatusResponse response = createNiceMock(RequestStatusResponse.class);
+    ResourceProviderFactory resourceProviderFactory = createNiceMock(ResourceProviderFactory.class);
+    Injector injector = createNiceMock(Injector.class);
+    Clusters clusters = createNiceMock(Clusters.class);
+    Cluster cluster = createNiceMock(Cluster.class);
+    Service service = createNiceMock(Service.class);
+    ServiceComponent component = createNiceMock(ServiceComponent.class);
+    ServiceComponentHost componentHost = createNiceMock(ServiceComponentHost.class);
+    RequestStageContainer stageContainer = createNiceMock(RequestStageContainer.class);
+    MaintenanceStateHelper maintenanceStateHelper = createNiceMock(MaintenanceStateHelper.class);
+
+    Collection<String> hosts = new HashSet<String>();
+    hosts.add("Host100");
+
+    Map<String, String> mapRequestProps = new HashMap<String, String>();
+    mapRequestProps.put("context", "Install and start components on added hosts");
+
+    Set<ServiceComponentHostResponse> nameResponse = new HashSet<ServiceComponentHostResponse>();
+    nameResponse.add(new ServiceComponentHostResponse(
+        "Cluster102", "Service100", "Component100", "Host100", "INIT", "", "INIT", "", null));
+    Set<ServiceComponentHostResponse> nameResponse2 = new HashSet<ServiceComponentHostResponse>();
+    nameResponse2.add(new ServiceComponentHostResponse(
+        "Cluster102", "Service100", "Component100", "Host100", "INIT", "", "INSTALLED", "", null));
+
+
+    // set expectations
+    expect(managementController.getClusters()).andReturn(clusters).anyTimes();
+    expect(managementController.findServiceName(cluster, "Component100")).andReturn("Service100").anyTimes();
+    expect(clusters.getCluster("Cluster102")).andReturn(cluster).anyTimes();
+    expect(cluster.getService("Service100")).andReturn(service).anyTimes();
+    expect(service.getServiceComponent("Component100")).andReturn(component).anyTimes();
+    expect(component.getServiceComponentHost("Host100")).andReturn(componentHost).anyTimes();
+    expect(component.getName()).andReturn("Component100").anyTimes();
+    // actual state is always INIT until stages actually execute
+    expect(componentHost.getState()).andReturn(State.INIT).anyTimes();
+    expect(componentHost.getHostName()).andReturn("Host100").anyTimes();
+    expect(componentHost.getServiceComponentName()).andReturn("Component100").anyTimes();
+    expect(response.getMessage()).andReturn("response msg").anyTimes();
+    //expect(response.getRequestId()).andReturn(1000L);
+
+    //Cluster is default type.  Maintenance mode is not being tested here so the default is returned.
+    expect(maintenanceStateHelper.isOperationAllowed(Resource.Type.Cluster, componentHost)).andReturn(true).anyTimes();
+
+    //todo: can we change to prevent having to call twice?
+    expect(managementController.getHostComponents(
+        EasyMock.<Set<ServiceComponentHostRequest>>anyObject())).andReturn(nameResponse);
+    expect(managementController.getHostComponents(
+        EasyMock.<Set<ServiceComponentHostRequest>>anyObject())).andReturn(nameResponse2);
+
+    Map<String, Map<State, List<ServiceComponentHost>>> changedHosts =
+        new HashMap<String, Map<State, List<ServiceComponentHost>>>();
+    List<ServiceComponentHost> changedComponentHosts = Collections.singletonList(componentHost);
+    changedHosts.put("Component100", Collections.singletonMap(State.INSTALLED, changedComponentHosts));
+
+    Map<String, Map<State, List<ServiceComponentHost>>> changedHosts2 =
+        new HashMap<String, Map<State, List<ServiceComponentHost>>>();
+    List<ServiceComponentHost> changedComponentHosts2 = Collections.singletonList(componentHost);
+    changedHosts2.put("Component100", Collections.singletonMap(State.STARTED, changedComponentHosts2));
+
+    expect(managementController.addStages(null, cluster, mapRequestProps, null, null, null, changedHosts,
+        Collections.<ServiceComponentHost>emptyList(), false, false)).andReturn(stageContainer).once();
+
+    expect(managementController.addStages(stageContainer, cluster, mapRequestProps, null, null, null, changedHosts2,
+        Collections.<ServiceComponentHost>emptyList(), false, false)).andReturn(stageContainer).once();
+
+    stageContainer.persist();
+    expect(stageContainer.getProjectedState("Host100", "Component100")).andReturn(State.INSTALLED).once();
+    expect(stageContainer.getRequestStatusResponse()).andReturn(response).once();
+
+    HostComponentResourceProvider provider =
+        new TestHostComponentResourceProvider(PropertyHelper.getPropertyIds(type),
+            PropertyHelper.getKeyPropertyIds(type),
+            managementController, injector, maintenanceStateHelper);
+
+    expect(resourceProviderFactory.getHostComponentResourceProvider(anyObject(Set.class),
+        anyObject(Map.class),
+        eq(managementController))).
+        andReturn(provider).anyTimes();
+
+    // replay
+    replay(managementController, response, resourceProviderFactory, clusters, cluster, service,
+        component, componentHost, stageContainer, maintenanceStateHelper);
+
+    Map<String, Object> properties = new LinkedHashMap<String, Object>();
+    properties.put(HostComponentResourceProvider.HOST_COMPONENT_STATE_PROPERTY_ID, "STARTED");
+
+    RequestStatusResponse requestResponse = provider.installAndStart("Cluster102", hosts);
+
+    assertSame(response, requestResponse);
+    // verify
+    verify(managementController, response, resourceProviderFactory, stageContainer);
   }
 
   @Test
@@ -309,7 +457,7 @@ public class HostComponentResourceProviderTest {
 
     AbstractResourceProviderTest.TestObserver observer = new AbstractResourceProviderTest.TestObserver();
 
-    ((ObservableResourceProvider)provider).addObserver(observer);
+    provider.addObserver(observer);
 
     Predicate predicate = new PredicateBuilder().
         property(HostComponentResourceProvider.HOST_COMPONENT_COMPONENT_NAME_PROPERTY_ID).equals("Component100").and().
@@ -372,4 +520,44 @@ public class HostComponentResourceProviderTest {
     unsupported = provider.checkPropertyIds(Collections.singleton("config/unknown_property"));
     Assert.assertTrue(unsupported.isEmpty());
   }
+
+  // Used to directly call updateHostComponents on the resource provider.
+  // This exists as a temporary solution as a result of moving updateHostComponents from
+  // AmbariManagentControllerImpl to HostComponentResourceProvider.
+  public static RequestStatusResponse updateHostComponents(AmbariManagementController controller,
+                                                           Injector injector,
+                                                           Set<ServiceComponentHostRequest> requests,
+                                                           Map<String, String> requestProperties,
+                                                           boolean runSmokeTest) throws Exception {
+    Resource.Type type = Resource.Type.HostComponent;
+    HostComponentResourceProvider provider =
+        new TestHostComponentResourceProvider(PropertyHelper.getPropertyIds(type),
+            PropertyHelper.getKeyPropertyIds(type),
+            controller, injector, injector.getInstance(MaintenanceStateHelper.class));
+    RequestStageContainer requestStages = provider.updateHostComponents(null, requests, requestProperties, runSmokeTest);
+    requestStages.persist();
+    return requestStages.getRequestStatusResponse();
+  }
+
+  private static class TestHostComponentResourceProvider extends HostComponentResourceProvider {
+
+    /**
+     * Create a  new resource provider for the given management controller.
+     *
+     * @param propertyIds          the property ids
+     * @param keyPropertyIds       the key property ids
+     * @param managementController the management controller
+     */
+    public TestHostComponentResourceProvider(Set<String> propertyIds, Map<Resource.Type, String> keyPropertyIds,
+                                             AmbariManagementController managementController, Injector injector,
+                                             MaintenanceStateHelper maintenanceStateHelper) throws Exception {
+
+      super(propertyIds, keyPropertyIds, managementController, injector);
+
+      Class<?> c = getClass().getSuperclass();
+      Field f = c.getDeclaredField("maintenanceStateHelper");
+      f.setAccessible(true);
+      f.set(this, maintenanceStateHelper);
+    }
+  }
 }

+ 13 - 5
ambari-server/src/test/java/org/apache/ambari/server/controller/internal/HostResourceProviderTest.java

@@ -51,11 +51,13 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.powermock.core.classloader.annotations.PrepareForTest;
 import org.powermock.modules.junit4.PowerMockRunner;
+
 import static org.powermock.api.easymock.PowerMock.replayAll;
 import java.net.InetAddress;
 import static org.powermock.api.easymock.PowerMock.*;
 
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
 import java.util.LinkedHashMap;
 import java.util.LinkedHashSet;
@@ -1153,7 +1155,15 @@ public class HostResourceProviderTest {
 
   public static void createHosts(AmbariManagementController controller, Set<HostRequest> requests) throws AmbariException {
     HostResourceProvider provider = getHostProvider(controller);
-    provider.createHosts(requests);
+    Set<Map<String, Object>> properties = new HashSet<Map<String, Object>>();
+
+    for (HostRequest request : requests) {
+      Map<String, Object> requestProperties = new HashMap<String, Object>();
+      requestProperties.put(HostResourceProvider.HOST_NAME_PROPERTY_ID, request.getHostname());
+      requestProperties.put(HostResourceProvider.HOST_CLUSTER_NAME_PROPERTY_ID, request.getClusterName());
+      properties.add(requestProperties);
+    }
+    provider.createHosts(PropertyHelper.getCreateRequest(properties, Collections.<String, String>emptyMap()));
   }
 
   public static Set<HostResponse> getHosts(AmbariManagementController controller,
@@ -1168,11 +1178,9 @@ public class HostResourceProviderTest {
     provider.deleteHosts(requests);
   }
   
-  public static void updateHosts(AmbariManagementController controller, Set<HostRequest> requests,
-      Map<String, String> requestProperties)
+  public static void updateHosts(AmbariManagementController controller, Set<HostRequest> requests)
       throws AmbariException {
     HostResourceProvider provider = getHostProvider(controller);
-    provider.updateHosts(requests, requestProperties);
+    provider.updateHosts(requests);
   }
-  
 }