فهرست منبع

AMBARI-18557. Create Component to Repo Version db associations (ncole)

Nate Cole 9 سال پیش
والد
کامیت
eb982a0eb3
16فایلهای تغییر یافته به همراه546 افزوده شده و 44 حذف شده
  1. 2 2
      ambari-server/src/main/java/org/apache/ambari/server/controller/AmbariManagementControllerImpl.java
  2. 22 1
      ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceComponentDesiredStateDAO.java
  3. 24 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceComponentDesiredStateEntity.java
  4. 159 0
      ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceComponentVersionEntity.java
  5. 2 1
      ambari-server/src/main/java/org/apache/ambari/server/stack/RepoUtil.java
  6. 2 2
      ambari-server/src/main/java/org/apache/ambari/server/state/repository/VersionDefinitionXml.java
  7. 40 0
      ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog250.java
  8. 14 1
      ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
  9. 13 1
      ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
  10. 12 0
      ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
  11. 13 1
      ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
  12. 12 0
      ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
  13. 13 1
      ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
  14. 1 0
      ambari-server/src/main/resources/META-INF/persistence.xml
  15. 130 10
      ambari-server/src/test/java/org/apache/ambari/server/state/ServiceComponentTest.java
  16. 87 24
      ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog250Test.java

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

@@ -201,6 +201,7 @@ import org.slf4j.LoggerFactory;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.ListMultimap;
 import com.google.common.collect.Multimap;
 import com.google.gson.Gson;
 import com.google.gson.reflect.TypeToken;
@@ -208,7 +209,6 @@ import com.google.inject.Inject;
 import com.google.inject.Injector;
 import com.google.inject.Singleton;
 import com.google.inject.persist.Transactional;
-import com.google.common.collect.ListMultimap;
 
 @Singleton
 public class AmbariManagementControllerImpl implements AmbariManagementController {
@@ -2333,7 +2333,7 @@ public class AmbariManagementControllerImpl implements AmbariManagementControlle
     execCmd.setAvailableServicesFromServiceInfoMap(ambariMetaInfo.getServices(stackId.getStackName(), stackId.getStackVersion()));
 
     if ((execCmd != null) && (execCmd.getConfigurationTags().containsKey("cluster-env"))) {
-      LOG.info("AmbariManagementControllerImpl.createHostAction: created ExecutionCommand for host {}, role {}, roleCommand {}, and command ID {}, with cluster-env tags {}",
+      LOG.debug("AmbariManagementControllerImpl.createHostAction: created ExecutionCommand for host {}, role {}, roleCommand {}, and command ID {}, with cluster-env tags {}",
         execCmd.getHostname(), execCmd.getRole(), execCmd.getRoleCommand(), execCmd.getCommandId(), execCmd.getConfigurationTags().get("cluster-env").get("tag"));
     }
   }

+ 22 - 1
ambari-server/src/main/java/org/apache/ambari/server/orm/dao/ServiceComponentDesiredStateDAO.java

@@ -28,6 +28,7 @@ import javax.persistence.TypedQuery;
 import org.apache.ambari.server.orm.RequiresSession;
 import org.apache.ambari.server.orm.entities.ServiceComponentDesiredStateEntity;
 import org.apache.ambari.server.orm.entities.ServiceComponentHistoryEntity;
+import org.apache.ambari.server.orm.entities.ServiceComponentVersionEntity;
 
 import com.google.inject.Inject;
 import com.google.inject.Provider;
@@ -175,7 +176,27 @@ public class ServiceComponentDesiredStateDAO {
     query.setParameter("serviceName", serviceName);
     query.setParameter("componentName", componentName);
 
-    ServiceComponentDesiredStateEntity entity = null;
     return daoUtils.selectList(query);
   }
+
+  /**
+   * @param clusterId     the cluster id
+   * @param serviceName   the service name
+   * @param componentName the component name
+   * @return the list of repository versions for a component
+   */
+  @RequiresSession
+  public List<ServiceComponentVersionEntity> findVersions(long clusterId, String serviceName,
+      String componentName) {
+    EntityManager entityManager = entityManagerProvider.get();
+    TypedQuery<ServiceComponentVersionEntity> query = entityManager.createNamedQuery(
+        "ServiceComponentVersionEntity.findByComponent", ServiceComponentVersionEntity.class);
+
+    query.setParameter("clusterId", clusterId);
+    query.setParameter("serviceName", serviceName);
+    query.setParameter("componentName", componentName);
+
+    return daoUtils.selectList(query);
+  }
+
 }

+ 24 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceComponentDesiredStateEntity.java

@@ -117,6 +117,9 @@ public class ServiceComponentDesiredStateEntity {
       cascade = { CascadeType.ALL })
   private Collection<ServiceComponentHistoryEntity> serviceComponentHistory;
 
