ambari-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d...@apache.org
Subject [04/13] ambari git commit: AMBARI-12885: Dynamic stack extensions - install and upgrade support for custom services (tthorpe via dili)
Date Mon, 11 Jan 2016 19:15:40 GMT
http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
index 1078343..91976d3 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/cluster/ClusterImpl.java
@@ -61,11 +61,15 @@ import org.apache.ambari.server.orm.cache.HostConfigMapping;
 import org.apache.ambari.server.orm.dao.AlertDefinitionDAO;
 import org.apache.ambari.server.orm.dao.AlertDispatchDAO;
 import org.apache.ambari.server.orm.dao.ClusterDAO;
+import org.apache.ambari.server.orm.dao.ClusterExtensionVersionDAO;
 import org.apache.ambari.server.orm.dao.ClusterStateDAO;
 import org.apache.ambari.server.orm.dao.ClusterVersionDAO;
 import org.apache.ambari.server.orm.dao.ConfigGroupHostMappingDAO;
+import org.apache.ambari.server.orm.dao.ExtensionRepositoryVersionDAO;
+import org.apache.ambari.server.orm.dao.HostComponentDesiredStateDAO;
 import org.apache.ambari.server.orm.dao.HostConfigMappingDAO;
 import org.apache.ambari.server.orm.dao.HostDAO;
+import org.apache.ambari.server.orm.dao.HostExtensionVersionDAO;
 import org.apache.ambari.server.orm.dao.HostVersionDAO;
 import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
 import org.apache.ambari.server.orm.dao.ServiceConfigDAO;
@@ -75,12 +79,17 @@ import org.apache.ambari.server.orm.dao.UpgradeDAO;
 import org.apache.ambari.server.orm.entities.ClusterConfigEntity;
 import org.apache.ambari.server.orm.entities.ClusterConfigMappingEntity;
 import org.apache.ambari.server.orm.entities.ClusterEntity;
+import org.apache.ambari.server.orm.entities.ClusterExtensionVersionEntity;
 import org.apache.ambari.server.orm.entities.ClusterServiceEntity;
 import org.apache.ambari.server.orm.entities.ClusterStateEntity;
 import org.apache.ambari.server.orm.entities.ClusterVersionEntity;
 import org.apache.ambari.server.orm.entities.ConfigGroupEntity;
+import org.apache.ambari.server.orm.entities.ExtensionEntity;
+import org.apache.ambari.server.orm.entities.ExtensionRepositoryVersionEntity;
+import org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntity;
 import org.apache.ambari.server.orm.entities.HostComponentStateEntity;
 import org.apache.ambari.server.orm.entities.HostEntity;
+import org.apache.ambari.server.orm.entities.HostExtensionVersionEntity;
 import org.apache.ambari.server.orm.entities.HostVersionEntity;
 import org.apache.ambari.server.orm.entities.PermissionEntity;
 import org.apache.ambari.server.orm.entities.PrivilegeEntity;
@@ -99,6 +108,8 @@ import org.apache.ambari.server.state.Config;
 import org.apache.ambari.server.state.ConfigFactory;
 import org.apache.ambari.server.state.ConfigHelper;
 import org.apache.ambari.server.state.DesiredConfig;
+import org.apache.ambari.server.state.ExtensionId;
+import org.apache.ambari.server.state.ExtensionInfo;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.HostHealthStatus;
 import org.apache.ambari.server.state.HostState;
