This is an automated email from the ASF dual-hosted git repository.

ncole pushed a commit to branch branch-feature-AMBARI-14714
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/branch-feature-AMBARI-14714 by 
this push:
     new 36a64f6  [Ambari 23318] API Addition for Upgrade Plan (#744)
36a64f6 is described below

commit 36a64f699c9fedc05b09ced1a22cc6e4e69d18e2
Author: ncole <nc...@hortonworks.com>
AuthorDate: Wed Mar 21 15:24:05 2018 -0400

    [Ambari 23318] API Addition for Upgrade Plan (#744)
    
    * [AMBARI-23318] API Addition for Upgrade Plan
    
    * API Addition for Upgrade Plan - update for review comments
---
 .../api/resources/ResourceInstanceFactoryImpl.java |   5 +
 .../ambari/server/api/services/ClusterService.java |  18 +-
 .../server/api/services/UpgradePlanService.java    | 114 +++++++
 .../controller/internal/DefaultProviderModule.java |   2 +
 .../internal/UpgradePlanResourceProvider.java      | 341 +++++++++++++++++++++
 .../ambari/server/controller/spi/Resource.java     |   2 +
 .../ambari/server/orm/dao/UpgradePlanDAO.java      |  31 ++
 .../orm/entities/UpgradePlanDetailEntity.java      | 118 +++++++
 .../server/orm/entities/UpgradePlanEntity.java     | 244 +++++++++++++++
 .../src/main/resources/Ambari-DDL-Derby-CREATE.sql |  30 +-
 .../src/main/resources/Ambari-DDL-MySQL-CREATE.sql |  25 ++
 .../main/resources/Ambari-DDL-Oracle-CREATE.sql    |  25 ++
 .../main/resources/Ambari-DDL-Postgres-CREATE.sql  |  26 +-
 .../resources/Ambari-DDL-SQLAnywhere-CREATE.sql    |  26 ++
 .../main/resources/Ambari-DDL-SQLServer-CREATE.sql |  26 +-
 .../src/main/resources/META-INF/persistence.xml    |   2 +
 .../internal/UpgradePlanResourceProviderTest.java  | 148 +++++++++
 17 files changed, 1178 insertions(+), 5 deletions(-)

diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
index 259ef70..d7e7fda 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/resources/ResourceInstanceFactoryImpl.java
@@ -453,6 +453,11 @@ public class ResourceInstanceFactoryImpl implements 
ResourceInstanceFactory {
             Resource.Type.UpgradeItem, "upgrade_item", "upgrade_items", 
Resource.Type.Task);
         break;
 
+      case UpgradePlan:
+        resourceDefinition = new SimpleResourceDefinition(
+            Resource.Type.UpgradePlan, "upgrade_plan", "upgrade_plans");
+        break;
+
       case UpgradeSummary:
         resourceDefinition = new SimpleResourceDefinition(
             Resource.Type.UpgradeSummary, "upgrade_summary", 
"upgrade_summary");
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
index 31af4e0..140f916 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/ClusterService.java
@@ -767,6 +767,22 @@ public class ClusterService extends BaseService {
   }
 
   /**
+   * Gets the services for upgrade plans.
+   *
+   * @param request the request
+   * @param clusterName the cluster name
+   *
+   * @return the upgrade plan services
+   */
+  @Path("{clusterName}/upgrade_plans")
+  public UpgradePlanService getUpgradePlanService(
+      @Context javax.ws.rs.core.Request request,
+      @PathParam("clusterName") String clusterName) {
+    return new UpgradePlanService(clusterName);
+  }
+
+
+  /**
    * Gets a list of upgrade summaries.
    *
    * @param request the request
@@ -780,7 +796,7 @@ public class ClusterService extends BaseService {
       @PathParam("clusterName") String clusterName) {
     return new UpgradeSummaryService(clusterName);
   }
-  
+
   /**
    * Gets the pre-upgrade checks service.
    *
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/api/services/UpgradePlanService.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/UpgradePlanService.java
new file mode 100644
index 0000000..256addd
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/api/services/UpgradePlanService.java
@@ -0,0 +1,114 @@
+/*
+ * 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.api.services;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.UriInfo;
+
+import org.apache.ambari.annotations.ApiIgnore;
+import org.apache.ambari.server.api.resources.ResourceInstance;
+import org.apache.ambari.server.controller.spi.Resource;
+
+/**
+ * Endpoint for cluster upgrade plans.
+ */
+public class UpgradePlanService extends BaseService {
+
+  private String m_clusterName = null;
+
+  /**
+   * Constructor.
+   *
+   * @param clusterName the cluster name (not {@code null}).
+   */
+  UpgradePlanService(String clusterName) {
+    m_clusterName = clusterName;
+  }
+
+  @POST @ApiIgnore // until documented
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response createPlan(String body,
+      @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, body, ui, Request.Type.POST,
+        createResourceInstance(null));
+  }
+
+  @GET @ApiIgnore // until documented
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response getPlans( @Context HttpHeaders headers,
+      @Context UriInfo ui) {
+    return handleRequest(headers, null, ui, Request.Type.GET,
+        createResourceInstance(null));
+  }
+
+  @GET @ApiIgnore // until documented
+  @Path("{upgradePlanId}")
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response getPlan(@Context HttpHeaders headers,
+      @Context UriInfo ui, @PathParam("upgradePlanId") Long id) {
+    return handleRequest(headers, null, ui, Request.Type.GET,
+        createResourceInstance(id));
+  }
+
+  @PUT @ApiIgnore // until documented
+  @Path("{upgradePlanId}")
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response updatePlan(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui, @PathParam("upgradePlanId") Long id) {
+    return handleRequest(headers, body, ui, Request.Type.PUT,
+        createResourceInstance(id));
+  }
+
+  @DELETE @ApiIgnore // until documented
+  @Path("{upgradePlanId}")
+  @Produces(MediaType.TEXT_PLAIN)
+  public Response deleteUpgradePlan(String body, @Context HttpHeaders headers,
+      @Context UriInfo ui, @PathParam("upgradePlanId") Long id) {
+    return handleRequest(headers, body, ui, Request.Type.DELETE,
+        createResourceInstance(id));
+  }
+
+  /**
+   * @param upgradePlanId the upgrade plan id
+   * @return the resource instance
+   */
+  private ResourceInstance createResourceInstance(Long upgradePlanId) {
+    Map<Resource.Type, String> mapIds = new HashMap<>();
+    mapIds.put(Resource.Type.Cluster, m_clusterName);
+
+    if (null != upgradePlanId) {
+      mapIds.put(Resource.Type.UpgradePlan, upgradePlanId.toString());
+    }
+
+    return createResource(Resource.Type.UpgradePlan, mapIds);
+  }
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
index ceeee92..8a4a385 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/DefaultProviderModule.java
@@ -114,6 +114,8 @@ public class DefaultProviderModule extends 
AbstractProviderModule {
         return new UpgradeGroupResourceProvider(managementController);
       case UpgradeItem:
         return new UpgradeItemResourceProvider(managementController);
+      case UpgradePlan:
+        return new UpgradePlanResourceProvider(managementController);
       case UpgradeSummary:
         return new UpgradeSummaryResourceProvider(managementController);
       case PreUpgradeCheck:
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradePlanResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradePlanResourceProvider.java
new file mode 100644
index 0000000..13117cb
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/UpgradePlanResourceProvider.java
@@ -0,0 +1,341 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.internal;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.ambari.server.AmbariException;
+import org.apache.ambari.server.StaticallyInject;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.NoSuchParentResourceException;
+import org.apache.ambari.server.controller.spi.NoSuchResourceException;
+import org.apache.ambari.server.controller.spi.Predicate;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.spi.ResourceAlreadyExistsException;
+import org.apache.ambari.server.controller.spi.SystemException;
+import org.apache.ambari.server.controller.spi.UnsupportedPropertyException;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.dao.UpgradePlanDAO;
+import org.apache.ambari.server.orm.entities.UpgradePlanDetailEntity;
+import org.apache.ambari.server.orm.entities.UpgradePlanEntity;
+import org.apache.ambari.server.security.authorization.RoleAuthorization;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.stack.upgrade.Direction;
+import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.commons.lang.BooleanUtils;
+import org.apache.commons.lang.StringUtils;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.gson.Gson;
+import com.google.gson.annotations.SerializedName;
+import com.google.gson.reflect.TypeToken;
+import com.google.inject.Inject;
+
+/**
+ * Manages upgrade plans.
+ */
+@StaticallyInject
+public class UpgradePlanResourceProvider extends 
AbstractControllerResourceProvider {
+
+  private static final String UPGRADE_PLAN = "UpgradePlan" + 
PropertyHelper.EXTERNAL_PATH_SEP;
+
+  protected static final String UPGRADE_PLAN_ID = UPGRADE_PLAN + "id";
+  protected static final String UPGRADE_PLAN_CLUSTER_NAME = UPGRADE_PLAN + 
"cluster_name";
+
+  public static final String UPGRADE_PLAN_TYPE                        = 
UPGRADE_PLAN + "upgrade_type";
+  public static final String UPGRADE_PLAN_DIRECTION                   = 
UPGRADE_PLAN + "direction";
+  public static final String UPGRADE_PLAN_SKIP_FAILURES               = 
UPGRADE_PLAN + "skip_failures";
+  public static final String UPGRADE_PLAN_SKIP_PREREQUISITE_CHECKS    = 
UPGRADE_PLAN + "skip_prerequisite_checks";
+  public static final String UPGRADE_PLAN_SKIP_SERVICE_CHECKS         = 
UPGRADE_PLAN + "skip_service_checks";
+  public static final String UPGRADE_PLAN_SKIP_SERVICE_CHECK_FAILURES = 
UPGRADE_PLAN + "skip_service_check_failures";
+  public static final String UPGRADE_PLAN_FAIL_ON_CHECK_WARNINGS      = 
UPGRADE_PLAN + "fail_on_check_warnings";
+
+  public static final String UPGRADE_PLAN_SERVICE_GROUPS              = 
UPGRADE_PLAN + "servicegroups";
+
+  private static final Map<Resource.Type, String> KEY_PROPERTY_IDS = 
ImmutableMap.<Resource.Type, String>builder()
+      .put(Resource.Type.UpgradePlan, UPGRADE_PLAN_ID)
+      .put(Resource.Type.Cluster, UPGRADE_PLAN_CLUSTER_NAME)
+      .build();
+
+  private static final Set<String> PK_PROPERTY_IDS = 
Sets.newHashSet(KEY_PROPERTY_IDS.values());
+
+  private static final Set<String> PROPERTY_IDS = Sets.newHashSet(
+      UPGRADE_PLAN_ID,
+      UPGRADE_PLAN_CLUSTER_NAME,
+      UPGRADE_PLAN_TYPE,
+      UPGRADE_PLAN_DIRECTION,
+      UPGRADE_PLAN_SKIP_FAILURES,
+      UPGRADE_PLAN_SKIP_PREREQUISITE_CHECKS,
+      UPGRADE_PLAN_SKIP_SERVICE_CHECKS,
+      UPGRADE_PLAN_SKIP_SERVICE_CHECK_FAILURES,
+      UPGRADE_PLAN_FAIL_ON_CHECK_WARNINGS,
+      UPGRADE_PLAN_SERVICE_GROUPS);
+
+  /**
+   * Used to deserialize the repository JSON into an object.
+   */
+  @Inject
+  private static Gson s_gson;
+
+  @Inject
+  private static UpgradePlanDAO s_upgradePlanDAO;
+
+  /**
+   * Constructor.
+   *
+   * @param controller the controller
+   */
+  UpgradePlanResourceProvider(AmbariManagementController controller) {
+    super(Resource.Type.UpgradePlan, PROPERTY_IDS, KEY_PROPERTY_IDS, 
controller);
+
+    
setRequiredCreateAuthorizations(EnumSet.of(RoleAuthorization.CLUSTER_UPGRADE_DOWNGRADE_STACK));
+  }
+
+  @Override
+  public RequestStatus createResourcesAuthorized(final Request request)
+      throws SystemException,
+      UnsupportedPropertyException, ResourceAlreadyExistsException,
+      NoSuchParentResourceException {
+
+    Set<Map<String, Object>> propertyMaps = request.getProperties();
+
+    if (propertyMaps.size() > 1) {
+      throw new IllegalArgumentException("Cannot create more than one Upgrade 
Plan at a time");
+    }
+
+    Map<String, Object> propertyMap = propertyMaps.iterator().next();
+
+    // !!! i hate you, framework
+    @SuppressWarnings("unchecked")
+    Set<Map<String, Object>> groupMaps = (Set<Map<String, Object>>) 
propertyMap.get(
+        UPGRADE_PLAN_SERVICE_GROUPS);
+    String json = s_gson.toJson(groupMaps);
+
+    java.lang.reflect.Type listType = new 
TypeToken<ArrayList<ServiceGroupJson>>(){}.getType();
+    List<ServiceGroupJson> l = s_gson.fromJson(json, listType);
+
+    UpgradePlanEntity entity = toEntity(propertyMap, l);
+
+    s_upgradePlanDAO.create(entity);
+
+    notifyCreate(Resource.Type.UpgradePlan, request);
+
+    Resource res = new ResourceImpl(Resource.Type.UpgradePlan);
+    res.setProperty(UPGRADE_PLAN_ID, entity.getId());
+
+    return new RequestStatusImpl(null, Collections.singleton(res));
+  }
+
+  @Override
+  public Set<Resource> getResourcesAuthorized(Request request, Predicate 
predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    Set<String> requestPropertyIds = getRequestPropertyIds(request, predicate);
+    List<UpgradePlanEntity> entities = new ArrayList<>();
+    Set<Resource> results = new LinkedHashSet<>();
+
+    for (Map<String, Object> propertyMap : getPropertyMaps(predicate)) {
+      String clusterName = (String) propertyMap.get(UPGRADE_PLAN_CLUSTER_NAME);
+      String upgradePlanIdStr = (String) propertyMap.get(UPGRADE_PLAN_ID);
+
+      if (StringUtils.isNotEmpty(upgradePlanIdStr)) {
+        Long upgradePlanId = Long.valueOf(upgradePlanIdStr);
+        UpgradePlanEntity entity = s_upgradePlanDAO.findByPK(upgradePlanId);
+
+        if (null == entity) {
+          throw new NoSuchResourceException(String.format("Could not find 
upgrade plan %s",
+              upgradePlanIdStr));
+        }
+
+        results.add(toResource(entity, requestPropertyIds, clusterName));
+      } else {
+        // find all plans
+        entities.addAll(s_upgradePlanDAO.findAll());
+
+        entities.forEach(entity -> {
+          results.add(toResource(entity, requestPropertyIds, clusterName));
+        });
+      }
+    }
+
+    return results;
+  }
+
+  @Override
+  public RequestStatus updateResourcesAuthorized(final Request request,
+      Predicate predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+
+    throw new SystemException("Upgrade plans cannot be modified");
+  }
+
+  @Override
+  public RequestStatus deleteResourcesAuthorized(Request request, Predicate 
predicate)
+      throws SystemException, UnsupportedPropertyException,
+      NoSuchResourceException, NoSuchParentResourceException {
+    throw new SystemException("Upgrade plans cannot be removed");
+  }
+
+  @Override
+  protected Set<String> getPKPropertyIds() {
+    return PK_PROPERTY_IDS;
+  }
+
+  /**
+   * Creates an entity from a request
+   * @param propertyMap
+   *            the property map containing values
+   * @param serviceGroupJsons
+   *            the collection of service groups parsed from the request
+   * @return the entity represeting the request
+   *
+   * @throws SystemException
+   */
+  private UpgradePlanEntity toEntity(Map<String, Object> propertyMap, 
Collection<ServiceGroupJson> serviceGroupJsons)
+    throws SystemException {
+    Arrays.asList(UPGRADE_PLAN_CLUSTER_NAME, UPGRADE_PLAN_TYPE, 
UPGRADE_PLAN_DIRECTION).stream()
+      .forEach(property -> {
+        if (!propertyMap.containsKey(property)) {
+          throw new IllegalArgumentException(
+              String.format("Property %s is required for an upgrade plan", 
property));
+        }
+      });
+
+    if (CollectionUtils.isEmpty(serviceGroupJsons)) {
+      throw new IllegalArgumentException("Service groups must be provided");
+    }
+
+    String clusterName = propertyMap.get(UPGRADE_PLAN_CLUSTER_NAME).toString();
+    Cluster cluster = null;
+    try {
+      cluster = 
getManagementController().getClusters().getCluster(clusterName);
+    } catch (AmbariException e) {
+      throw new SystemException(String.format("Could not load cluster %s", 
clusterName), e);
+    }
+
+    String upgradeTypeName = propertyMap.get(UPGRADE_PLAN_TYPE).toString();
+    UpgradeType upgradeType = UpgradeType.valueOf(upgradeTypeName);
+
+    String directionName = propertyMap.get(UPGRADE_PLAN_DIRECTION).toString();
+    Direction direction = Direction.valueOf(directionName);
+
+    UpgradePlanEntity entity = new UpgradePlanEntity();
+
+    entity.setClusterId(cluster.getClusterId());
+    entity.setUpgradeType(upgradeType);
+    entity.setDirection(direction);
+
+    if (propertyMap.containsKey(UPGRADE_PLAN_FAIL_ON_CHECK_WARNINGS)) {
+      boolean failOn = 
BooleanUtils.toBoolean(propertyMap.get(UPGRADE_PLAN_FAIL_ON_CHECK_WARNINGS).toString());
+      entity.setFailOnPrerequisiteWarnings(failOn);
+    }
+
+    if (propertyMap.containsKey(UPGRADE_PLAN_SKIP_FAILURES)) {
+      boolean skip = 
BooleanUtils.toBoolean(propertyMap.get(UPGRADE_PLAN_SKIP_FAILURES).toString());
+      entity.setSkipFailures(skip);
+    }
+
+    if (propertyMap.containsKey(UPGRADE_PLAN_SKIP_PREREQUISITE_CHECKS)) {
+      boolean skip = 
BooleanUtils.toBoolean(propertyMap.get(UPGRADE_PLAN_SKIP_PREREQUISITE_CHECKS).toString());
+      entity.setSkipPrerequisiteChecks(skip);
+    }
+
+    if (propertyMap.containsKey(UPGRADE_PLAN_SKIP_SERVICE_CHECK_FAILURES)) {
+      boolean skip = 
BooleanUtils.toBoolean(propertyMap.get(UPGRADE_PLAN_SKIP_SERVICE_CHECK_FAILURES).toString());
+      entity.setSkipPrerequisiteChecks(skip);
+    }
+
+    if (propertyMap.containsKey(UPGRADE_PLAN_SKIP_SERVICE_CHECKS)) {
+      boolean skip = 
BooleanUtils.toBoolean(propertyMap.get(UPGRADE_PLAN_SKIP_SERVICE_CHECKS).toString());
+      entity.setSkipServiceChecks(skip);
+    }
+
+    List<UpgradePlanDetailEntity> details = new ArrayList<>();
+
+    serviceGroupJsons.forEach(serviceGroupJson -> {
+      if (null == serviceGroupJson.mpackTargetId || null == 
serviceGroupJson.serviceGroupId) {
+        return;
+      }
+
+      UpgradePlanDetailEntity detail = new UpgradePlanDetailEntity();
+      detail.setMpackTargetId(serviceGroupJson.mpackTargetId);
+      detail.setServiceGroupId(serviceGroupJson.serviceGroupId);
+
+      details.add(detail);
+    });
+
+    entity.setDetails(details);
+
+    return entity;
+  }
+
+  /**
+   * Converts an entity to a resource
+   * @param upgradePlan
+   *            the upgrade plan entity
+   * @param requestedIds
+   *            the requested ids
+   * @param clusterName
+   *            the cluster name
+   * @return the resource
+   */
+  private Resource toResource(UpgradePlanEntity upgradePlan, Set<String> 
requestedIds,
+      String clusterName) {
+    ResourceImpl resource = new ResourceImpl(Resource.Type.UpgradePlan);
+
+    setResourceProperty(resource, UPGRADE_PLAN_CLUSTER_NAME, clusterName, 
requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_ID, upgradePlan.getId(), 
requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_DIRECTION, 
upgradePlan.getDirection(), requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_FAIL_ON_CHECK_WARNINGS, 
upgradePlan.isFailOnPrerequisiteWarnings(), requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_SKIP_FAILURES, 
upgradePlan.isSkipFailures(), requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_SKIP_PREREQUISITE_CHECKS, 
upgradePlan.isSkipPrerequisiteChecks(), requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_SKIP_SERVICE_CHECK_FAILURES, 
upgradePlan.isSkipServiceCheckFailures(), requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_SKIP_SERVICE_CHECKS, 
upgradePlan.isSkipServiceChecks(), requestedIds);
+    setResourceProperty(resource, UPGRADE_PLAN_SERVICE_GROUPS, 
upgradePlan.getDetails(), requestedIds);
+
+    return resource;
+  }
+
+
+  /**
+   * An object representing the service groups in a request.
+   */
+  private static class ServiceGroupJson {
+    @SerializedName("service_group_id")
+    private Long serviceGroupId;
+
+    @SerializedName("mpack_target_id")
+    private Long mpackTargetId;
+
+  }
+
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
index 9077f35..57f7e28 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/spi/Resource.java
@@ -159,6 +159,7 @@ public interface Resource {
     Upgrade,
     UpgradeGroup,
     UpgradeItem,
+    UpgradePlan,
     UpgradeSummary,
     PreUpgradeCheck,
     Stage,
@@ -297,6 +298,7 @@ public interface Resource {
     public static final Type Upgrade = InternalType.Upgrade.getType();
     public static final Type UpgradeGroup = 
InternalType.UpgradeGroup.getType();
     public static final Type UpgradeItem = InternalType.UpgradeItem.getType();
+    public static final Type UpgradePlan = InternalType.UpgradePlan.getType();
     public static final Type UpgradeSummary = 
InternalType.UpgradeSummary.getType();
     public static final Type PreUpgradeCheck = 
InternalType.PreUpgradeCheck.getType();
     public static final Type Stage = InternalType.Stage.getType();
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradePlanDAO.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradePlanDAO.java
new file mode 100644
index 0000000..68f662e
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/dao/UpgradePlanDAO.java
@@ -0,0 +1,31 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.orm.dao;
+
+import org.apache.ambari.server.orm.entities.UpgradePlanEntity;
+
+/**
+ * DAO used to manage upgrade plans
+ */
+public class UpgradePlanDAO extends CrudDAO<UpgradePlanEntity, Long> {
+
+  public UpgradePlanDAO() {
+    super(UpgradePlanEntity.class);
+  }
+
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradePlanDetailEntity.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradePlanDetailEntity.java
new file mode 100644
index 0000000..7b7c501
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradePlanDetailEntity.java
@@ -0,0 +1,118 @@
+/*
+ * 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 javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+import com.google.common.base.Objects;
+
+/**
+ * Models a single upgrade plan that can be used to invoke an Upgrade.
+ */
+@Table(name = "upgrade_plan_detail")
+@Entity
+@TableGenerator(name = "upgrade_plan_detail_id_generator",
+    table = "ambari_sequences", pkColumnName = "sequence_name", 
valueColumnName = "sequence_value",
+    pkColumnValue = "upgrade_plan_detail_id_seq",
+    initialValue = 0)
+public class UpgradePlanDetailEntity {
+
+  @Id
+  @Column(name = "id", nullable = false, insertable = true, updatable = false)
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = 
"upgrade_plan_detail_id_generator")
+  private Long id;
+
+  @Column(name = "upgrade_plan_id", nullable = false, insertable = false, 
updatable = false)
+  private Long upgradePlanId;
+
+  @Column(name = "service_group_id", nullable = false, insertable = true, 
updatable = true)
+  private Long serviceGroupId;
+
+  @Column(name = "mpack_target_id", nullable = false, insertable = true, 
updatable = true)
+  private Long mpackTargetId;
+
+  @ManyToOne
+  @JoinColumn(name = "upgrade_plan_id", referencedColumnName = "id", nullable 
= false)
+  private UpgradePlanEntity upgradePlanEntity;
+
+  /**
+   * @return the id
+   */
+  public Long getId() {
+    return id;
+  }
+
+  /**
+   * @param planId the id
+   */
+  public void setId(Long planId) {
+    id = planId;
+  }
+
+  /**
+   * @return the id of the service group to be upgraded
+   */
+  public long getServiceGroupId() {
+    return serviceGroupId;
+  }
+
+  /**
+   * @param id the service group id to upgrade
+   */
+  public void setServiceGroupId(long id) {
+    serviceGroupId = id;
+  }
+
+  /**
+   * @return the target mpack id
+   */
+  public long getMpackTargetId() {
+    return mpackTargetId;
+  }
+
+  /**
+   * @param id the target mpack id
+   */
+  public void setMpackTargetId(long id) {
+    mpackTargetId = id;
+  }
+
+  /**
+   * @param plan  the owning upgrade plan
+   */
+  void setUpgradePlanEntity(UpgradePlanEntity plan) {
+    upgradePlanEntity = plan;
+  }
+
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this).omitNullValues()
+        .add("id", id)
+        .toString();
+  }
+
+}
diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradePlanEntity.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradePlanEntity.java
new file mode 100644
index 0000000..403b536
--- /dev/null
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/orm/entities/UpgradePlanEntity.java
@@ -0,0 +1,244 @@
+/*
+ * 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.List;
+
+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.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.TableGenerator;
+
+import org.apache.ambari.server.state.stack.upgrade.Direction;
+import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
+import org.apache.commons.lang.ObjectUtils;
+
+import com.google.common.base.Objects;
+
+/**
+ * Models a single upgrade plan that can be used to invoke an Upgrade.
+ */
+@Table(name = "upgrade_plan")
+@Entity
+@TableGenerator(name = "upgrade_plan_id_generator",
+    table = "ambari_sequences", pkColumnName = "sequence_name", 
valueColumnName = "sequence_value",
+    pkColumnValue = "upgrade_plan_id_seq",
+    initialValue = 0)
+public class UpgradePlanEntity {
+
+  @Id
+  @Column(name = "id", nullable = false, insertable = true, updatable = false)
+  @GeneratedValue(strategy = GenerationType.TABLE, generator = 
"upgrade_plan_id_generator")
+  private Long id;
+
+  @Column(name = "cluster_id", nullable = false, insertable = true, updatable 
= false)
+  private Long clusterId;
+
+  @Column(name = "upgrade_type", nullable = false)
+  @Enumerated(value = EnumType.STRING)
+  private UpgradeType upgradeType = UpgradeType.ROLLING;
+
+  @Column(name = "direction", nullable=false)
+  @Enumerated(value = EnumType.STRING)
+  private Direction direction = Direction.UPGRADE;
+
+  @Column(name = "skip_failures", nullable = false)
+  private short skipFailures = (short) 0;
+
+  @Column(name = "skip_sc_failures", nullable = false)
+  private short skipServiceCheckFailures = (short) 0;
+
+  @Column(name = "skip_prechecks", nullable = false)
+  private short skipPrerequisiteChecks = (short) 0;
+
+  @Column(name = "fail_on_precheck_warnings", nullable = false)
+  private short failOnPrerequisiteWarnings = (short) 0;
+
+  @Column(name = "skip_service_checks", nullable = false)
+  private short skipServiceChecks = (short) 0;
+
+  @OneToMany(mappedBy = "upgradePlanEntity", cascade = { CascadeType.ALL })
+  private List<UpgradePlanDetailEntity> upgradePlanDetails;
+
+  /**
+   * @return the id
+   */
+  public Long getId() {
+    return id;
+  }
+
+  /**
+   * @param planId the id
+   */
+  public void setId(Long planId) {
+    id = planId;
+  }
+
+  /**
+   * @return the cluster id
+   */
+  public long getClusterId() {
+    return clusterId;
+  }
+
+  /**
+   * @param id the cluster id
+   */
+  public void setClusterId(long id) {
+    clusterId = id;
+  }
+
+  /**
+   * @return the direction
+   */
+  public Direction getDirection() {
+    return direction;
+  }
+
+  /**
+   * @param direction the direction
+   */
+  public void setDirection(Direction direction) {
+    this.direction = direction;
+  }
+
+  /**
+   * @return the upgrade type
+   */
+  public UpgradeType getUpgradeType() {
+    return upgradeType;
+  }
+
+  /**
+   * @param type the upgrade type
+   */
+  public void setUpgradeType(UpgradeType type) {
+    upgradeType = type;
+  }
+
+  /**
+   * @return if failures should be skipped
+   */
+  public boolean isSkipFailures() {
+    return skipFailures != 0;
+  }
+
+  /**
+   * @param skip {@code true} to skip failures that are not service check based
+   */
+  public void setSkipFailures(boolean skip) {
+    skipFailures = skip ? (short) 1 : (short) 0;
+  }
+
+  /**
+   * @return if service check failures can be auto-skipped
+   */
+  public boolean isSkipServiceCheckFailures() {
+    return skipServiceCheckFailures != 0;
+  }
+
+  /**
+   * @param skip {@code true} to skip service check failures
+   */
+  public void setSkipServiceCheckFailures(boolean skip) {
+    skipServiceCheckFailures = skip ? (short) 1 : (short) 0;
+  }
+
+  /**
+   * @return if prerequiste checks are skipped
+   */
+  public boolean isSkipPrerequisiteChecks() {
+    return skipPrerequisiteChecks != 0;
+  }
+
+  /**
+   * @param skip {@code true} to skip prerequiste checks
+   */
+  public void setSkipPrerequisiteChecks(boolean skip) {
+    skipPrerequisiteChecks = skip ? (short) 1 : (short) 0;
+  }
+
+  /**
+   * @return if precheck warnings should result in a failure
+   */
+  public boolean isFailOnPrerequisiteWarnings() {
+    return failOnPrerequisiteWarnings != 0;
+  }
+
+  /**
+   * @param fail {@code true} to treat prerequisite warnings as failures
+   */
+  public void setFailOnPrerequisiteWarnings(boolean fail) {
+    failOnPrerequisiteWarnings = fail ? (short) 1 : (short) 0;
+  }
+
+  /**
+   * @return if service checks should be skipped
+   */
+  public boolean isSkipServiceChecks() {
+    return skipServiceChecks != 0;
+  }
+
+  /**
+   * @param skip {@code true} to skip service checks
+   */
+  public void setSkipServiceChecks(boolean skip) {
+    skipServiceChecks = skip ? (short) 1 : (short) 0;
+  }
+
+  /**
+   * @return the plan details
+   */
+  public List<UpgradePlanDetailEntity> getDetails() {
+    return upgradePlanDetails;
+  }
+
+  /**
+   * @param details the plan details
+   */
+  public void setDetails(List<UpgradePlanDetailEntity> details) {
+    for (UpgradePlanDetailEntity entity : details) {
+      entity.setUpgradePlanEntity(this);
+    }
+
+    upgradePlanDetails = details;
+  }
+
+
+  @Override
+  public int hashCode() {
+    return ObjectUtils.hashCode(id);
+  }
+
+  @Override
+  public String toString() {
+    return Objects.toStringHelper(this).omitNullValues()
+        .add("id", id)
+        .add("upgrade_type", upgradeType)
+        .add("direction", direction)
+        .toString();
+  }
+
+}
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql 
b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
index d7f4e00..377b14b 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Derby-CREATE.sql
@@ -1046,6 +1046,28 @@ CREATE TABLE upgrade_history(
   CONSTRAINT UQ_upgrade_hist_srvc_grp UNIQUE (upgrade_id, service_group_id)
 );
 
+CREATE TABLE upgrade_plan(
+  id BIGINT NOT NULL,
+  cluster_id BIGINT NOT NULL,
+  upgrade_type VARCHAR(255) DEFAULT 'ROLLING' NOT NULL,
+  direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  skip_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_sc_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_prechecks SMALLINT DEFAULT 0 NOT NULL,
+  fail_on_precheck_warnings SMALLINT DEFAULT 0 NOT NULL,
+  skip_service_checks SMALLINT DEFAULT 0 NOT NULL,
+  CONSTRAINT PK_upgrade_plan PRIMARY KEY (id)
+);
+
+CREATE TABLE upgrade_plan_detail (
+  id BIGINT NOT NULL,
+  upgrade_plan_id BIGINT NOT NULL,
+  service_group_id BIGINT NOT NULL,
+  mpack_target_id BIGINT NOT NULL,
+  CONSTRAINT PK_upgrade_plan_detail PRIMARY KEY (id),
+  CONSTRAINT FK_upgrade_det_upgrade_plan FOREIGN KEY (upgrade_plan_id) 
REFERENCES upgrade_plan (id)
+);
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1319,12 +1341,16 @@ INSERT INTO ambari_sequences (sequence_name, 
sequence_value)
   union all
   select 'upgrade_group_id_seq', 0 FROM SYSIBM.SYSDUMMY1
   union all
+  select 'upgrade_item_id_seq', 0 FROM SYSIBM.SYSDUMMY1
+  union all
+  select 'upgrade_plan_id_seq', 0 FROM SYSIBM.SYSDUMMY1
+  union all
+  select 'upgrade_plan_detail_id_seq', 0 FROM SYSIBM.SYSDUMMY1
+  union all
   select 'widget_id_seq', 0 FROM SYSIBM.SYSDUMMY1
   union all
   select 'widget_layout_id_seq', 0 FROM SYSIBM.SYSDUMMY1
   union all
-  select 'upgrade_item_id_seq', 0 FROM SYSIBM.SYSDUMMY1
-  union all
   select 'stack_id_seq', 0 FROM SYSIBM.SYSDUMMY1
   union all
   select 'mpack_id_seq', 0 FROM SYSIBM.SYSDUMMY1
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 78d424d..b298ded 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-MySQL-CREATE.sql
@@ -1063,6 +1063,29 @@ CREATE TABLE upgrade_history(
   CONSTRAINT UQ_upgrade_hist_srvc_grp UNIQUE (upgrade_id, service_group_id)
 );
 
+CREATE TABLE upgrade_plan (
+  id BIGINT NOT NULL,
+  cluster_id BIGINT NOT NULL,
+  upgrade_type VARCHAR(255) DEFAULT 'ROLLING' NOT NULL,
+  direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  skip_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_sc_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_prechecks SMALLINT DEFAULT 0 NOT NULL,
+  fail_on_precheck_warnings SMALLINT DEFAULT 0 NOT NULL,
+  skip_service_checks SMALLINT DEFAULT 0 NOT NULL,
+  CONSTRAINT PK_upgrade_plan PRIMARY KEY (id)
+);
+
+CREATE TABLE upgrade_plan_detail (
+  id BIGINT NOT NULL,
+  upgrade_plan_id BIGINT NOT NULL,
+  service_group_id BIGINT NOT NULL,
+  mpack_target_id BIGINT NOT NULL,
+  CONSTRAINT PK_upgrade_plan_detail PRIMARY KEY (id),
+  CONSTRAINT FK_upgrade_det_upgrade_plan FOREIGN KEY (upgrade_plan_id) 
REFERENCES upgrade_plan (id)
+);
+
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1295,6 +1318,8 @@ INSERT INTO ambari_sequences(sequence_name, 
sequence_value) VALUES
   ('upgrade_id_seq', 0),
   ('upgrade_group_id_seq', 0),
   ('upgrade_item_id_seq', 0),
+  ('upgrade_plan_id_seq', 0),
+  ('upgrade_plan_detail_id_seq', 0),
   ('stack_id_seq', 0),
   ('mpack_id_seq', 0),
   ('registry_id_seq', 0),
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql 
b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
index 9e6fb9e..ca1f8cb 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Oracle-CREATE.sql
@@ -1041,6 +1041,29 @@ CREATE TABLE upgrade_history(
   CONSTRAINT UQ_upgrade_hist_srvc_grp UNIQUE (upgrade_id, service_group_id)
 );
 
+CREATE TABLE upgrade_plan (
+  id NUMBER(19) NOT NULL,
+  cluster_id NUMBER(19) NOT NULL,
+  upgrade_type VARCHAR2(255) DEFAULT 'ROLLING' NOT NULL,
+  direction VARCHAR2(255) DEFAULT 'UPGRADE' NOT NULL,
+  skip_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_sc_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_prechecks SMALLINT DEFAULT 0 NOT NULL,
+  fail_on_precheck_warnings SMALLINT DEFAULT 0 NOT NULL,
+  skip_service_checks SMALLINT DEFAULT 0 NOT NULL,
+  CONSTRAINT PK_upgrade_plan PRIMARY KEY (id)
+);
+
+CREATE TABLE upgrade_plan_detail (
+  id NUMBER(19) NOT NULL,
+  upgrade_plan_id NUMBER(19) NOT NULL,
+  service_group_id NUMBER(19) NOT NULL,
+  mpack_target_id NUMBER(19) NOT NULL,
+  CONSTRAINT PK_upgrade_plan_detail PRIMARY KEY (id),
+  CONSTRAINT FK_upgrade_det_upgrade_plan FOREIGN KEY (upgrade_plan_id) 
REFERENCES upgrade_plan (id)
+);
+
+
 CREATE TABLE ambari_operation_history(
   id NUMBER(19) NOT NULL,
   from_version VARCHAR2(255) NOT NULL,
@@ -1273,6 +1296,8 @@ INSERT INTO ambari_sequences(sequence_name, 
sequence_value) values ('repo_defini
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_group_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_item_id_seq', 0);
+INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_plan_id_seq', 0);
+INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_plan_detail_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('stack_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('mpack_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('registry_id_seq', 0);
diff --git a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql 
b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
index 3ac3e56..c42f9e9 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-Postgres-CREATE.sql
@@ -1046,6 +1046,28 @@ CREATE TABLE upgrade_history(
   CONSTRAINT UQ_upgrade_hist_srvc_grp UNIQUE (upgrade_id, service_group_id)
 );
 
+CREATE TABLE upgrade_plan (
+  id BIGINT NOT NULL,
+  cluster_id BIGINT NOT NULL,
+  upgrade_type VARCHAR(255) DEFAULT 'ROLLING' NOT NULL,
+  direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  skip_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_sc_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_prechecks SMALLINT DEFAULT 0 NOT NULL,
+  fail_on_precheck_warnings SMALLINT DEFAULT 0 NOT NULL,
+  skip_service_checks SMALLINT DEFAULT 0 NOT NULL,
+  CONSTRAINT PK_upgrade_plan PRIMARY KEY (id)
+);
+
+CREATE TABLE upgrade_plan_detail (
+  id BIGINT NOT NULL,
+  upgrade_plan_id BIGINT NOT NULL,
+  service_group_id BIGINT NOT NULL,
+  mpack_target_id BIGINT NOT NULL,
+  CONSTRAINT PK_upgrade_plan_detail PRIMARY KEY (id),
+  CONSTRAINT FK_upgrade_det_upgrade_plan FOREIGN KEY (upgrade_plan_id) 
REFERENCES upgrade_plan (id)
+);
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1274,9 +1296,11 @@ INSERT INTO ambari_sequences (sequence_name, 
sequence_value) VALUES
   ('service_config_id_seq', 1),
   ('upgrade_id_seq', 0),
   ('upgrade_group_id_seq', 0),
+  ('upgrade_item_id_seq', 0),
+  ('upgrade_plan_id_seq', 0),
+  ('upgrade_plan_detail_id_seq', 0),
   ('widget_id_seq', 0),
   ('widget_layout_id_seq', 0),
-  ('upgrade_item_id_seq', 0),
   ('stack_id_seq', 0),
   ('mpack_id_seq',0),
   ('registry_id_seq',0),
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql 
b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
index 805aa1a..c8aca19 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLAnywhere-CREATE.sql
@@ -1041,6 +1041,30 @@ CREATE TABLE upgrade_history(
   CONSTRAINT UQ_upgrade_hist_srvc_grp UNIQUE (upgrade_id, service_group_id)
 );
 
+
+CREATE TABLE upgrade_plan (
+  id NUMERIC(19) NOT NULL,
+  cluster_id NUMERIC(19) NOT NULL,
+  upgrade_type VARCHAR(255) NOT NULL DEFAULT 'ROLLING',
+  direction VARCHAR(255) NOT NULL DEFAULT 'UPGRADE',
+  skip_failures SMALLINT NOT NULL DEFAULT 0,
+  skip_sc_failures SMALLINT NOT NULL DEFAULT 0,
+  skip_prechecks SMALLINT NOT NULL DEFAULT 0,
+  fail_on_precheck_warnings SMALLINT NOT NULL DEFAULT 0,
+  skip_service_checks SMALLINT NOT NULL DEFAULT 0,
+  CONSTRAINT PK_upgrade_plan PRIMARY KEY (id)
+);
+
+CREATE TABLE upgrade_plan_detail (
+  id NUMERIC(19) NOT NULL,
+  upgrade_plan_id NUMERIC(19) NOT NULL,
+  service_group_id NUMERIC(19) NOT NULL,
+  mpack_target_id NUMERIC(19) NOT NULL,
+  CONSTRAINT PK_upgrade_plan_detail PRIMARY KEY (id),
+  CONSTRAINT FK_upgrade_det_upgrade_plan FOREIGN KEY (upgrade_plan_id) 
REFERENCES upgrade_plan (id)
+);
+
+
 CREATE TABLE ambari_operation_history(
   id NUMERIC(19) NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1273,6 +1297,8 @@ INSERT INTO ambari_sequences(sequence_name, 
sequence_value) values ('repo_defini
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_group_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_item_id_seq', 0);
+INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_plan_id_seq', 0);
+INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('upgrade_plan_detail_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('stack_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('mpack_id_seq', 0);
 INSERT INTO ambari_sequences(sequence_name, sequence_value) values 
('registry_id_seq', 0);
diff --git a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql 
b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
index 43ec9dc..103a2b7 100644
--- a/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
+++ b/ambari-server/src/main/resources/Ambari-DDL-SQLServer-CREATE.sql
@@ -1064,6 +1064,28 @@ CREATE TABLE upgrade_history(
   CONSTRAINT UQ_upgrade_hist_srvc_grp UNIQUE (upgrade_id, service_group_id)
 );
 
+CREATE TABLE upgrade_plan (
+  id BIGINT NOT NULL,
+  cluster_id BIGINT NOT NULL,
+  upgrade_type VARCHAR(255) DEFAULT 'ROLLING' NOT NULL,
+  direction VARCHAR(255) DEFAULT 'UPGRADE' NOT NULL,
+  skip_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_sc_failures SMALLINT DEFAULT 0 NOT NULL, 
+  skip_prechecks SMALLINT DEFAULT 0 NOT NULL,
+  fail_on_precheck_warnings SMALLINT DEFAULT 0 NOT NULL,
+  skip_service_checks SMALLINT DEFAULT 0 NOT NULL,
+  CONSTRAINT PK_upgrade_plan PRIMARY KEY (id)
+);
+
+CREATE TABLE upgrade_plan_detail (
+  id BIGINT NOT NULL,
+  upgrade_plan_id BIGINT NOT NULL,
+  service_group_id BIGINT NOT NULL,
+  mpack_target_id BIGINT NOT NULL,
+  CONSTRAINT PK_upgrade_plan_detail PRIMARY KEY (id),
+  CONSTRAINT FK_upgrade_det_upgrade_plan FOREIGN KEY (upgrade_plan_id) 
REFERENCES upgrade_plan (id)
+);
+
 CREATE TABLE ambari_operation_history(
   id BIGINT NOT NULL,
   from_version VARCHAR(255) NOT NULL,
@@ -1301,9 +1323,11 @@ BEGIN TRANSACTION
     ('service_config_id_seq', 1),
     ('upgrade_id_seq', 0),
     ('upgrade_group_id_seq', 0),
+    ('upgrade_item_id_seq', 0),
+    ('upgrade_plan_id_seq', 0),
+    ('upgrade_plan_detail_id_seq', 0),
     ('widget_id_seq', 0),
     ('widget_layout_id_seq', 0),
-    ('upgrade_item_id_seq', 0),
     ('stack_id_seq', 0),
     ('mpack_id_seq', 0),
     ('registry_id_seq', 0),
diff --git a/ambari-server/src/main/resources/META-INF/persistence.xml 
b/ambari-server/src/main/resources/META-INF/persistence.xml
index 331fd0f..19efd06 100644
--- a/ambari-server/src/main/resources/META-INF/persistence.xml
+++ b/ambari-server/src/main/resources/META-INF/persistence.xml
@@ -87,6 +87,8 @@
     <class>org.apache.ambari.server.orm.entities.UpgradeGroupEntity</class>
     <class>org.apache.ambari.server.orm.entities.UpgradeItemEntity</class>
     <class>org.apache.ambari.server.orm.entities.UpgradeHistoryEntity</class>
+    <class>org.apache.ambari.server.orm.entities.UpgradePlanEntity</class>
+    
<class>org.apache.ambari.server.orm.entities.UpgradePlanDetailEntity</class>
     <class>org.apache.ambari.server.orm.entities.UserEntity</class>
     
<class>org.apache.ambari.server.orm.entities.UserAuthenticationEntity</class>
     <class>org.apache.ambari.server.orm.entities.WidgetEntity</class>
diff --git 
a/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradePlanResourceProviderTest.java
 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradePlanResourceProviderTest.java
new file mode 100644
index 0000000..2063642
--- /dev/null
+++ 
b/ambari-server/src/test/java/org/apache/ambari/server/controller/internal/UpgradePlanResourceProviderTest.java
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.ambari.server.controller.internal;
+
+import static org.easymock.EasyMock.anyString;
+import static org.easymock.EasyMock.createNiceMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Collections;
+import java.util.Map;
+
+import org.apache.ambari.server.H2DatabaseCleaner;
+import org.apache.ambari.server.controller.AmbariManagementController;
+import org.apache.ambari.server.controller.spi.Request;
+import org.apache.ambari.server.controller.spi.RequestStatus;
+import org.apache.ambari.server.controller.spi.Resource;
+import org.apache.ambari.server.controller.utilities.PropertyHelper;
+import org.apache.ambari.server.orm.GuiceJpaInitializer;
+import org.apache.ambari.server.orm.InMemoryDefaultTestModule;
+import org.apache.ambari.server.orm.dao.UpgradePlanDAO;
+import org.apache.ambari.server.orm.entities.UpgradePlanDetailEntity;
+import org.apache.ambari.server.orm.entities.UpgradePlanEntity;
+import org.apache.ambari.server.state.Cluster;
+import org.apache.ambari.server.state.Clusters;
+import org.apache.ambari.server.state.stack.upgrade.Direction;
+import org.apache.ambari.server.state.stack.upgrade.UpgradeType;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Sets;
+import com.google.inject.Binder;
+import com.google.inject.Guice;
+import com.google.inject.Injector;
+import com.google.inject.Module;
+import com.google.inject.util.Modules;
+
+/**
+ * UpgradePlanResourceProvider tests.
+ */
+public class UpgradePlanResourceProviderTest {
+
+  private Injector injector;
+  private AmbariManagementController amc;
+  private UpgradePlanDAO planDAO;
+
+  @Before
+  public void before() throws Exception {
+    Cluster cluster = createNiceMock(Cluster.class);
+    expect(cluster.getClusterName()).andReturn("c1").atLeastOnce();
+    expect(cluster.getClusterId()).andReturn(1L).anyTimes();
+
+    Clusters clusters = createNiceMock(Clusters.class);
+    expect(clusters.getCluster(anyString())).andReturn(cluster).atLeastOnce();
+
+    amc = createNiceMock(AmbariManagementController.class);
+
+    expect(amc.getClusters()).andReturn(clusters).atLeastOnce();
+
+    replay(cluster, clusters, amc);
+
+    // Create an injector which will inject the mocks
+    injector = Guice.createInjector(Modules.override(new 
InMemoryDefaultTestModule()).with(new MockModule()));
+    injector.getInstance(GuiceJpaInitializer.class);
+
+    planDAO = injector.getInstance(UpgradePlanDAO.class);
+  }
+
+  @After
+  public void after() throws Exception {
+    H2DatabaseCleaner.clearDatabaseAndStopPersistenceService(injector);
+    injector = null;
+  }
+
+  @Test
+  public void testCreateResources() throws Exception {
+
+    UpgradePlanResourceProvider provider = createProvider(amc);
+
+    Map<String, Object> serviceGroups = ImmutableMap.<String, Object>builder()
+        .put("service_group_id", 4L)
+        .put("mpack_target_id", 2L)
+        .build();
+
+    Map<String, Object> requestMap = ImmutableMap.<String, Object>builder()
+        .put(UpgradePlanResourceProvider.UPGRADE_PLAN_CLUSTER_NAME, "c1")
+        .put(UpgradePlanResourceProvider.UPGRADE_PLAN_TYPE, 
UpgradeType.ROLLING.name())
+        .put(UpgradePlanResourceProvider.UPGRADE_PLAN_DIRECTION, 
Direction.UPGRADE.name())
+        .put(UpgradePlanResourceProvider.UPGRADE_PLAN_SERVICE_GROUPS, 
Sets.newHashSet(serviceGroups))
+        .build();
+
+    Request request = 
PropertyHelper.getCreateRequest(Collections.singleton(requestMap), null);
+
+    RequestStatus requestStatus = provider.createResourcesAuthorized(request);
+
+    assertEquals(1, requestStatus.getAssociatedResources().size());
+
+    Resource resource = 
requestStatus.getAssociatedResources().iterator().next();
+    Long id = (Long) 
resource.getPropertyValue(UpgradePlanResourceProvider.UPGRADE_PLAN_ID);
+    assertNotNull(id);
+
+    UpgradePlanEntity entity = planDAO.findByPK(id);
+    assertNotNull(entity);
+    assertEquals(1, entity.getDetails().size());
+
+    UpgradePlanDetailEntity detail = entity.getDetails().iterator().next();
+    assertEquals(4L, detail.getServiceGroupId());
+    assertEquals(2L, detail.getMpackTargetId());
+  }
+
+
+  /**
+   * @param amc
+   * @return the provider
+   */
+  private UpgradePlanResourceProvider 
createProvider(AmbariManagementController amc) {
+    return new UpgradePlanResourceProvider(amc);
+  }
+
+  /**
+   * Mock module that will bind UpgradeHelper to a mock instance.
+   */
+  private class MockModule implements Module {
+    @Override
+    public void configure(Binder binder) {
+      binder.bind(AmbariManagementController.class).toInstance(amc);
+    }
+  }
+}

-- 
To stop receiving notification emails like this one, please contact
nc...@apache.org.

Reply via email to