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


Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/058dc168
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/058dc168
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/058dc168

Branch: refs/heads/trunk
Commit: 058dc168e6173f4206837a1db87d066b372b07b1
Parents: 501785f
Author: John Speidel <jspei...@hortonworks.com>
Authored: Wed Nov 26 13:35:45 2014 -0500
Committer: John Speidel <jspei...@hortonworks.com>
Committed: Wed Nov 26 17:56:02 2014 -0500

----------------------------------------------------------------------
 .../controller/AmbariManagementController.java  |  18 -
 .../AmbariManagementControllerImpl.java         | 336 +-----------
 .../ambari/server/controller/HostRequest.java   |  31 +-
 .../internal/AbstractResourceProvider.java      |   2 +-
 .../internal/BaseBlueprintProcessor.java        |  81 +++
 .../internal/ClusterControllerImpl.java         |  13 +-
 .../internal/ClusterResourceProvider.java       |  46 +-
 .../internal/ConfigGroupResourceProvider.java   |  31 +-
 .../internal/HostComponentResourceProvider.java | 544 ++++++++++++++++++-
 .../internal/HostResourceProvider.java          | 324 ++++++++---
 .../org/apache/ambari/server/state/Cluster.java |   7 +-
 .../server/state/cluster/ClusterImpl.java       |   2 +-
 .../AmbariManagementControllerTest.java         | 145 ++---
 .../internal/ClusterResourceProviderTest.java   |   9 +-
 .../HostComponentResourceProviderTest.java      | 220 +++++++-
 .../internal/HostResourceProviderTest.java      |  18 +-
 16 files changed, 1239 insertions(+), 588 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
index 5af88a8..a1ece2c 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementController.java
@@ -235,24 +235,6 @@ public interface AmbariManagementController {
       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.
    *
    * @param requests the users to modify

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
index 7d08a7b..0e65a1d 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
+++ 
b/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;

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/HostRequest.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/HostRequest.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/HostRequest.java
index b577bb0..a616839 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/HostRequest.java
+++ 
b/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(']');
     }

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractResourceProvider.java
index 2c9179d..69d13b5 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/AbstractResourceProvider.java
+++ 
b/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());

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessor.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessor.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessor.java
index 9be73cc..9cfb635 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/BaseBlueprintProcessor.java
+++ 
b/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 -----------------------------------------------------
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java
index 352e0ad..b075d25 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterControllerImpl.java
+++ 
b/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;
   }
 
 

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
index bb6a39c..15cd8de 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ClusterResourceProvider.java
@@ -599,49 +599,6 @@ public class ClusterResourceProvider extends 
BaseBlueprintProcessor {
   }
 
   /**
-   * 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.
    *
    * @param blueprintHostGroups  host groups specified in blueprint
@@ -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)).

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigGroupResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigGroupResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigGroupResourceProvider.java
index c73a8d4..3fcb84b 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/ConfigGroupResourceProvider.java
+++ 
b/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>();

http://git-wip-us.apache.org/repos/asf/ambari/blob/058dc168/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostComponentResourceProvider.java
----------------------------------------------------------------------
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostComponentResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostComponentResourceProvider.java
index 4600538..47d3f70 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/HostComponentResourceProvider.java
+++ 
b/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;
+    }
+  }
 }

Reply via email to