@@ -204,12 +215,21 @@ public class ClusterImpl implements Cluster {
   private ClusterVersionDAO clusterVersionDAO;
 
   @Inject
+  private ClusterExtensionVersionDAO clusterExtensionVersionDAO;
+
+  @Inject
   private HostDAO hostDAO;
 
   @Inject
+  private HostComponentDesiredStateDAO hostComponentDesiredStateDAO;
+
+  @Inject
   private HostVersionDAO hostVersionDAO;
 
   @Inject
+  private HostExtensionVersionDAO hostExtensionVersionDAO;
+
+  @Inject
   private ServiceFactory serviceFactory;
 
   @Inject
@@ -252,6 +272,9 @@ public class ClusterImpl implements Cluster {
   private RepositoryVersionDAO repositoryVersionDAO;
 
   @Inject
+  private ExtensionRepositoryVersionDAO extensionRepositoryVersionDAO;
+
+  @Inject
   private Configuration configuration;
 
   @Inject
@@ -1076,6 +1099,15 @@ public class ClusterImpl implements Cluster {
   }
 
   /**
+   * Get all of the ClusterVersionEntity objects for the cluster.
+   * @return
+   */
+  @Override
+  public Collection<ClusterExtensionVersionEntity> getAllClusterExtensionVersions(String extensionName) {
+    return clusterExtensionVersionDAO.findByClusterAndExtensionName(getClusterName(), extensionName);
+  }
+
+  /**
    * During the Finalize Action, want to transition all Host Versions from UPGRADED to CURRENT, and the last CURRENT one to INSTALLED.
    * @param hostNames Collection of host names
    * @param currentClusterVersion Entity that contains the cluster's current stack (with its name and version)
@@ -1441,6 +1473,146 @@ public class ClusterImpl implements Cluster {
   }
 
   /**
+   * {@inheritDoc}
+   */
+  @Override
+  public void recalculateClusterExtensionVersionState(ExtensionRepositoryVersionEntity repositoryVersion) throws AmbariException {
+    if (repositoryVersion == null) {
+      return;
+    }
+
+    ExtensionId extensionId = repositoryVersion.getExtensionId();
+    String version = repositoryVersion.getVersion();
+
+    //Map<String, Host> hosts = clusters.getHostsForCluster(getClusterName());
+    List<HostComponentDesiredStateEntity> hosts = hostComponentDesiredStateDAO.findByExtension(repositoryVersion.getExtension());
+    clusterGlobalLock.writeLock().lock();
+
+    try {
+      // Part 1, bootstrap cluster version if necessary.
+
+      ClusterExtensionVersionEntity clusterExtensionVersion = clusterExtensionVersionDAO.findByClusterAndExtensionAndVersion(
+          getClusterName(), extensionId, version);
+
+      boolean performingInitialBootstrap = false;
+      if (clusterExtensionVersion == null) {
+        if (clusterExtensionVersionDAO.findByClusterAndExtensionName(getClusterName(), extensionId.getExtensionName()).isEmpty()) {
+          // During the initial add of an extension service it will not exist, so bootstrap it.
+          performingInitialBootstrap = true;
+          createClusterExtensionVersionInternal(
+              extensionId,
+              version,
+              AuthorizationHelper.getAuthenticatedName(configuration.getAnonymousAuditName()),
+              RepositoryVersionState.INSTALLING);
+          clusterExtensionVersion = clusterExtensionVersionDAO.findByClusterAndExtensionAndVersion(
+              getClusterName(), extensionId, version);
+
+          if (clusterExtensionVersion == null) {
+            LOG.warn(String.format(
+                "Could not create a cluster extension version for cluster %s and extension %s using repo version %s",
+                getClusterName(), extensionId.getExtensionId(), repositoryVersion));
+            return;
+          }
+        } else {
+          LOG.warn(String.format(
+              "Repository version %s not found for cluster %s",
+              repositoryVersion, getClusterName()));
+          return;
+        }
+      }
+
+      // Ignore if cluster version is CURRENT or UPGRADE_FAILED
+      if (clusterExtensionVersion.getState() != RepositoryVersionState.INSTALL_FAILED &&
+              clusterExtensionVersion.getState() != RepositoryVersionState.OUT_OF_SYNC &&
+              clusterExtensionVersion.getState() != RepositoryVersionState.INSTALLING &&
+              clusterExtensionVersion.getState() != RepositoryVersionState.INSTALLED &&
+              clusterExtensionVersion.getState() != RepositoryVersionState.UPGRADING &&
+              clusterExtensionVersion.getState() != RepositoryVersionState.UPGRADED) {
+        // anything else is not supported as of now
+        return;
+      }
+
+      // Part 2, check for transitions.
+      Set<String> hostsWithoutHostVersion = new HashSet<String>();
+      Map<RepositoryVersionState, Set<String>> stateToHosts = new HashMap<RepositoryVersionState, Set<String>>();
+
+      //hack until better hostversion integration into in-memory cluster structure
+
+      List<HostExtensionVersionEntity> hostExtensionVersionEntities =
+              hostExtensionVersionDAO.findByClusterExtensionAndVersion(getClusterName(), extensionId, version);
+
+      Set<String> hostsWithState = new HashSet<String>();
+      for (HostExtensionVersionEntity hostExtensionVersionEntity : hostExtensionVersionEntities) {
+        String hostname = hostExtensionVersionEntity.getHostEntity().getHostName();
+        hostsWithState.add(hostname);
+        RepositoryVersionState hostState = hostExtensionVersionEntity.getState();
+
+        if (stateToHosts.containsKey(hostState)) {
+          stateToHosts.get(hostState).add(hostname);
+        } else {
+          Set<String> hostsInState = new HashSet<String>();
+          hostsInState.add(hostname);
+          stateToHosts.put(hostState, hostsInState);
+        }
+      }
+
+      for (HostComponentDesiredStateEntity host : hosts) {
+        hostsWithoutHostVersion.add(host.getHostEntity().getHostName());
+      }
+      hostsWithoutHostVersion.removeAll(hostsWithState);
+
+      // Ensure that all of the hosts without a Host Version only have
+      // Components that do not advertise a version.
+      // Otherwise, operations are still in progress.
+      for (String hostname : hostsWithoutHostVersion) {
+        HostEntity hostEntity = hostDAO.findByName(hostname);
+
+        // During initial bootstrap, unhealthy hosts are ignored
+        // so we boostrap the CURRENT version anyway
+        if (performingInitialBootstrap &&
+                hostEntity.getHostStateEntity().getCurrentState() != HostState.HEALTHY) {
+          continue;
+        }
+
+        final Collection<HostComponentStateEntity> allHostComponents = hostEntity.getHostComponentStateEntities();
+
+        for (HostComponentStateEntity hostComponentStateEntity : allHostComponents) {
+          if (hostComponentStateEntity.getVersion().equalsIgnoreCase(
+              State.UNKNOWN.toString())) {
+            // Some Components cannot advertise a version. E.g., ZKF, AMBARI_METRICS,
+            // Kerberos
+            ComponentInfo compInfo = ambariMetaInfo.getExtensionComponentSafe(
+                extensionId.getExtensionName(), extensionId.getExtensionVersion(),
+                hostComponentStateEntity.getServiceName(),
+                hostComponentStateEntity.getComponentName());
+
+            if (compInfo != null && compInfo.isVersionAdvertised()) {
+              LOG.debug("Skipping transitioning the cluster extension version because host "
+                  + hostname + " does not have a version yet.");
+              return;
+            }
+          }
+        }
+      }
+
+      RepositoryVersionState effectiveClusterVersionState = getEffectiveState(stateToHosts);
+      if (effectiveClusterVersionState != null
+          && effectiveClusterVersionState != clusterExtensionVersion.getState()) {
+        // Any mismatch will be caught while transitioning, and raise an
+        // exception.
+        try {
+          transitionClusterExtensionVersion(extensionId, version,
+              effectiveClusterVersionState);
+        } catch (AmbariException e) {
+          ;
+        }
+      }
+    } finally {
+      clusterGlobalLock.writeLock().unlock();
+    }
+  }
+
+  /**
    * Transition the Host Version across states.
    * @param host Host object
    * @param repositoryVersion Repository Version with stack and version information
@@ -1502,6 +1674,67 @@ public class ClusterImpl implements Cluster {
     return hostVersionEntity;
   }
 
+  /**
+   * Transition the Host Version across states.
+   * @param host Host object
+   * @param repositoryVersion Repository Version with stack and version information
+   * @param stack Stack information
+   * @throws AmbariException
+   */
+  @Override
+  @Transactional
+  public HostExtensionVersionEntity transitionHostExtensionVersionState(HostEntity host, final ExtensionRepositoryVersionEntity repositoryVersion, final ExtensionId extension) throws AmbariException {
+    ExtensionEntity repoVersionExtensionEntity = repositoryVersion.getExtension();
+    ExtensionId repoVersionExtensionId = new ExtensionId(repoVersionExtensionEntity);
+
+    HostExtensionVersionEntity hostExtensionVersionEntity = hostExtensionVersionDAO.findByClusterExtensionVersionAndHost(
+        getClusterName(), repoVersionExtensionId, repositoryVersion.getVersion(), host.getHostName());
+
+    hostTransitionStateWriteLock.lock();
+    try {
+      // Create one if it doesn't already exist. It will be possible to make further transitions below.
+      boolean performingInitialBootstrap = false;
+      if (hostExtensionVersionEntity == null) {
+        if (hostExtensionVersionDAO.findByClusterAndHost(getClusterName(), host.getHostName()).isEmpty()) {
+          // That is an initial bootstrap
+          performingInitialBootstrap = true;
+        }
+        hostExtensionVersionEntity = new HostExtensionVersionEntity(host, repositoryVersion, RepositoryVersionState.UPGRADING);
+        hostExtensionVersionDAO.create(hostExtensionVersionEntity);
+      }
+
+      HostExtensionVersionEntity currentVersionEntity = hostExtensionVersionDAO.findByHostAndStateCurrent(getClusterName(), host.getHostName());
+      boolean isCurrentPresent = (currentVersionEntity != null);
+      final ServiceComponentHostSummary hostSummary = new ServiceComponentHostSummary(ambariMetaInfo, host, extension);
+
+      if (!isCurrentPresent) {
+        // Transition from UPGRADING -> CURRENT. This is allowed because Host Version Entity is bootstrapped in an UPGRADING state.
+        // Alternatively, transition to CURRENT during initial bootstrap if at least one host component advertised a version
+        if (hostSummary.isUpgradeFinished() && hostExtensionVersionEntity.getState().equals(RepositoryVersionState.UPGRADING) || performingInitialBootstrap) {
+          hostExtensionVersionEntity.setState(RepositoryVersionState.CURRENT);
+          hostExtensionVersionDAO.merge(hostExtensionVersionEntity);
+        }
+      } else {
+        // Handle transitions during a Rolling Upgrade
+
+        // If a host only has one Component to update, that single report can still transition the host version from
+        // INSTALLED->UPGRADING->UPGRADED in one shot.
+        if (hostSummary.isUpgradeInProgress(currentVersionEntity.getRepositoryVersion().getVersion()) && hostExtensionVersionEntity.getState().equals(RepositoryVersionState.INSTALLED)) {
+          hostExtensionVersionEntity.setState(RepositoryVersionState.UPGRADING);
+          hostExtensionVersionDAO.merge(hostExtensionVersionEntity);
+        }
+
+        if (hostSummary.isUpgradeFinished() && hostExtensionVersionEntity.getState().equals(RepositoryVersionState.UPGRADING)) {
+          hostExtensionVersionEntity.setState(RepositoryVersionState.UPGRADED);
+          hostExtensionVersionDAO.merge(hostExtensionVersionEntity);
+        }
+      }
+    } finally {
+      hostTransitionStateWriteLock.unlock();
+    }
+    return hostExtensionVersionEntity;
+  }
+
   @Override
   public void recalculateAllClusterVersionStates() throws AmbariException {
     clusterGlobalLock.writeLock().lock();
@@ -1512,8 +1745,10 @@ public class ClusterImpl implements Cluster {
         RepositoryVersionEntity repositoryVersionEntity = clusterVersionEntity.getRepositoryVersion();
         StackId repoVersionStackId = repositoryVersionEntity.getStackId();
 
-        if (repoVersionStackId.equals(currentStackId)
-            && clusterVersionEntity.getState() != RepositoryVersionState.CURRENT) {
+        StackEntity stackEntity = clusterVersionEntity.getRepositoryVersion().getStack();
+        if (stackEntity.getStackName().equals(currentStackId.getStackName())
+                && stackEntity.getStackVersion().equals(currentStackId.getStackVersion())
+                && clusterVersionEntity.getState() != RepositoryVersionState.CURRENT) {
           recalculateClusterVersionState(clusterVersionEntity.getRepositoryVersion());
         }
       }
@@ -1574,6 +1809,47 @@ public class ClusterImpl implements Cluster {
   }
 
   /**
+   * See {@link #createClusterVersion}
+   *
+   * This method is intended to be called only when cluster lock is already acquired.
+   */
+  private void createClusterExtensionVersionInternal(ExtensionId extensionId, String version,
+      String userName, RepositoryVersionState state) throws AmbariException {
+    Set<RepositoryVersionState> allowedStates = new HashSet<RepositoryVersionState>();
+    /*Collection<ClusterExtensionVersionEntity> allClusterExtensionVersions = getAllClusterExtensionVersions(extensionId.getExtensionName());
+    if (allClusterExtensionVersions == null || allClusterExtensionVersions.isEmpty()) {
+      allowedStates.add(RepositoryVersionState.UPGRADING);
+    } else {
+      allowedStates.add(RepositoryVersionState.INSTALLING);
+    }*/
+    allowedStates.add(RepositoryVersionState.INSTALLING);
+
+    if (!allowedStates.contains(state)) {
+      throw new AmbariException("The allowed state for a new cluster version must be within " + allowedStates);
+    }
+
+    ClusterExtensionVersionEntity existing = clusterExtensionVersionDAO.findByClusterAndExtensionAndVersion(
+        getClusterName(), extensionId, version);
+    if (existing != null) {
+      throw new DuplicateResourceException(
+          "Duplicate item, a cluster extension version with extension=" + extensionId
+              + ", version=" +
+          version + " for cluster " + getClusterName() + " already exists");
+    }
+
+    ExtensionRepositoryVersionEntity extensionRepositoryVersionEntity = extensionRepositoryVersionDAO.findByExtensionAndVersion(
+        extensionId, version);
+    if (extensionRepositoryVersionEntity == null) {
+      LOG.warn("Could not find extension repository version for extension=" + extensionId
+          + ", version=" + version);
+      return;
+    }
+
+    ClusterExtensionVersionEntity clusterExtensionVersionEntity = new ClusterExtensionVersionEntity(clusterEntity, extensionRepositoryVersionEntity, state, System.currentTimeMillis(), System.currentTimeMillis(), userName);
+    clusterExtensionVersionDAO.create(clusterExtensionVersionEntity);
+  }
+
+  /**
    * Transition an existing cluster version from one state to another. The
    * following are some of the steps that are taken when transitioning between
    * specific states:
@@ -1737,6 +2013,173 @@ public class ClusterImpl implements Cluster {
   }
 
   /**
+   * Transition an existing cluster version from one state to another. The
+   * following are some of the steps that are taken when transitioning between
+   * specific states:
+   * <ul>
+   * <li>UPGRADING/UPGRADED --> CURRENT</lki>: Set the current stack to the
+   * desired stack, ensure all hosts with the desired stack are CURRENT as well.
+   * </ul>
+   * <li>UPGRADING/UPGRADED --> CURRENT</lki>: Set the current stack to the
+   * desired stack. </ul>
+   *
+   * @param extensionId
+   *          Extension ID
+   * @param version
+   *          Extension version
+   * @param state
+   *          Desired state
+   * @throws AmbariException
+   */
+  @Override
+  @Transactional
+  public void transitionClusterExtensionVersion(ExtensionId extensionId, String version,
+      RepositoryVersionState state) throws AmbariException {
+    Set<RepositoryVersionState> allowedStates = new HashSet<RepositoryVersionState>();
+    clusterGlobalLock.writeLock().lock();
+    try {
+      ClusterExtensionVersionEntity existingClusterExtensionVersion = clusterExtensionVersionDAO.findByClusterAndExtensionAndVersion(
+          getClusterName(), extensionId, version);
+
+      if (existingClusterExtensionVersion == null) {
+        throw new AmbariException(
+            "Existing cluster extension version not found for cluster="
+                + getClusterName() + ", extension=" + extensionId + ", version="
+                + version);
+      }
+
+      // NOOP
+      if (existingClusterExtensionVersion.getState() == state) {
+        return;
+      }
+
+      switch (existingClusterExtensionVersion.getState()) {
+        case CURRENT:
+          // If CURRENT state is changed here cluster will not have CURRENT
+          // state.
+          // CURRENT state will be changed to INSTALLED when another CURRENT
+          // state is added.
+          // allowedStates.add(RepositoryVersionState.INSTALLED);
+          break;
+        case INSTALLING:
+          allowedStates.add(RepositoryVersionState.INSTALLED);
+          allowedStates.add(RepositoryVersionState.INSTALL_FAILED);
+          allowedStates.add(RepositoryVersionState.OUT_OF_SYNC);
+          break;
+        case INSTALL_FAILED:
+          allowedStates.add(RepositoryVersionState.INSTALLING);
+          break;
+        case INSTALLED:
+          allowedStates.add(RepositoryVersionState.INSTALLING);
+          allowedStates.add(RepositoryVersionState.UPGRADING);
+          allowedStates.add(RepositoryVersionState.OUT_OF_SYNC);
+          break;
+        case OUT_OF_SYNC:
+          allowedStates.add(RepositoryVersionState.INSTALLING);
+          break;
+        case UPGRADING:
+          allowedStates.add(RepositoryVersionState.UPGRADED);
+          allowedStates.add(RepositoryVersionState.UPGRADE_FAILED);
+          if (clusterExtensionVersionDAO.findByClusterAndExtensionNameAndStateCurrent(getClusterName(), extensionId.getExtensionName()) == null) {
+            allowedStates.add(RepositoryVersionState.CURRENT);
+          }
+          break;
+        case UPGRADED:
+          allowedStates.add(RepositoryVersionState.CURRENT);
+          break;
+        case UPGRADE_FAILED:
+          allowedStates.add(RepositoryVersionState.UPGRADING);
+          break;
+      }
+
+      if (!allowedStates.contains(state)) {
+        throw new AmbariException("Invalid cluster extension version transition from "
+            + existingClusterExtensionVersion.getState() + " to " + state);
+      }
+
+      // There must be at most one cluster extension version for any extension name whose state is CURRENT at
+      // all times.
+      if (state == RepositoryVersionState.CURRENT) {
+        ClusterExtensionVersionEntity currentExtensionVersion = clusterExtensionVersionDAO.findByClusterAndExtensionNameAndStateCurrent(getClusterName(), extensionId.getExtensionName());
+        if (currentExtensionVersion != null) {
+          currentExtensionVersion.setState(RepositoryVersionState.INSTALLED);
+          clusterExtensionVersionDAO.merge(currentExtensionVersion);
+        }
+      }
+
+      existingClusterExtensionVersion.setState(state);
+      existingClusterExtensionVersion.setEndTime(System.currentTimeMillis());
+      clusterExtensionVersionDAO.merge(existingClusterExtensionVersion);
+
+      if (state == RepositoryVersionState.CURRENT) {
+        for (HostEntity hostEntity : clusterEntity.getHostEntities()) {
+          if (hostHasReportables(existingClusterExtensionVersion.getRepositoryVersion(),
+              hostEntity)) {
+            continue;
+          }
+
+          Collection<HostExtensionVersionEntity> versions = hostExtensionVersionDAO.findByHost(hostEntity.getHostName());
+
+          HostExtensionVersionEntity target = null;
+          if (null != versions) {
+            // Set anything that was previously marked CURRENT as INSTALLED, and
+            // the matching version as CURRENT
+            for (HostExtensionVersionEntity entity : versions) {
+              if (entity.getRepositoryVersion().getId().equals(
+		    existingClusterExtensionVersion.getRepositoryVersion().getId())) {
+                target = entity;
+                target.setState(state);
+                hostExtensionVersionDAO.merge(target);
+              } else if (entity.getState() == RepositoryVersionState.CURRENT) {
+                entity.setState(RepositoryVersionState.INSTALLED);
+                hostExtensionVersionDAO.merge(entity);
+              }
+            }
+          }
+
+          if (null == target) {
+            // If no matching version was found, create one with the desired
+            // state
+		  HostExtensionVersionEntity heve = new HostExtensionVersionEntity(hostEntity,
+                existingClusterExtensionVersion.getRepositoryVersion(), state);
+
+            hostExtensionVersionDAO.create(heve);
+          }
+        }
+
+        // when setting the cluster's state to current, we must also
+        // bring the desired stack and current stack in line with each other
+        /*StackEntity desiredStackEntity = clusterEntity.getDesiredStack();
+        StackInfo desiredStack = ambariMetaInfo.getStack(desiredStackEntity.getStackName(), desiredStackEntity.getStackVersion());
+        ExtensionInfo desiredExtension = desiredStack.getExtension(extensionId.getExtensionName());
+        ExtensionId desiredExtensionId = new ExtensionId(desiredExtension);
+
+        // if the desired extension ID doesn't match the target when setting the
+        // cluster to CURRENT, then there's a problem
+        if (!desiredExtensionId.equals(extensionId)) {
+          String message = MessageFormat.format(
+              "The desired extension ID {0} must match {1} when transitioning the cluster''s state to {2}",
+              desiredExtensionId, extensionId, RepositoryVersionState.CURRENT);
+
+          throw new AmbariException(message);
+        }*/
+
+        // TODO - do we need to do something like this?
+        //setCurrentStackVersion(extensionId);
+      }
+    } catch (RollbackException e) {
+      String message = MessageFormat.format(
+          "Unable to transition stack {0} at version {1} for cluster {2} to state {3}",
+          extensionId, version, getClusterName(), state);
+
+      LOG.warn(message);
+      throw new AmbariException(message, e);
+    } finally {
+      clusterGlobalLock.writeLock().unlock();
+    }
+  }
+
+  /**
    * Checks if the host has any components reporting version information.
    * @param repoVersion the repo version
    * @param host        the host entity
@@ -1760,6 +2203,30 @@ public class ClusterImpl implements Cluster {
     return false;
   }
 
+  /**
+   * Checks if the host has any extension components reporting version information.
+   * @param repoVersion the extension repo version
+   * @param host        the host entity
+   * @return {@code true} if the host has any extension component that report version
+   * @throws AmbariException
+   */
+  private boolean hostHasReportables(ExtensionRepositoryVersionEntity repoVersion, HostEntity host)
+      throws AmbariException {
+
+    for (HostComponentStateEntity hcse : host.getHostComponentStateEntities()) {
+      ComponentInfo ci = ambariMetaInfo.getExtensionComponent(
+          repoVersion.getExtensionName(),
+          repoVersion.getExtensionVersion(),
+          hcse.getServiceName(),
+          hcse.getComponentName());
+
+      if (ci != null && ci.isVersionAdvertised()) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   @Override
   public void setCurrentStackVersion(StackId stackId)
     throws AmbariException {

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/AbstractLatestRepoCallable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/AbstractLatestRepoCallable.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/AbstractLatestRepoCallable.java
new file mode 100644
index 0000000..4cb4264
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/AbstractLatestRepoCallable.java
@@ -0,0 +1,172 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.stack;
+
+import java.io.File;
+import java.io.FileReader;
+import java.io.InputStreamReader;
+import java.lang.reflect.Type;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.Callable;
+
+import org.apache.ambari.server.controller.internal.URLStreamProvider;
+import org.apache.ambari.server.state.RepositoryInfo;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+
+/**
+ * Encapsulates the work to resolve the latest repo information for a stack or extension.
+ * This class must be used AFTER the stack or extension has created its owned repositories.
+ */
+public abstract class AbstractLatestRepoCallable implements Callable<Void> {
+  private static final int LOOKUP_CONNECTION_TIMEOUT = 2000;
+  private static final int LOOKUP_READ_TIMEOUT = 1000;
+
+  private final static Logger LOG = LoggerFactory.getLogger(AbstractLatestRepoCallable.class);
+
+  private String sourceUri = null;
+  private File repoFolder = null;
+  private OsFamily os_family;
+
+  public AbstractLatestRepoCallable(String latestSourceUri, File repoFolder, OsFamily os_family) {
+    this.sourceUri = latestSourceUri;
+    this.repoFolder = repoFolder;
+    this.os_family = os_family;
+  }
+
+  public abstract String getName();
+
+  public abstract String getVersion();
+
+  public abstract Collection<RepositoryInfo> getRepositories();
+
+  @Override
+  public Void call() throws Exception {
+
+    Type type = new TypeToken<Map<String, Map<String, Object>>>(){}.getType();
+    Gson gson = new Gson();
+
+    Map<String, Map<String, Object>> latestUrlMap = null;
+
+    try {
+      if (sourceUri.startsWith("http")) {
+
+        URLStreamProvider streamProvider = new URLStreamProvider(
+            LOOKUP_CONNECTION_TIMEOUT, LOOKUP_READ_TIMEOUT,
+            null, null, null);
+
+        LOG.info("Loading latest URL info for " + getName() + "-" + getVersion() + " from " + sourceUri);
+        latestUrlMap = gson.fromJson(new InputStreamReader(
+            streamProvider.readFrom(sourceUri)), type);
+      } else {
+        File jsonFile = null;
+        if (sourceUri.charAt(0) == '.') {
+          jsonFile = new File(repoFolder, sourceUri);
+        } else {
+          jsonFile = new File(sourceUri);
+        }
+
+        if (jsonFile.exists()) {
+          LOG.info("Loading latest URL info for " + getName() + "-" +
+                  getVersion() + " from " + jsonFile);
+          latestUrlMap = gson.fromJson(new FileReader(jsonFile), type);
+        }
+      }
+    } catch (Exception e) {
+      LOG.error("Could not load the URI for " + getName() + "-" +
+              getVersion() + " from " + sourceUri + " (" + e.getMessage() + ")");
+      throw e;
+    }
+
+
+    if (null != latestUrlMap) {
+      for (RepositoryInfo ri : getRepositories()) {
+        if (latestUrlMap.containsKey(ri.getRepoId())) {
+          Map<String, Object> valueMap = latestUrlMap.get(ri.getRepoId());
+          if (valueMap.containsKey("latest")) {
+
+            @SuppressWarnings("unchecked")
+            Map<String, String> osMap = (Map<String, String>) valueMap.get("latest");
+
+            String baseUrl = resolveOsUrl(ri.getOsType(), osMap);
+            if (null != baseUrl) {
+              // !!! in the case where <name>.repo is defined with the base url, strip that off.
+              // Agents do the reverse action (take the base url, and append <name>.repo)
+
+              String repo_file_format;
+
+              if(os_family.isUbuntuFamily(ri.getOsType())) {
+                repo_file_format = "list";
+              } else {
+                repo_file_format = "repo";
+              }
+
+              String repoFileName = getName().toLowerCase() + "." + repo_file_format;
+              int idx = baseUrl.toLowerCase().indexOf(repoFileName);
+
+              if (-1 != idx && baseUrl.toLowerCase().endsWith(repoFileName)) {
+                baseUrl = baseUrl.substring(0, idx);
+              }
+
+              if ('/' == baseUrl.charAt(baseUrl.length()-1)) {
+                baseUrl = baseUrl.substring(0, baseUrl.length()-1);
+              }
+
+              ri.setLatestBaseUrl(baseUrl);
+              if (ri.getBaseUrl() != null && !ri.isBaseUrlFromSaved()) {
+                // Override baseUrl with the latestBaseUrl.
+                ri.setBaseUrl(baseUrl);
+              }
+            }
+          }
+        }
+      }
+    }
+
+    return null;
+  }
+
+  /**
+   * Resolves a base url given that certain OS types can be used interchangeably.
+   * @param os the target os to find
+   * @param osMap the map of os-to-baseurl
+   * @return the url for an os.
+   */
+  private String resolveOsUrl(String os, Map<String, String> osMap) {
+
+    // !!! look for the OS directly
+    if (osMap.containsKey(os))
+      return osMap.get(os);
+
+    // !!! os not found, find and return the first compatible one
+    Set<String> possibleTypes = os_family.findTypes(os);
+
+    for (String type : possibleTypes) {
+      if (osMap.containsKey(type))
+        return osMap.get(type);
+    }
+
+    return null;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionMetainfoXml.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionMetainfoXml.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionMetainfoXml.java
new file mode 100644
index 0000000..e178f71
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionMetainfoXml.java
@@ -0,0 +1,184 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.stack;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElementWrapper;
+import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.apache.ambari.server.stack.Validable;
+
+/**
+ * Represents the stack <code>metainfo.xml</code> file.
+ */
+@XmlRootElement(name="metainfo")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ExtensionMetainfoXml implements Validable{
+
+  @XmlElement(name="extends")
+  private String extendsVersion = null;
+
+  @XmlElement(name="versions")
+  private Version version = new Version();
+
+  @XmlElementWrapper(name="stacks")
+  @XmlElements(@XmlElement(name="stack"))
+  private List<Stack> stacks = new ArrayList<Stack>();
+
+  @XmlElementWrapper(name="dependsOn")
+  @XmlElements(@XmlElement(name="extension"))
+  private List<Extension> extensions = new ArrayList<Extension>();
+
+  @XmlTransient
+  private boolean valid = true;
+
+  /**
+   *
+   * @return valid xml flag
+   */
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  /**
+   *
+   * @param valid set validity flag
+   */
+  @Override
+  public void setValid(boolean valid) {
+    this.valid = valid;
+  }
+
+  @XmlTransient
+  private Set<String> errorSet = new HashSet<String>();
+
+  @Override
+  public void setErrors(String error) {
+    errorSet.add(error);
+  }
+
+  @Override
+  public Collection getErrors() {
+    return errorSet;
+  }
+
+  @Override
+  public void setErrors(Collection error) {
+    this.errorSet.addAll(error);
+  }
+
+  /**
+   * @return the parent stack version number
+   */
+  public String getExtends() {
+    return extendsVersion;
+  }
+
+  /**
+   * @return gets the version
+   */
+  public Version getVersion() {
+    return version;
+  }
+
+  public List<Stack> getStacks() {
+    return stacks;
+  }
+
+  public List<Extension> getExtensions() {
+    return extensions;
+  }
+
+  @XmlAccessorType(XmlAccessType.FIELD)
+  public static class Version {
+    private Version() {
+    }
+    private boolean active = false;
+    private String upgrade = null;
+
+    /**
+     * @return <code>true</code> if the stack is active
+     */
+    public boolean isActive() {
+      return active;
+    }
+
+    /**
+     * @return the upgrade version number, if set
+     */
+    public String getUpgrade() {
+      return upgrade;
+    }
+  }
+
+  @XmlAccessorType(XmlAccessType.FIELD)
+  public static class Stack {
+    private Stack() {
+    }
+    private String name = null;
+    private String version = null;
+
+    /**
+     * @return the stack name
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * @return the stack version, this may be something like 1.0.*
+     */
+    public String getVersion() {
+      return version;
+    }
+  }
+
+  @XmlAccessorType(XmlAccessType.FIELD)
+  public static class Extension {
+    private Extension() {
+    }
+    private String name = null;
+    private String version = null;
+
+    /**
+     * @return the extension name
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * @return the extension version, this may be something like 1.0.*
+     */
+    public String getVersion() {
+      return version;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionsXml.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionsXml.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionsXml.java
new file mode 100644
index 0000000..1f29321
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ExtensionsXml.java
@@ -0,0 +1,151 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.stack;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlElements;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+import org.apache.ambari.server.stack.StackManager;
+import org.apache.ambari.server.stack.Validable;
+
+/**
+ * Represents the stack's <code>extensions.xml</code> file.
+ */
+@XmlRootElement(name="extensions")
+@XmlAccessorType(XmlAccessType.FIELD)
+public class ExtensionsXml implements Validable{
+
+  @XmlElements(@XmlElement(name="extension"))
+  private List<Extension> extensions = new ArrayList<Extension>();
+
+  @XmlTransient
+  private boolean valid = true;
+
+  /**
+   *
+   * @return valid xml flag
+   */
+  @Override
+  public boolean isValid() {
+    return valid;
+  }
+
+  public List<String> getExtensionKeys() {
+    List<String> keys = new ArrayList<String>();
+    for (Extension extension : extensions) {
+      keys.add(extension.getName() + StackManager.PATH_DELIMITER + extension.getVersion());
+    }
+    return keys;
+  }
+
+  public List<Extension> getExtensions() {
+    return extensions;
+  }
+
+  public void addExtension(String name, String version) {
+    Extension extension = new Extension(name, version);
+    extensions.add(extension);
+  }
+
+  /**
+   *
+   * @param valid set validity flag
+   */
+  @Override
+  public void setValid(boolean valid) {
+    this.valid = valid;
+  }
+
+  @XmlTransient
+  private Set<String> errorSet = new HashSet<String>();
+
+  @Override
+  public void setErrors(String error) {
+    errorSet.add(error);
+  }
+
+  @Override
+  public Collection getErrors() {
+    return errorSet;
+  }
+
+  @Override
+  public void setErrors(Collection error) {
+    this.errorSet.addAll(error);
+  }
+
+  @XmlAccessorType(XmlAccessType.FIELD)
+  public static class Version {
+    private Version() {
+    }
+    private boolean active = false;
+    private String upgrade = null;
+
+    /**
+     * @return <code>true</code> if the stack is active
+     */
+    public boolean isActive() {
+      return active;
+    }
+
+    /**
+     * @return the upgrade version number, if set
+     */
+    public String getUpgrade() {
+      return upgrade;
+    }
+  }
+
+  @XmlAccessorType(XmlAccessType.FIELD)
+  public static class Extension {
+    Extension() {}
+
+    Extension(String name, String version) {
+      this.name = name;
+      this.version = version;
+    }
+
+    private String name = null;
+    private String version = null;
+
+    /**
+     * @return the stack name
+     */
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * @return the stack version, this may be something like 1.0.*
+     */
+    public String getVersion() {
+      return version;
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestExtensionRepoCallable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestExtensionRepoCallable.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestExtensionRepoCallable.java
new file mode 100644
index 0000000..b893984
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestExtensionRepoCallable.java
@@ -0,0 +1,54 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.stack;
+
+import java.io.File;
+import java.util.Collection;
+
+import org.apache.ambari.server.state.ExtensionInfo;
+import org.apache.ambari.server.state.RepositoryInfo;
+
+/**
+ * Encapsulates the work to resolve the latest repo information for an extension.
+ * This class must be used AFTER the extension has created its owned repositories.
+ */
+public class LatestExtensionRepoCallable extends AbstractLatestRepoCallable {
+
+  private ExtensionInfo extension = null;
+
+  public LatestExtensionRepoCallable(String latestSourceUri, File repoFolder, OsFamily os_family, ExtensionInfo extension) {
+    super(latestSourceUri, repoFolder, os_family);
+    this.extension = extension;
+  }
+
+  @Override
+  public String getName() {
+    return extension.getName();
+  }
+
+  @Override
+  public String getVersion() {
+    return extension.getVersion();
+  }
+
+  @Override
+  public Collection<RepositoryInfo> getRepositories() {
+    return extension.getRepositories();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestRepoCallable.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestRepoCallable.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestRepoCallable.java
index 0ad24fe..901b0aa 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestRepoCallable.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/LatestRepoCallable.java
@@ -18,153 +18,37 @@
 package org.apache.ambari.server.state.stack;
 
 import java.io.File;
-import java.io.FileReader;
-import java.io.InputStreamReader;
-import java.lang.reflect.Type;
-import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.Callable;
+import java.util.Collection;
 
-import org.apache.ambari.server.controller.internal.URLStreamProvider;
 import org.apache.ambari.server.state.RepositoryInfo;
 import org.apache.ambari.server.state.StackInfo;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-import com.google.gson.Gson;
-import com.google.gson.reflect.TypeToken;
 
 /**
  * Encapsulates the work to resolve the latest repo information for a stack.
  * This class must be used AFTER the stack has created its owned repositories.
  */
-public class LatestRepoCallable implements Callable<Void> {
-  private static final int LOOKUP_CONNECTION_TIMEOUT = 2000;
-  private static final int LOOKUP_READ_TIMEOUT = 1000;
-
-  private final static Logger LOG = LoggerFactory.getLogger(LatestRepoCallable.class);
+public class LatestRepoCallable extends AbstractLatestRepoCallable {
 
-  private String sourceUri = null;
-  private File stackRepoFolder = null;
   private StackInfo stack = null;
-  private OsFamily os_family;
 
-  public LatestRepoCallable(String latestSourceUri, File stackRepoFolder, StackInfo stack, OsFamily os_family) {
-    this.sourceUri = latestSourceUri;
-    this.stackRepoFolder = stackRepoFolder;
+  public LatestRepoCallable(String latestSourceUri, File repoFolder, OsFamily os_family, StackInfo stack) {
+    super(latestSourceUri, repoFolder, os_family);
     this.stack = stack;
-    this.os_family = os_family;
   }
 
   @Override
-  public Void call() throws Exception {
-
-    Type type = new TypeToken<Map<String, Map<String, Object>>>(){}.getType();
-    Gson gson = new Gson();
-
-    Map<String, Map<String, Object>> latestUrlMap = null;
-
-    try {
-      if (sourceUri.startsWith("http")) {
-
-        URLStreamProvider streamProvider = new URLStreamProvider(
-            LOOKUP_CONNECTION_TIMEOUT, LOOKUP_READ_TIMEOUT,
-            null, null, null);
-
-        LOG.info("Loading latest URL info for stack " + stack.getName() + "-" +
-                stack.getVersion() + " from " + sourceUri);
-        latestUrlMap = gson.fromJson(new InputStreamReader(
-            streamProvider.readFrom(sourceUri)), type);
-      } else {
-        File jsonFile = null;
-        if (sourceUri.charAt(0) == '.') {
-          jsonFile = new File(stackRepoFolder, sourceUri);
-        } else {
-          jsonFile = new File(sourceUri);
-        }
-
-        if (jsonFile.exists()) {
-          LOG.info("Loading latest URL info for stack " + stack.getName() + "-" +
-                  stack.getVersion() + " from " + jsonFile);
-          latestUrlMap = gson.fromJson(new FileReader(jsonFile), type);
-        }
-      }
-    } catch (Exception e) {
-      LOG.info("Could not load the URI for stack " + stack.getName() + "-" +
-              stack.getVersion() + " from " + sourceUri + " (" + e.getMessage() + ")" +
-              ". Using default repository values.");
-      throw e;
-    }
-
-
-    if (null != latestUrlMap) {
-      for (RepositoryInfo ri : stack.getRepositories()) {
-        if (latestUrlMap.containsKey(ri.getRepoId())) {
-          Map<String, Object> valueMap = latestUrlMap.get(ri.getRepoId());
-          if (valueMap.containsKey("latest")) {
-
-            @SuppressWarnings("unchecked")
-            Map<String, String> osMap = (Map<String, String>) valueMap.get("latest");
-
-            String baseUrl = resolveOsUrl(ri.getOsType(), osMap);
-            if (null != baseUrl) {
-              // !!! in the case where <name>.repo is defined with the base url, strip that off.
-              // Agents do the reverse action (take the base url, and append <name>.repo)
-
-              String repo_file_format;
-              
-              if(os_family.isUbuntuFamily(ri.getOsType())) {
-                repo_file_format = "list";
-              } else {
-                repo_file_format = "repo";
-              }
-
-              String repoFileName = stack.getName().toLowerCase() + "." + repo_file_format;
-              int idx = baseUrl.toLowerCase().indexOf(repoFileName);
-
-              if (-1 != idx && baseUrl.toLowerCase().endsWith(repoFileName)) {
-                baseUrl = baseUrl.substring(0, idx);
-              }
-
-              if ('/' == baseUrl.charAt(baseUrl.length()-1)) {
-                baseUrl = baseUrl.substring(0, baseUrl.length()-1);
-              }
-
-              ri.setLatestBaseUrl(baseUrl);
-              if (ri.getBaseUrl() != null && !ri.isBaseUrlFromSaved()) {
-                // Override baseUrl with the latestBaseUrl.
-                ri.setBaseUrl(baseUrl);
-              }
-            }
-          }
-        }
-      }
-    }
-
-    return null;
+  public String getName() {
+    return stack.getName();
   }
 
-  /**
-   * Resolves a base url given that certain OS types can be used interchangeably.
-   * @param os the target os to find
-   * @param osMap the map of os-to-baseurl
-   * @return the url for an os.
-   */
-  private String resolveOsUrl(String os, Map<String, String> osMap) {
-
-    // !!! look for the OS directly
-    if (osMap.containsKey(os))
-      return osMap.get(os);
-
-    // !!! os not found, find and return the first compatible one
-    Set<String> possibleTypes = os_family.findTypes(os);
-
-    for (String type : possibleTypes) {
-      if (osMap.containsKey(type))
-        return osMap.get(type);
-    }
+  @Override
+  public String getVersion() {
+    return stack.getVersion();
+  }
 
-    return null;
+  @Override
+  public Collection<RepositoryInfo> getRepositories() {
+    return stack.getRepositories();
   }
 
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/OperationType.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/OperationType.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/OperationType.java
new file mode 100644
index 0000000..39e70e2
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/OperationType.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.state.stack;
+
+/**
+ * Type of operation.
+ */
+public enum OperationType {
+  UPGRADE,
+  INSTALL,
+  EXTENSION_UPGRADE,
+  EXTENSION_LINK
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ServiceMetainfoXml.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ServiceMetainfoXml.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ServiceMetainfoXml.java
index 56dac8a..8e429c6 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ServiceMetainfoXml.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/ServiceMetainfoXml.java
@@ -86,6 +86,10 @@ public class ServiceMetainfoXml implements Validable{
   public List<ServiceInfo> getServices() {
     return services;
   }
+
+  public void setServices(List<ServiceInfo> services) {
+    this.services = services;
+  }
   
   public String getSchemaVersion() {
     return schemaVersion;

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackRoleCommandOrder.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackRoleCommandOrder.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackRoleCommandOrder.java
index 75587c4..29efe99 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackRoleCommandOrder.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/StackRoleCommandOrder.java
@@ -62,7 +62,6 @@ public class StackRoleCommandOrder {
    *
    * @return role command order content
    */
-
   public HashMap<String, Object> getContent() {
     return content;
   }
@@ -72,7 +71,6 @@ public class StackRoleCommandOrder {
    *
    * @param content role command order content
    */
-
   public void setContent(HashMap<String, Object> content) {
     this.content = content;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RepositoryVersionHelper.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RepositoryVersionHelper.java b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RepositoryVersionHelper.java
index 6a36522..ac0e4b5 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RepositoryVersionHelper.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/stack/upgrade/RepositoryVersionHelper.java
@@ -183,4 +183,55 @@ public class RepositoryVersionHelper {
     throw new AmbariException("There were no suitable upgrade packs for stack " + stackName + " " + stackVersion +
         ((null != upgradeType) ? " and upgrade type " + upgradeType : ""));
   }
+
+  /**
+   * Scans the given extension for upgrade packages which can be applied to update the cluster to given repository version.
+   *
+   * @param extensionName extension name
+   * @param extensionVersion extension version
+   * @param repositoryVersion target repository version
+   * @param upgradeType if not {@code null} null, will only return upgrade packs whose type matches.
+   * @return upgrade pack name
+   * @throws AmbariException if no upgrade packs suit the requirements
+   */
+  public String getExtensionUpgradePackageName(String extensionName, String extensionVersion, String repositoryVersion, UpgradeType upgradeType) throws AmbariException {
+    final Map<String, UpgradePack> upgradePacks = ambariMetaInfo.getExtensionUpgradePacks(extensionName, extensionVersion);
+    for (UpgradePack upgradePack : upgradePacks.values()) {
+      final String upgradePackName = upgradePack.getName();
+
+      if (null != upgradeType && upgradePack.getType() != upgradeType) {
+        continue;
+      }
+
+      // check that upgrade pack has <target> node
+      if (StringUtils.isBlank(upgradePack.getTarget())) {
+        LOG.error("Upgrade pack " + upgradePackName + " is corrupted, it should contain <target> node");
+        continue;
+      }
+      if (upgradePack.canBeApplied(repositoryVersion)) {
+        return upgradePackName;
+      }
+    }
+    throw new AmbariException("There were no suitable upgrade packs for extension " + extensionName + " " + extensionVersion +
+        ((null != upgradeType) ? " and upgrade type " + upgradeType : ""));
+  }
+
+  /**
+   * Scans the given extension for upgrade packages which can be applied to update the cluster to given repository version.
+   * Returns NONE if there were no suitable packages.
+   *
+   * @param extensionName extension name
+   * @param extensionVersion extension version
+   * @param repositoryVersion target repository version
+   * @param upgradeType if not {@code null} null, will only return upgrade packs whose type matches.
+   * @return upgrade pack name or NONE
+   */
+  public String getExtensionUpgradePackageNameSafe(String extensionName, String extensionVersion, String repositoryVersion, UpgradeType upgradeType) {
+    try {
+      return getExtensionUpgradePackageName(extensionName, extensionVersion, repositoryVersion, upgradeType);
+    } catch (AmbariException ex) {
+      return "NONE";
+    }
+  }
+
 }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ExtensionServiceComponentHostSummary.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ExtensionServiceComponentHostSummary.java b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ExtensionServiceComponentHostSummary.java
new file mode 100644
index 0000000..143018f
--- /dev/null
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ExtensionServiceComponentHostSummary.java
@@ -0,0 +1,132 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.ambari.server.state.svccomphost;
+
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.api.services.AmbariMetaInfo;
+import org.apache.ambari.server.orm.entities.HostComponentStateEntity;
+import org.apache.ambari.server.orm.entities.HostEntity;
+import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.ExtensionId;
+import org.apache.ambari.server.state.State;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+
+/**
+ * Represents a summary of the versions of the components installed on a host.
+ */
+public class ExtensionServiceComponentHostSummary  {
+
+  private Collection<HostComponentStateEntity> allHostComponents;
+  private Collection<HostComponentStateEntity> haveAdvertisedVersion;
+  private Collection<HostComponentStateEntity> waitingToAdvertiseVersion;
+  private Collection<HostComponentStateEntity> noVersionToAdvertise;
+  private Set<String> versions;
+
+
+  public ExtensionServiceComponentHostSummary(AmbariMetaInfo ambariMetaInfo, HostEntity host, String extensionName, String extensionVersion) throws AmbariException {
+    allHostComponents = host.getHostComponentStateEntities();
+    haveAdvertisedVersion = new HashSet<HostComponentStateEntity>();
+    waitingToAdvertiseVersion = new HashSet<HostComponentStateEntity>();
+    noVersionToAdvertise = new HashSet<HostComponentStateEntity>();
+    versions = new HashSet<String>();
+
+    for (HostComponentStateEntity hostComponentStateEntity: allHostComponents) {
+      ComponentInfo compInfo = ambariMetaInfo.getExtensionComponent(
+          extensionName, extensionVersion, hostComponentStateEntity.getServiceName(),
+          hostComponentStateEntity.getComponentName());
+
+      if (!compInfo.isVersionAdvertised()) {
+        // Some Components cannot advertise a version. E.g., ZKF, AMBARI_METRICS, Kerberos
+        noVersionToAdvertise.add(hostComponentStateEntity);
+      } else {
+        if (hostComponentStateEntity.getVersion() == null || hostComponentStateEntity.getVersion().isEmpty() || hostComponentStateEntity.getVersion().equalsIgnoreCase(State.UNKNOWN.toString())) {
+          waitingToAdvertiseVersion.add(hostComponentStateEntity);
+        } else {
+          haveAdvertisedVersion.add(hostComponentStateEntity);
+          versions.add(hostComponentStateEntity.getVersion());
+        }
+      }
+    }
+  }
+
+  public ExtensionServiceComponentHostSummary(AmbariMetaInfo ambariMetaInfo, HostEntity host, ExtensionId extensionId) throws AmbariException {
+    this(ambariMetaInfo, host, extensionId.getExtensionName(), extensionId.getExtensionVersion());
+  }
+
+  public Collection<HostComponentStateEntity> getHaveAdvertisedVersion() {
+    return haveAdvertisedVersion;
+  }
+
+  public boolean isUpgradeFinished() {
+    return haveAllComponentsFinishedAdvertisingVersion() && haveSameVersion(getHaveAdvertisedVersion());
+  }
+
+  /**
+   * @param currentRepoVersion Repo Version that is CURRENT for this host
+   * @return Return true if multiple component versions are found for this host, or if it does not coincide with the
+   * CURRENT repo version.
+   */
+  public boolean isUpgradeInProgress(String currentRepoVersion) {
+    // Exactly one CURRENT version must exist
+    // We can only detect an upgrade if the Host has at least one component that advertises a version and has done so already
+    // If distinct versions have been advertises, then an upgrade is in progress.
+    // If exactly one version has been advertises, but it doesn't coincide with the CURRENT HostVersion, then an upgrade is in progress.
+    return currentRepoVersion != null && (versions.size() > 1 || (versions.size() == 1 && !versions.iterator().next().equals(currentRepoVersion)));
+  }
+
+  /**
+   * Determine if all of the components on that need to advertise a version have finished doing so.
+   * @return Return a bool indicating if all components that can report a version have done so.
+   */
+  public boolean haveAllComponentsFinishedAdvertisingVersion() {
+    return waitingToAdvertiseVersion.size() == 0;
+  }
+
+  /**
+   * Checks that every component has the same version
+   *
+   * @param hostComponents host components
+   * @return true if components have the same version, or collection is empty, false otherwise.
+   */
+  public static boolean haveSameVersion(Collection<HostComponentStateEntity> hostComponents) {
+    // It is important to return true even if the collection is empty because technically, there are no conflicts.
+    if (hostComponents.isEmpty()) {
+      return true;
+    }
+    String firstVersion = null;
+    for (HostComponentStateEntity hostComponent : hostComponents) {
+      if (!hostComponent.getVersion().isEmpty()) {
+        if (firstVersion == null) {
+          firstVersion = hostComponent.getVersion();
+        } else {
+          if (!StringUtils.equals(firstVersion, hostComponent.getVersion())) {
+            return false;
+          }
+        }
+      }
+    }
+    return true;
+  }
+}

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
index 7bc4680..95a77af 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostImpl.java
@@ -39,12 +39,16 @@ import org.apache.ambari.server.events.MaintenanceModeEvent;
 import org.apache.ambari.server.events.ServiceComponentInstalledEvent;
 import org.apache.ambari.server.events.ServiceComponentUninstalledEvent;
 import org.apache.ambari.server.events.publishers.AmbariEventPublisher;
+import org.apache.ambari.server.orm.dao.ExtensionDAO;
+import org.apache.ambari.server.orm.dao.ExtensionRepositoryVersionDAO;
 import org.apache.ambari.server.orm.dao.HostComponentDesiredStateDAO;
 import org.apache.ambari.server.orm.dao.HostComponentStateDAO;
 import org.apache.ambari.server.orm.dao.HostDAO;
 import org.apache.ambari.server.orm.dao.RepositoryVersionDAO;
 import org.apache.ambari.server.orm.dao.ServiceComponentDesiredStateDAO;
 import org.apache.ambari.server.orm.dao.StackDAO;
+import org.apache.ambari.server.orm.entities.ExtensionEntity;
+import org.apache.ambari.server.orm.entities.ExtensionRepositoryVersionEntity;
 import org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntity;
 import org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntityPK;
 import org.apache.ambari.server.orm.entities.HostComponentStateEntity;
@@ -56,16 +60,20 @@ import org.apache.ambari.server.orm.entities.StackEntity;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
 import org.apache.ambari.server.state.ConfigHelper;
+import org.apache.ambari.server.state.ExtensionId;
+import org.apache.ambari.server.state.ExtensionInfo;
 import org.apache.ambari.server.state.Host;
 import org.apache.ambari.server.state.HostComponentAdminState;
 import org.apache.ambari.server.state.HostConfig;
 import org.apache.ambari.server.state.HostState;
 import org.apache.ambari.server.state.MaintenanceState;
+import org.apache.ambari.server.state.RepositoryInfo;
 import org.apache.ambari.server.state.SecurityState;
 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.ServiceComponentHostEventType;
+import org.apache.ambari.server.state.ServiceInfo;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.StackInfo;
 import org.apache.ambari.server.state.State;
@@ -109,6 +117,8 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
   @Inject
   RepositoryVersionDAO repositoryVersionDAO;
   @Inject
+  ExtensionRepositoryVersionDAO extensionRepositoryVersionDAO;
+  @Inject
   ServiceComponentDesiredStateDAO serviceComponentDesiredStateDAO;
   @Inject
   Clusters clusters;
@@ -138,6 +148,12 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
   @Inject
   private StackDAO stackDAO;
 
+  /**
+   * Data access object for extension.
+   */
+  @Inject
+  private ExtensionDAO extensionDAO;
+
   // Only used when object state is not persisted
   private HostComponentStateEntity stateEntity;
   private HostComponentDesiredStateEntity desiredStateEntity;
@@ -743,6 +759,17 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
     StackEntity stackEntity = stackDAO.find(stackId.getStackName(),
         stackId.getStackVersion());
 
+    ExtensionEntity extensionEntity = null;
+
+    try {
+      ExtensionInfo extension = ambariMetaInfo.getExtensionByStackService(stackId.getStackName(),
+          stackId.getStackVersion(), serviceComponent.getServiceName());
+      if (extension != null)
+        extensionEntity = extensionDAO.find(extension.getName(), extension.getVersion());
+    } catch (AmbariException e) {
+      throw new RuntimeException(e);
+    }
+
     stateEntity = new HostComponentStateEntity();
     stateEntity.setClusterId(serviceComponent.getClusterId());
     stateEntity.setComponentName(serviceComponent.getName());
@@ -752,6 +779,8 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
     stateEntity.setCurrentState(stateMachine.getCurrentState());
     stateEntity.setUpgradeState(UpgradeState.NONE);
     stateEntity.setCurrentStack(stackEntity);
+    if (extensionEntity != null)
+      stateEntity.setCurrentExtension(extensionEntity);
 
     desiredStateEntity = new HostComponentDesiredStateEntity();
     desiredStateEntity.setClusterId(serviceComponent.getClusterId());
@@ -760,6 +789,8 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
     desiredStateEntity.setHostEntity(hostEntity);
     desiredStateEntity.setDesiredState(State.INIT);
     desiredStateEntity.setDesiredStack(stackEntity);
+    if (extensionEntity != null)
+	desiredStateEntity.setDesiredExtension(extensionEntity);
 
     if(!serviceComponent.isMasterComponent() && !serviceComponent.isClientComponent()) {
       desiredStateEntity.setAdminState(HostComponentAdminState.INSERVICE);
@@ -1567,9 +1598,11 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
     if (stateEntity != null) {
       // make sure that the state entities are removed from the associated (detached) host entity
       // Also refresh before delete
-      stateEntity.getHostEntity().removeHostComponentStateEntity(stateEntity);
+      if (stateEntity != null && stateEntity.getHostEntity() != null)
+        stateEntity.getHostEntity().removeHostComponentStateEntity(stateEntity);
       HostComponentDesiredStateEntity desiredStateEntity = getDesiredStateEntity();
-      desiredStateEntity.getHostEntity().removeHostComponentDesiredStateEntity(desiredStateEntity);
+      if (desiredStateEntity != null && desiredStateEntity.getHostEntity() != null)
+        desiredStateEntity.getHostEntity().removeHostComponentDesiredStateEntity(desiredStateEntity);
 
       hostComponentDesiredStateDAO.remove(desiredStateEntity);
 
@@ -1747,6 +1780,40 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
         repositoryVersionHelper.serializeOperatingSystems(stackInfo.getRepositories()));
   }
 
+  @Transactional
+  private ExtensionRepositoryVersionEntity createExtensionRepositoryVersion(String version, ExtensionInfo extension) throws AmbariException {
+    LOG.info("Creating new extension repository version " + extension.getName() + "-" + extension.getVersion());
+
+    ExtensionEntity extensionEntity = extensionDAO.find(extension.getName(), extension.getVersion());
+
+    // Ensure that the version provided is part of the Extension
+    if (null == version) {
+      throw new AmbariException(MessageFormat.format("Cannot create Repository Version for Extension {0}-{1} if the version is empty",
+	    extension.getName(), extension.getVersion()));
+    }
+
+    return extensionRepositoryVersionDAO.create(
+	   extensionEntity,
+        version,
+        extension.getName() + "-" + version,
+        repositoryVersionHelper.serializeOperatingSystems(extension.getRepositories()));
+  }
+
+  public boolean isExtensionService() {
+    try {
+      final String hostName = getHostName();
+      final Set<Cluster> clustersForHost = clusters.getClustersForHost(hostName);
+      final Cluster cluster = clustersForHost.iterator().next();
+      final StackId stackId = cluster.getDesiredStackVersion();
+      final ServiceInfo serviceInfo = ambariMetaInfo.getService(stackId.getStackName(), stackId.getStackVersion(), getServiceName());
+
+      return serviceInfo.isExtensionService();
+    }
+    catch (Exception e) {
+      return false;
+    }
+  }
+
   /**
    * Bootstrap any Repo Version, and potentially transition the Host Version across states.
    * If a Host Component has a valid version, then create a Host Version if it does not already exist.
@@ -1757,6 +1824,7 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
   @Override
   public RepositoryVersionEntity recalculateHostVersionState() throws AmbariException {
     RepositoryVersionEntity repositoryVersion = null;
+
     String version = getVersion();
     if (version == null || version.isEmpty() || version.equalsIgnoreCase(State.UNKNOWN.toString())) {
       // Recalculate only if some particular version is set
@@ -1770,6 +1838,11 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
     }
     final Cluster cluster = clustersForHost.iterator().next();
     final StackId stackId = cluster.getDesiredStackVersion();
+
+    final ServiceInfo serviceInfo = ambariMetaInfo.getService(stackId.getStackName(), stackId.getStackVersion(), getServiceName());
+    if (serviceInfo.isExtensionService())
+      return null;
+
     final StackInfo stackInfo = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
 
     writeLock.lock();
@@ -1790,6 +1863,59 @@ public class ServiceComponentHostImpl implements ServiceComponentHost {
     return repositoryVersion;
   }
 
+  /**
+   * Bootstrap any Extension Repository Version, and potentially transition the Host Version across states.
+   * If a Host Component has a valid version, then create a Host Version if it does not already exist.
+   * If a Host Component does not have a version, return right away because no information is known.
+   * @return Return the Extension Repository Version object
+   * @throws AmbariException
+   */
+  @Override
+  public ExtensionRepositoryVersionEntity recalculateHostExtensionVersionState() throws AmbariException {
+    ExtensionRepositoryVersionEntity repositoryVersion = null;
+
+    String version = getVersion();
+    if (version == null || version.isEmpty() || version.equalsIgnoreCase(State.UNKNOWN.toString())) {
+      // Recalculate only if some particular version is set
+      return null;
+    }
+
+    final String hostName = getHostName();
+    final Set<Cluster> clustersForHost = clusters.getClustersForHost(hostName);
+    if (clustersForHost.size() != 1) {
+      throw new AmbariException("Host " + hostName + " should be assigned only to one cluster");
+    }
+    final Cluster cluster = clustersForHost.iterator().next();
+    final StackId stackId = cluster.getDesiredStackVersion();
+
+    String serviceName = getServiceName();
+
+    final ServiceInfo serviceInfo = ambariMetaInfo.getService(stackId.getStackName(), stackId.getStackVersion(), serviceName);
+    if (!serviceInfo.isExtensionService())
+      return null;
+
+    final StackInfo stackInfo = ambariMetaInfo.getStack(stackId.getStackName(), stackId.getStackVersion());
+    ExtensionInfo extensionInfo = stackInfo.getExtensionByService(serviceName);
+
+    writeLock.lock();
+    try {
+      // Check if there is a Extension Repo Version already for the version.
+      // If it doesn't exist, will have to create it.s
+      repositoryVersion = extensionRepositoryVersionDAO.findByExtensionNameAndVersion(extensionInfo.getName(), version);
+
+      if (null == repositoryVersion) {
+        repositoryVersion = createExtensionRepositoryVersion(version, extensionInfo);
+      }
+
+      final HostEntity host = hostDAO.findByName(hostName);
+      ExtensionId extensionId = new ExtensionId(extensionInfo);
+      cluster.transitionHostExtensionVersionState(host, repositoryVersion, extensionId);
+    } finally {
+      writeLock.unlock();
+    }
+    return repositoryVersion;
+  }
+
   // Get the cached desired state entity or load it fresh through the DAO.
   private HostComponentDesiredStateEntity getDesiredStateEntity() {
     if (isPersisted()) {

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostSummary.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostSummary.java b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostSummary.java
index 1c36143..4c7e5df 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostSummary.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/state/svccomphost/ServiceComponentHostSummary.java
@@ -24,6 +24,7 @@ import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.orm.entities.HostComponentStateEntity;
 import org.apache.ambari.server.orm.entities.HostEntity;
 import org.apache.ambari.server.state.ComponentInfo;
+import org.apache.ambari.server.state.ExtensionId;
 import org.apache.ambari.server.state.StackId;
 import org.apache.ambari.server.state.State;
 import org.apache.commons.lang.StringUtils;
@@ -45,12 +46,17 @@ public class ServiceComponentHostSummary  {
   private Set<String> versions;
 
 
-  public ServiceComponentHostSummary(AmbariMetaInfo ambariMetaInfo, HostEntity host, String stackName, String stackVersion) throws AmbariException {
-    allHostComponents = host.getHostComponentStateEntities();
+  public ServiceComponentHostSummary(HostEntity host) throws AmbariException {
     haveAdvertisedVersion = new HashSet<HostComponentStateEntity>();
     waitingToAdvertiseVersion = new HashSet<HostComponentStateEntity>();
     noVersionToAdvertise = new HashSet<HostComponentStateEntity>();
     versions = new HashSet<String>();
+    allHostComponents = host.getHostComponentStateEntities();
+  }
+
+
+  public ServiceComponentHostSummary(AmbariMetaInfo ambariMetaInfo, HostEntity host, String stackName, String stackVersion) throws AmbariException {
+    this(host);
 
     for (HostComponentStateEntity hostComponentStateEntity: allHostComponents) {
       ComponentInfo compInfo = ambariMetaInfo.getComponent(
@@ -75,6 +81,30 @@ public class ServiceComponentHostSummary  {
     this(ambariMetaInfo, host, stackId.getStackName(), stackId.getStackVersion());
   }
 
+  public ServiceComponentHostSummary(AmbariMetaInfo ambariMetaInfo, HostEntity host, ExtensionId extensionId) throws AmbariException {
+    this(host);
+
+    for (HostComponentStateEntity hostComponentStateEntity: allHostComponents) {
+      ComponentInfo compInfo = ambariMetaInfo.getExtensionComponentSafe(
+	  extensionId.getExtensionName(), extensionId.getExtensionVersion(), hostComponentStateEntity.getServiceName(),
+          hostComponentStateEntity.getComponentName());
+
+      if (compInfo != null) {
+        if (!compInfo.isVersionAdvertised()) {
+          // Some Components cannot advertise a version. E.g., ZKF, AMBARI_METRICS, Kerberos
+          noVersionToAdvertise.add(hostComponentStateEntity);
+        } else {
+          if (hostComponentStateEntity.getVersion() == null || hostComponentStateEntity.getVersion().isEmpty() || hostComponentStateEntity.getVersion().equalsIgnoreCase(State.UNKNOWN.toString())) {
+            waitingToAdvertiseVersion.add(hostComponentStateEntity);
+          } else {
+            haveAdvertisedVersion.add(hostComponentStateEntity);
+            versions.add(hostComponentStateEntity.getVersion());
+          }
+        }
+      }
+    }
+  }
+
   public Collection<HostComponentStateEntity> getHaveAdvertisedVersion() {
     return haveAdvertisedVersion;
   }

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/java/org/apache/ambari/server/upgrade/StackUpgradeUtil.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/StackUpgradeUtil.java b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/StackUpgradeUtil.java
index 8c629ca..e893468 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/upgrade/StackUpgradeUtil.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/upgrade/StackUpgradeUtil.java
@@ -50,6 +50,7 @@ public class StackUpgradeUtil {
 
   @Transactional
   public void updateStackDetails(String stackName, String stackVersion) {
+    // TODO - need to handle extension upgrade as well
     ClusterDAO clusterDAO = injector.getInstance(ClusterDAO.class);
     StackDAO stackDAO = injector.getInstance(StackDAO.class);
     List<Long> clusterIds = new ArrayList<Long>();

http://git-wip-us.apache.org/repos/asf/ambari/blob/647929db/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
index 5d65665..c861152 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -34,6 +34,20 @@ CREATE TABLE stack(
   PRIMARY KEY (stack_id)
 );
 
+CREATE TABLE extension(
+  extension_id BIGINT NOT NULL,
+  extension_name VARCHAR(255) NOT NULL,
+  extension_version VARCHAR(255) NOT NULL,
+  PRIMARY KEY (extension_id)
+);
+
+CREATE TABLE extensionlink(
+  link_id BIGINT NOT NULL,
+  stack_id BIGINT NOT NULL,
+  extension_id BIGINT NOT NULL,
+  PRIMARY KEY (link_id)
+);
+
 CREATE TABLE clusters (
   cluster_id BIGINT NOT NULL,
   resource_id BIGINT NOT NULL,
@@ -105,10 +119,21 @@ CREATE TABLE cluster_version (
   user_name VARCHAR(32),
   PRIMARY KEY (id));
 
+CREATE TABLE cluster_extension_version (
+  id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  cluster_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  start_time BIGINT NOT NULL,
+  end_time BIGINT,
+  user_name VARCHAR(32),
+  PRIMARY KEY (id));
+
 CREATE TABLE hostcomponentdesiredstate (
   cluster_id BIGINT NOT NULL,
   component_name VARCHAR(100) NOT NULL,
   desired_stack_id BIGINT NOT NULL,
+  desired_extension_id BIGINT,
   desired_state VARCHAR(255) NOT NULL,
   host_id BIGINT NOT NULL,
   service_name VARCHAR(100) NOT NULL,
@@ -125,6 +150,7 @@ CREATE TABLE hostcomponentstate (
   component_name VARCHAR(100) NOT NULL,
   version VARCHAR(32) NOT NULL DEFAULT 'UNKNOWN',
   current_stack_id BIGINT NOT NULL,
+  current_extension_id BIGINT,
   current_state VARCHAR(255) NOT NULL,
   host_id BIGINT NOT NULL,
   service_name VARCHAR(100) NOT NULL,
@@ -171,6 +197,13 @@ CREATE TABLE host_version (
   state VARCHAR(32) NOT NULL,
   PRIMARY KEY (id));
 
+CREATE TABLE host_extension_version (
+  id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  host_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  PRIMARY KEY (id));
+
 CREATE TABLE servicecomponentdesiredstate (
   component_name VARCHAR(100) NOT NULL,
   cluster_id BIGINT NOT NULL,
@@ -674,6 +707,8 @@ ALTER TABLE adminpermission ADD CONSTRAINT UQ_perm_name_resource_type_id UNIQUE
 ALTER TABLE repo_version ADD CONSTRAINT UQ_repo_version_display_name UNIQUE (display_name);
 ALTER TABLE repo_version ADD CONSTRAINT UQ_repo_version_stack_id UNIQUE (stack_id, version);
 ALTER TABLE stack ADD CONSTRAINT unq_stack UNIQUE (stack_name, stack_version);
+ALTER TABLE extension ADD CONSTRAINT unq_extension UNIQUE (extension_name, extension_version);
+ALTER TABLE extensionlink ADD CONSTRAINT unq_extension_link UNIQUE (stack_id, extension_id);
 
 -- altering tables by creating foreign keys----------
 -- Note, Oracle has a limitation of 32 chars in the FK name, and we should use the same FK name in all DB types.
@@ -755,11 +790,15 @@ ALTER TABLE clusterconfig ADD CONSTRAINT FK_clusterconfig_stack_id FOREIGN KEY (
 ALTER TABLE serviceconfig ADD CONSTRAINT FK_serviceconfig_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
 ALTER TABLE clusterstate ADD CONSTRAINT FK_cs_current_stack_id FOREIGN KEY (current_stack_id) REFERENCES stack(stack_id);
 ALTER TABLE hostcomponentdesiredstate ADD CONSTRAINT FK_hcds_desired_stack_id FOREIGN KEY (desired_stack_id) REFERENCES stack(stack_id);
+ALTER TABLE hostcomponentdesiredstate ADD CONSTRAINT FK_hcds_desired_extension_id FOREIGN KEY (desired_extension_id) REFERENCES extension(extension_id);
 ALTER TABLE hostcomponentstate ADD CONSTRAINT FK_hcs_current_stack_id FOREIGN KEY (current_stack_id) REFERENCES stack(stack_id);
+ALTER TABLE hostcomponentstate ADD CONSTRAINT FK_hcs_current_extension_id FOREIGN KEY (current_extension_id) REFERENCES extension(extension_id);
 ALTER TABLE servicecomponentdesiredstate ADD CONSTRAINT FK_scds_desired_stack_id FOREIGN KEY (desired_stack_id) REFERENCES stack(stack_id);
 ALTER TABLE servicedesiredstate ADD CONSTRAINT FK_sds_desired_stack_id FOREIGN KEY (desired_stack_id) REFERENCES stack(stack_id);
 ALTER TABLE blueprint ADD CONSTRAINT FK_blueprint_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
 ALTER TABLE repo_version ADD CONSTRAINT FK_repoversion_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
+ALTER TABLE extensionlink ADD CONSTRAINT FK_extensionlink_stack_id FOREIGN KEY (stack_id) REFERENCES stack(stack_id);
+ALTER TABLE extensionlink ADD CONSTRAINT FK_extensionlink_extension_id FOREIGN KEY (extension_id) REFERENCES extension(extension_id);
 
 -- Kerberos
 CREATE TABLE kerberos_principal (


Mime
View raw message