+  @OneToMany(mappedBy = "m_serviceComponentDesiredStateEntity", cascade = { CascadeType.ALL })
+  private Collection<ServiceComponentVersionEntity> serviceComponentVersion;
+
   public Long getId() {
     return id;
   }
@@ -195,6 +198,27 @@ public class ServiceComponentDesiredStateEntity {
     return serviceComponentHistory;
   }
 
+
+  /**
+   * @param versionEntry the version to add
+   */
+  public void addVersion(ServiceComponentVersionEntity versionEntry) {
+    if (null == serviceComponentVersion) {
+      serviceComponentVersion = new ArrayList<>();
+    }
+
+    serviceComponentVersion.add(versionEntry);
+    versionEntry.setServiceComponentDesiredState(this);
+  }
+
+  /**
+   * @return the collection of versions for the component
+   */
+  public Collection<ServiceComponentVersionEntity> getVersions() {
+    return serviceComponentVersion;
+  }
+
+
   public boolean isRecoveryEnabled() {
     return recoveryEnabled != 0;
   }

+ 159 - 0
ambari-server/src/main/java/org/apache/ambari/server/orm/entities/ServiceComponentVersionEntity.java

@@ -0,0 +1,159 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.entities;
+
+import java.util.Objects;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+import org.apache.ambari.server.state.RepositoryVersionState;
+
+/**
+ * The {@link ServiceComponentVersionEntity} class is used to represent the
+ * association of a component and repository version.
+ */
+@Entity
+@Table(name = "servicecomponent_version")
+@TableGenerator(
+    name = "servicecomponent_version_id_generator",
+    table = "ambari_sequences",
+    pkColumnName = "sequence_name",
+    valueColumnName = "sequence_value",
+    pkColumnValue = "servicecomponent_version_id_seq",
+    initialValue = 0)
+@NamedQueries({ @NamedQuery(
+    name = "ServiceComponentVersionEntity.findByComponent",
+    query = "SELECT version FROM ServiceComponentVersionEntity version WHERE version.m_serviceComponentDesiredStateEntity.clusterId = :clusterId AND version.m_serviceComponentDesiredStateEntity.serviceName = :serviceName AND version.m_serviceComponentDesiredStateEntity.componentName = :componentName") })
+public class ServiceComponentVersionEntity {
+
+  @Id
+  @GeneratedValue(
+      strategy = GenerationType.TABLE,
+      generator = "servicecomponent_version_id_generator")
+  @Column(name = "id", nullable = false, updatable = false)
+  private long m_id;
+
+  @ManyToOne(optional = false, cascade = { CascadeType.MERGE })
+  @JoinColumn(name = "component_id", referencedColumnName = "id", nullable = false)
+  private ServiceComponentDesiredStateEntity m_serviceComponentDesiredStateEntity;
+
+  @ManyToOne
+  @JoinColumn(name = "repo_version_id", referencedColumnName = "repo_version_id", nullable = false)
+  private RepositoryVersionEntity m_repositoryVersion;
+
+  @Column(name = "state", nullable = false, insertable = true, updatable = true)
+  @Enumerated(value = EnumType.STRING)
+  private RepositoryVersionState m_state = RepositoryVersionState.CURRENT;
+
+  @Column(name = "user_name", nullable = false, insertable=true, updatable=true)
+  private String userName;
+
+
+  /**
+   * @return the associated component
+   */
+  public ServiceComponentDesiredStateEntity getServiceComponentDesiredState() {
+    return m_serviceComponentDesiredStateEntity;
+  }
+
+  /**
+   * @param serviceComponentDesiredStateEntity  the associated component (not {@code null})
+   */
+  public void setServiceComponentDesiredState(ServiceComponentDesiredStateEntity serviceComponentDesiredStateEntity) {
+    m_serviceComponentDesiredStateEntity = serviceComponentDesiredStateEntity;
+  }
+
+  /**
+   * @param repositoryVersion the repository
+   */
+  public void setRepositoryVersion(RepositoryVersionEntity repositoryVersion) {
+    m_repositoryVersion = repositoryVersion;
+  }
+
+  /**
+   * @return the id
+   */
+  public long getId() {
+    return m_id;
+  }
+
+  /**
+   * @return the state of the repository
+   */
+  public RepositoryVersionState getState() {
+    return m_state;
+  }
+
+  /**
+   * @param state the state of the repository
+   */
+  public void setState(RepositoryVersionState state) {
+    m_state = state;
+  }
+
+  /**
+   * @return the user name
+   */
+  public String getUserName() {
+    return userName;
+  }
+
+  /**
+   * @param name  the user name
+   */
+  public void setUserName(String name) {
+    userName = name;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(m_id, m_repositoryVersion, m_serviceComponentDesiredStateEntity, m_state);
+  }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (this == obj) {
+      return true;
+    }
+
+    if (obj == null || getClass() != obj.getClass()) {
+      return false;
+    }
+
+    final ServiceComponentVersionEntity other = (ServiceComponentVersionEntity) obj;
+
+    return Objects.equals(m_id, other.m_id)
+        && Objects.equals(m_repositoryVersion, other.m_repositoryVersion)
+        && Objects.equals(m_serviceComponentDesiredStateEntity, other.m_serviceComponentDesiredStateEntity)
+        && Objects.equals(m_state, other.m_state);
+  }
+
+}

+ 2 - 1
ambari-server/src/main/java/org/apache/ambari/server/stack/RepoUtil.java

@@ -23,6 +23,7 @@ import java.util.Collection;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
+
 import javax.annotation.Nullable;
 import javax.xml.bind.JAXBException;
 
@@ -153,7 +154,7 @@ public class RepoUtil {
         }
       }
     }
-    LOG.info("Found {} service repos: {}", serviceRepoIds.size(),Iterables.toString(serviceRepoIds));
+    LOG.debug("Found {} service repos: {}", serviceRepoIds.size(),Iterables.toString(serviceRepoIds));
     return serviceRepos;
   }
 

+ 2 - 2
ambari-server/src/main/java/org/apache/ambari/server/state/repository/VersionDefinitionXml.java

@@ -120,7 +120,7 @@ public class VersionDefinitionXml {
    * collection is either the subset of the manifest, or the manifest itself if no services
    * are specified as "available".
    */
-  public Collection<AvailableService> getAvailableServices(StackInfo stack) {
+  public synchronized Collection<AvailableService> getAvailableServices(StackInfo stack) {
     if (null == m_availableMap) {
       Map<String, ManifestService> manifests = buildManifest();
       m_availableMap = new HashMap<>();
@@ -155,7 +155,7 @@ public class VersionDefinitionXml {
    * @param stack the stack for which to get the information
    * @return the list of {@code ManifestServiceInfo} instances for each service in the stack
    */
-  public List<ManifestServiceInfo> getStackServices(StackInfo stack) {
+  public synchronized List<ManifestServiceInfo> getStackServices(StackInfo stack) {
 
     if (null != m_manifest) {
       return m_manifest;

+ 40 - 0
ambari-server/src/main/java/org/apache/ambari/server/upgrade/UpgradeCatalog250.java

@@ -27,6 +27,7 @@ import java.util.Map;
 
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.orm.DBAccessor.DBColumnInfo;
 import org.apache.ambari.server.orm.dao.DaoUtils;
 import org.apache.ambari.server.state.Cluster;
 import org.apache.ambari.server.state.Clusters;
@@ -45,6 +46,15 @@ public class UpgradeCatalog250 extends AbstractUpgradeCatalog {
 
   protected static final String HOST_VERSION_TABLE = "host_version";
   private static final String AMS_ENV = "ams-env";
+  private static final String KAFKA_BROKER = "kafka-broker";
+  private static final String KAFKA_TIMELINE_METRICS_HOST = "kafka.timeline.metrics.host";
+
+  public static final String COMPONENT_TABLE = "servicecomponentdesiredstate";
+  public static final String COMPONENT_VERSION_TABLE = "servicecomponent_version";
+  public static final String COMPONENT_VERSION_PK = "PK_sc_version";
+  public static final String COMPONENT_VERSION_FK_COMPONENT = "FK_scv_component_id";
+  public static final String COMPONENT_VERSION_FK_REPO_VERSION = "FK_scv_repo_version_id";
+
   /**
    * Logger.
    */
@@ -93,6 +103,7 @@ public class UpgradeCatalog250 extends AbstractUpgradeCatalog {
   @Override
   protected void executeDDLUpdates() throws AmbariException, SQLException {
     updateHostVersionTable();
+    createComponentVersionTable();
   }
 
   /**
@@ -183,5 +194,34 @@ public class UpgradeCatalog250 extends AbstractUpgradeCatalog {
     addRoleAuthorization("AMBARI.RUN_CUSTOM_COMMAND", "Perform custom administrative actions",
         Collections.singletonList("AMBARI.ADMINISTRATOR:AMBARI"));
   }
+
+  /**
+   * Creates the servicecomponent_version table
+   * @throws SQLException
+   */
+  private void createComponentVersionTable() throws SQLException {
+
+    List<DBColumnInfo> columns = new ArrayList<>();
+
+    // Add extension link table
+    LOG.info("Creating {} table", COMPONENT_VERSION_TABLE);
+
+    columns.add(new DBColumnInfo("id", Long.class, null, null, false));
+    columns.add(new DBColumnInfo("component_id", Long.class, null, null, false));
+    columns.add(new DBColumnInfo("repo_version_id", Long.class, null, null, false));
+    columns.add(new DBColumnInfo("state", String.class, 32, null, false));
+    columns.add(new DBColumnInfo("user_name", String.class, 255, null, false));
+    dbAccessor.createTable(COMPONENT_VERSION_TABLE, columns, (String[]) null);
+
+    dbAccessor.addPKConstraint(COMPONENT_VERSION_TABLE, COMPONENT_VERSION_PK, "id");
+
+    dbAccessor.addFKConstraint(COMPONENT_VERSION_TABLE, COMPONENT_VERSION_FK_COMPONENT, "component_id",
+        COMPONENT_TABLE, "id", false);
+
+    dbAccessor.addFKConstraint(COMPONENT_VERSION_TABLE, COMPONENT_VERSION_FK_REPO_VERSION, "repo_version_id",
+        "repo_version", "repo_version_id", false);
+
+    addSequence("servicecomponent_version_id_seq", 0L, false);
+  }
 }
 

+ 14 - 1
ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql

@@ -872,6 +872,17 @@ CREATE TABLE servicecomponent_history(
   CONSTRAINT FK_sc_history_to_stack_id FOREIGN KEY (to_stack_id) REFERENCES stack (stack_id)
 );
 
+CREATE TABLE servicecomponent_version(
+  id BIGINT NOT NULL,
+  component_id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  user_name VARCHAR(255) NOT NULL,
+  CONSTRAINT PK_sc_version PRIMARY KEY (id),
+  CONSTRAINT FK_scv_component_id FOREIGN KEY (component_id) REFERENCES servicecomponentdesiredstate (id),
+  CONSTRAINT FK_scv_repo_version_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id)
+);
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1142,7 +1153,9 @@ INSERT INTO ambari_sequences (sequence_name, sequence_value)
   union all
   select 'remote_cluster_id_seq', 0 FROM SYSIBM.SYSDUMMY1
   union all
-  select 'remote_cluster_service_id_seq', 0 FROM SYSIBM.SYSDUMMY1;
+  select 'remote_cluster_service_id_seq', 0 FROM SYSIBM.SYSDUMMY1
+  union all
+  select 'servicecomponent_version_id_seq', 0 FROM SYSIBM.SYSDUMMY1;
 
 
 INSERT INTO adminresourcetype (resource_type_id, resource_type_name)

+ 13 - 1
ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql

@@ -879,6 +879,17 @@ CREATE TABLE servicecomponent_history(
   CONSTRAINT FK_sc_history_to_stack_id FOREIGN KEY (to_stack_id) REFERENCES stack (stack_id)
 );
 
+CREATE TABLE servicecomponent_version(
+  id BIGINT NOT NULL,
+  component_id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  user_name VARCHAR(255) NOT NULL,
+  CONSTRAINT PK_sc_version PRIMARY KEY (id),
+  CONSTRAINT FK_scv_component_id FOREIGN KEY (component_id) REFERENCES servicecomponentdesiredstate (id),
+  CONSTRAINT FK_scv_repo_version_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id)
+);
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1097,7 +1108,8 @@ INSERT INTO ambari_sequences(sequence_name, sequence_value) VALUES
   ('blueprint_setting_id_seq', 0),
   ('ambari_operation_history_id_seq', 0),
   ('remote_cluster_id_seq', 0),
-  ('remote_cluster_service_id_seq', 0);
+  ('remote_cluster_service_id_seq', 0),
+  ('servicecomponent_version_id_seq', 0);
 
 INSERT INTO adminresourcetype (resource_type_id, resource_type_name) VALUES
   (1, 'AMBARI'),

+ 12 - 0
ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql

@@ -869,6 +869,17 @@ CREATE TABLE servicecomponent_history(
   CONSTRAINT FK_sc_history_to_stack_id FOREIGN KEY (to_stack_id) REFERENCES stack (stack_id)
 );
 
+CREATE TABLE servicecomponent_version(
+  id NUMBER(19) NOT NULL,
+  component_id NUMBER(19) NOT NULL,
+  repo_version_id NUMBER(19) NOT NULL,
+  state VARCHAR2(32) NOT NULL,
+  user_name VARCHAR2(255) NOT NULL,
+  CONSTRAINT PK_sc_version PRIMARY KEY (id),
+  CONSTRAINT FK_scv_component_id FOREIGN KEY (component_id) REFERENCES servicecomponentdesiredstate (id),
+  CONSTRAINT FK_scv_repo_version_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id)
+);
+
 CREATE TABLE ambari_operation_history(
   id NUMBER(19) NOT NULL,
   from_version VARCHAR2(255) NOT NULL,
@@ -1088,6 +1099,7 @@ INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('blueprint_s
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('ambari_operation_history_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('remote_cluster_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('remote_cluster_service_id_seq', 0);
+INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('servicecomponent_version_id_seq', 0);
 
 INSERT INTO metainfo("metainfo_key", "metainfo_value") values ('version', '${ambariSchemaVersion}');
 

+ 13 - 1
ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql

@@ -871,6 +871,17 @@ CREATE TABLE servicecomponent_history(
   CONSTRAINT FK_sc_history_to_stack_id FOREIGN KEY (to_stack_id) REFERENCES stack (stack_id)
 );
 
+CREATE TABLE servicecomponent_version(
+  id BIGINT NOT NULL,
+  component_id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  user_name VARCHAR(255) NOT NULL,
+  CONSTRAINT PK_sc_version PRIMARY KEY (id),
+  CONSTRAINT FK_scv_component_id FOREIGN KEY (component_id) REFERENCES servicecomponentdesiredstate (id),
+  CONSTRAINT FK_scv_repo_version_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id)
+);
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1088,7 +1099,8 @@ INSERT INTO ambari_sequences (sequence_name, sequence_value) VALUES
   ('blueprint_setting_id_seq', 0),
   ('ambari_operation_history_id_seq', 0),
   ('remote_cluster_id_seq', 0),
-  ('remote_cluster_service_id_seq', 0);
+  ('remote_cluster_service_id_seq', 0),
+  ('servicecomponent_version_id_seq', 0);
 
 INSERT INTO adminresourcetype (resource_type_id, resource_type_name) VALUES
   (1, 'AMBARI'),

+ 12 - 0
ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql

@@ -868,6 +868,17 @@ CREATE TABLE servicecomponent_history(
   CONSTRAINT FK_sc_history_to_stack_id FOREIGN KEY (to_stack_id) REFERENCES stack (stack_id)
 );
 
+CREATE TABLE servicecomponent_version(
+  id NUMERIC(19) NOT NULL,
+  component_id NUMERIC(19) NOT NULL,
+  repo_version_id NUMERIC(19) NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  user_name VARCHAR(255) NOT NULL,
+  CONSTRAINT PK_sc_version PRIMARY KEY (id),
+  CONSTRAINT FK_scv_component_id FOREIGN KEY (component_id) REFERENCES servicecomponentdesiredstate (id),
+  CONSTRAINT FK_scv_repo_version_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id)
+);
+
 CREATE TABLE ambari_operation_history(
   id NUMERIC(19) NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1087,6 +1098,7 @@ INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('blueprint_s
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('ambari_operation_history_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('remote_cluster_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('remote_cluster_service_id_seq', 0);
+INSERT INTO ambari_sequences(sequence_name, sequence_value) values ('servicecomponent_version_id_seq', 0);
 
 insert into adminresourcetype (resource_type_id, resource_type_name)
   select 1, 'AMBARI'

+ 13 - 1
ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql

@@ -889,6 +889,17 @@ CREATE TABLE servicecomponent_history(
   CONSTRAINT FK_sc_history_to_stack_id FOREIGN KEY (to_stack_id) REFERENCES stack (stack_id)
 );
 
+CREATE TABLE servicecomponent_version(
+  id BIGINT NOT NULL,
+  component_id BIGINT NOT NULL,
+  repo_version_id BIGINT NOT NULL,
+  state VARCHAR(32) NOT NULL,
+  user_name VARCHAR(255) NOT NULL,
+  CONSTRAINT PK_sc_version PRIMARY KEY (id),
+  CONSTRAINT FK_scv_component_id FOREIGN KEY (component_id) REFERENCES servicecomponentdesiredstate (id),
+  CONSTRAINT FK_scv_repo_version_id FOREIGN KEY (repo_version_id) REFERENCES repo_version (repo_version_id)
+);
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1112,7 +1123,8 @@ BEGIN TRANSACTION
     ('blueprint_setting_id_seq', 0),
     ('ambari_operation_history_id_seq', 0),
     ('remote_cluster_id_seq', 0),
-    ('remote_cluster_service_id_seq', 0);
+    ('remote_cluster_service_id_seq', 0),
+    ('servicecomponent_version_id_seq', 0);
 
   insert into adminresourcetype (resource_type_id, resource_type_name)
   values

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

@@ -67,6 +67,7 @@
     <class>org.apache.ambari.server.orm.entities.RoleSuccessCriteriaEntity</class>
     <class>org.apache.ambari.server.orm.entities.ServiceComponentDesiredStateEntity</class>
     <class>org.apache.ambari.server.orm.entities.ServiceComponentHistoryEntity</class>
+    <class>org.apache.ambari.server.orm.entities.ServiceComponentVersionEntity</class>
     <class>org.apache.ambari.server.orm.entities.ServiceConfigEntity</class>
     <class>org.apache.ambari.server.orm.entities.ServiceDesiredStateEntity</class>
     <class>org.apache.ambari.server.orm.entities.StackEntity</class>

+ 130 - 10
ambari-server/src/test/java/org/apache/ambari/server/state/ServiceComponentTest.java

@@ -18,10 +18,14 @@
 
 package org.apache.ambari.server.state;
 
-import com.google.inject.Guice;
-import com.google.inject.Injector;
-import com.google.inject.persist.PersistService;
-import junit.framework.Assert;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.api.services.AmbariMetaInfo;
 import org.apache.ambari.server.controller.ServiceComponentResponse;
@@ -31,14 +35,17 @@ import org.apache.ambari.server.orm.OrmTestHelper;
 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.UpgradeDAO;
 import org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntity;
 import org.apache.ambari.server.orm.entities.HostComponentDesiredStateEntityPK;
 import org.apache.ambari.server.orm.entities.HostComponentStateEntity;
 import org.apache.ambari.server.orm.entities.HostEntity;
+import org.apache.ambari.server.orm.entities.RepositoryVersionEntity;
 import org.apache.ambari.server.orm.entities.ServiceComponentDesiredStateEntity;
 import org.apache.ambari.server.orm.entities.ServiceComponentHistoryEntity;
+import org.apache.ambari.server.orm.entities.ServiceComponentVersionEntity;
 import org.apache.ambari.server.orm.entities.UpgradeEntity;
 import org.apache.ambari.server.state.stack.upgrade.Direction;
 import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
@@ -46,13 +53,11 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.persist.PersistService;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
+import junit.framework.Assert;
 
 public class ServiceComponentTest {
 
@@ -510,6 +515,7 @@ public class ServiceComponentTest {
     ServiceComponentDesiredStateEntity serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.findByName(
         cluster.getClusterId(), serviceName, componentName);
 
+
     Assert.assertNotNull(serviceComponentDesiredStateEntity);
 
     UpgradeEntity upgradeEntity = createUpgradeEntity("2.2.0.0", "2.2.0.1");
@@ -551,6 +557,119 @@ public class ServiceComponentTest {
     assertEquals(0, componentHistoryList.size());
   }
 
+  @Test
+  public void testVersionCreation() throws Exception {
+    ServiceComponentDesiredStateDAO serviceComponentDesiredStateDAO = injector.getInstance(
+        ServiceComponentDesiredStateDAO.class);
+
+    String componentName = "NAMENODE";
+    ServiceComponent component = serviceComponentFactory.createNew(service, componentName);
+    service.addServiceComponent(component);
+    component.persist();
+
+    ServiceComponent sc = service.getServiceComponent(componentName);
+    Assert.assertNotNull(sc);
+
+    sc.setDesiredState(State.INSTALLED);
+    Assert.assertEquals(State.INSTALLED, sc.getDesiredState());
+
+    sc.setDesiredStackVersion(new StackId("HDP-2.2.0"));
+    StackId stackId = sc.getDesiredStackVersion();
+    Assert.assertEquals(new StackId("HDP", "2.2.0"), stackId);
+
+    Assert.assertEquals("HDP-2.2.0", sc.getDesiredStackVersion().getStackId());
+
+    ServiceComponentDesiredStateEntity serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.findByName(
+        cluster.getClusterId(), serviceName, componentName);
+
+    Assert.assertNotNull(serviceComponentDesiredStateEntity);
+
+    RepositoryVersionEntity rve = new RepositoryVersionEntity(
+        serviceComponentDesiredStateEntity.getDesiredStack(), "HDP-2.2.0", "2.2.0.1-1111", "[]");
+
+    RepositoryVersionDAO repositoryDAO = injector.getInstance(RepositoryVersionDAO.class);
+    repositoryDAO.create(rve);
+
+    ServiceComponentVersionEntity version = new ServiceComponentVersionEntity();
+    version.setState(RepositoryVersionState.CURRENT);
+    version.setRepositoryVersion(rve);
+    version.setUserName("user");
+    serviceComponentDesiredStateEntity.addVersion(version);
+
+    serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.merge(
+        serviceComponentDesiredStateEntity);
+
+    serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.findByName(
+        cluster.getClusterId(), serviceName, componentName);
+
+    assertEquals(1, serviceComponentDesiredStateEntity.getVersions().size());
+    ServiceComponentVersionEntity persistedVersion = serviceComponentDesiredStateEntity.getVersions().iterator().next();
+
+    assertEquals(RepositoryVersionState.CURRENT, persistedVersion.getState());
+  }
+
+  @Test
+  public void testVersionRemoval() throws Exception {
+    ServiceComponentDesiredStateDAO serviceComponentDesiredStateDAO = injector.getInstance(
+        ServiceComponentDesiredStateDAO.class);
+
+    String componentName = "NAMENODE";
+    ServiceComponent component = serviceComponentFactory.createNew(service, componentName);
+    service.addServiceComponent(component);
+    component.persist();
+
+    ServiceComponent sc = service.getServiceComponent(componentName);
+    Assert.assertNotNull(sc);
+
+    sc.setDesiredState(State.INSTALLED);
+    Assert.assertEquals(State.INSTALLED, sc.getDesiredState());
+
+    sc.setDesiredStackVersion(new StackId("HDP-2.2.0"));
+    StackId stackId = sc.getDesiredStackVersion();
+    Assert.assertEquals(new StackId("HDP", "2.2.0"), stackId);
+
+    Assert.assertEquals("HDP-2.2.0", sc.getDesiredStackVersion().getStackId());
+
+    ServiceComponentDesiredStateEntity serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.findByName(
+        cluster.getClusterId(), serviceName, componentName);
+
+    Assert.assertNotNull(serviceComponentDesiredStateEntity);
+
+    RepositoryVersionEntity rve = new RepositoryVersionEntity(
+        serviceComponentDesiredStateEntity.getDesiredStack(), "HDP-2.2.0", "2.2.0.1-1111", "[]");
+
+    RepositoryVersionDAO repositoryDAO = injector.getInstance(RepositoryVersionDAO.class);
+    repositoryDAO.create(rve);
+
+    ServiceComponentVersionEntity version = new ServiceComponentVersionEntity();
+    version.setState(RepositoryVersionState.CURRENT);
+    version.setRepositoryVersion(rve);
+    version.setUserName("user");
+    serviceComponentDesiredStateEntity.addVersion(version);
+
+    serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.merge(
+        serviceComponentDesiredStateEntity);
+
+    serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.findByName(
+        cluster.getClusterId(), serviceName, componentName);
+
+    assertEquals(1, serviceComponentDesiredStateEntity.getVersions().size());
+    ServiceComponentVersionEntity persistedVersion = serviceComponentDesiredStateEntity.getVersions().iterator().next();
+
+    assertEquals(RepositoryVersionState.CURRENT, persistedVersion.getState());
+
+    sc.delete();
+
+    serviceComponentDesiredStateEntity = serviceComponentDesiredStateDAO.findByName(
+        cluster.getClusterId(), serviceName, componentName);
+    Assert.assertNull(serviceComponentDesiredStateEntity);
+
+
+    // verify versions are gone, too
+    List<ServiceComponentVersionEntity> list = serviceComponentDesiredStateDAO.findVersions(cluster.getClusterId(), serviceName, componentName);
+    assertEquals(0, list.size());
+  }
+
   /**
    * Creates an upgrade entity, asserting it was created correctly.
    *
@@ -574,4 +693,5 @@ public class ServiceComponentTest {
     assertEquals(1, upgrades.size());
     return upgradeEntity;
   }
+
 }

+ 87 - 24
ambari-server/src/test/java/org/apache/ambari/server/upgrade/UpgradeCatalog250Test.java

@@ -18,13 +18,37 @@
 
 package org.apache.ambari.server.upgrade;
 
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createMockBuilder;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.createStrictMock;
+import static org.easymock.EasyMock.eq;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.newCapture;
+import static org.easymock.EasyMock.replay;
+import static org.easymock.EasyMock.reset;
+import static org.easymock.EasyMock.verify;
+import static org.junit.Assert.assertTrue;
+
+import java.lang.reflect.Method;
+import java.sql.Connection;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 import javax.persistence.EntityManager;
 
-import com.google.common.collect.Maps;
-import com.google.gson.Gson;
-import junit.framework.Assert;
 import org.apache.ambari.server.AmbariException;
 import org.apache.ambari.server.actionmanager.ActionManager;
+import org.apache.ambari.server.configuration.Configuration;
 import org.apache.ambari.server.controller.AmbariManagementController;
 import org.apache.ambari.server.controller.AmbariManagementControllerImpl;
 import org.apache.ambari.server.controller.KerberosHelper;
@@ -47,32 +71,15 @@ import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 
+import com.google.common.collect.Maps;
+import com.google.gson.Gson;
 import com.google.inject.Binder;
 import com.google.inject.Guice;
 import com.google.inject.Injector;
 import com.google.inject.Module;
 import com.google.inject.Provider;
 
-import java.lang.reflect.Method;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.anyString;
-import static org.easymock.EasyMock.createMockBuilder;
-import static org.easymock.EasyMock.capture;
-import static org.easymock.EasyMock.createNiceMock;
-import static org.easymock.EasyMock.createStrictMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.newCapture;
-import static org.easymock.EasyMock.replay;
-import static org.easymock.EasyMock.reset;
-import static org.easymock.EasyMock.verify;
-import static org.junit.Assert.assertTrue;
+import junit.framework.Assert;
 
 /**
  * {@link UpgradeCatalog250} unit tests.
@@ -99,10 +106,37 @@ public class UpgradeCatalog250Test {
   public void testExecuteDDLUpdates() throws Exception {
     final DBAccessor dbAccessor = createNiceMock(DBAccessor.class);
 
+    Configuration configuration = createNiceMock(Configuration.class);
+    Connection connection = createNiceMock(Connection.class);
+    Statement statement = createNiceMock(Statement.class);
+    ResultSet resultSet = createNiceMock(ResultSet.class);
+
+
+    // !!! setup capture for host_version
     dbAccessor.addUniqueConstraint("host_version", "UQ_host_repo", "repo_version_id", "host_id");
     expectLastCall().once();
 
-    replay(dbAccessor);
+    // !!! setup capture for servicecomponent_version
+    Capture<List<DBAccessor.DBColumnInfo>> capturedComponentVersionColumns = newCapture();
+
+    dbAccessor.createTable(eq(UpgradeCatalog250.COMPONENT_VERSION_TABLE), capture(capturedComponentVersionColumns),
+        eq((String[]) null));
+
+    dbAccessor.addPKConstraint(eq(UpgradeCatalog250.COMPONENT_VERSION_TABLE),
+        eq(UpgradeCatalog250.COMPONENT_VERSION_PK), eq("id"));
+    dbAccessor.addFKConstraint(eq(UpgradeCatalog250.COMPONENT_VERSION_TABLE),
+        eq(UpgradeCatalog250.COMPONENT_VERSION_FK_COMPONENT), eq("component_id"),
+        eq(UpgradeCatalog250.COMPONENT_TABLE), eq("id"), eq(false));
+    dbAccessor.addFKConstraint(eq(UpgradeCatalog250.COMPONENT_VERSION_TABLE),
+        eq(UpgradeCatalog250.COMPONENT_VERSION_FK_REPO_VERSION), eq("repo_version_id"),
+        eq("repo_version"), eq("repo_version_id"), eq(false));
+
+
+    expect(dbAccessor.getConnection()).andReturn(connection);
+    expect(connection.createStatement()).andReturn(statement);
+    expect(statement.executeQuery(anyObject(String.class))).andReturn(resultSet);
+
+    replay(dbAccessor, configuration, connection, statement, resultSet);
 
     Module module = new Module() {
       @Override
@@ -118,6 +152,35 @@ public class UpgradeCatalog250Test {
     upgradeCatalog250.executeDDLUpdates();
 
     verify(dbAccessor);
+
+    // !!! check the captured for host_version
+    // (no checks)
+
+    // !!! check the captured for servicecomponent_version
+    Map<String, DBAccessor.DBColumnInfo> expected = new HashMap<>();
+    expected.put("id", new DBAccessor.DBColumnInfo("id", Long.class, null, null, false));
+    expected.put("component_id", new DBAccessor.DBColumnInfo("component_id", Long.class, null, null, false));
+    expected.put("repo_version_id", new DBAccessor.DBColumnInfo("repo_version_id", Long.class, null, null, false));
+    expected.put("state", new DBAccessor.DBColumnInfo("state", String.class, 32, null, false));
+    expected.put("user_name", new DBAccessor.DBColumnInfo("user_name", String.class, 255, null, false));
+
+    List<DBAccessor.DBColumnInfo> captured = capturedComponentVersionColumns.getValue();
+    Assert.assertEquals(5, captured.size());
+
+    for (DBAccessor.DBColumnInfo column : captured) {
+      DBAccessor.DBColumnInfo expectedColumn = expected.remove(column.getName());
+
+      Assert.assertNotNull(expectedColumn);
+      Assert.assertEquals(expectedColumn.getDefaultValue(), column.getDefaultValue());
+      Assert.assertEquals(expectedColumn.getName(), column.getName());
+      Assert.assertEquals(expectedColumn.getLength(), column.getLength());
+      Assert.assertEquals(expectedColumn.getType(), column.getType());
+      Assert.assertEquals(expectedColumn.getClass(), column.getClass());
+    }
+
+    // did we get them all?
+    Assert.assertEquals(0, expected.size());
+
   }
 
   @Test