You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@falcon.apache.org by ba...@apache.org on 2016/04/20 20:49:11 UTC

falcon git commit: FALCON-1335 Backend support of instance search of a group of entities

Repository: falcon
Updated Branches:
  refs/heads/master c219812ca -> 054aa772b


FALCON-1335 Backend support of instance search of a group of entities

Tested REST API: api/instance/search; Added unit test

p.s. Accidentally deleted the previous branch. Previous reviews: https://github.com/apache/falcon/pull/93

Author: yzheng-hortonworks <yz...@hortonworks.com>

Reviewers: "Sowmya <sr...@hortonworks.com>, Peeyush<pe...@apache.org>, Balu <ba...@apache.org>"

Closes #106 from yzheng-hortonworks/FALCON-1335


Project: http://git-wip-us.apache.org/repos/asf/falcon/repo
Commit: http://git-wip-us.apache.org/repos/asf/falcon/commit/054aa772
Tree: http://git-wip-us.apache.org/repos/asf/falcon/tree/054aa772
Diff: http://git-wip-us.apache.org/repos/asf/falcon/diff/054aa772

Branch: refs/heads/master
Commit: 054aa772b404d96c38dbadcddee1e28a75861bb0
Parents: c219812
Author: yzheng-hortonworks <yz...@hortonworks.com>
Authored: Wed Apr 20 11:49:07 2016 -0700
Committer: bvellanki <bv...@hortonworks.com>
Committed: Wed Apr 20 11:49:07 2016 -0700

----------------------------------------------------------------------
 .../falcon/metadata/RelationshipType.java       | 14 +++
 .../org/apache/falcon/metadata/GraphUtils.java  | 41 +++++++++
 .../falcon/resource/AbstractEntityManager.java  | 31 +++++--
 .../resource/AbstractInstanceManager.java       | 94 ++++++++++++++++++++
 .../metadata/AbstractMetadataResource.java      |  2 +-
 .../resource/proxy/InstanceManagerProxy.java    | 18 ++++
 .../falcon/resource/InstanceManagerTest.java    | 70 ++++++++++++++-
 .../resource/metadata/MetadataTestContext.java  | 46 +++++++---
 .../apache/falcon/resource/InstanceManager.java | 20 +++++
 9 files changed, 313 insertions(+), 23 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/client/src/main/java/org/apache/falcon/metadata/RelationshipType.java
----------------------------------------------------------------------
diff --git a/client/src/main/java/org/apache/falcon/metadata/RelationshipType.java b/client/src/main/java/org/apache/falcon/metadata/RelationshipType.java
index 6624319..47bc377 100644
--- a/client/src/main/java/org/apache/falcon/metadata/RelationshipType.java
+++ b/client/src/main/java/org/apache/falcon/metadata/RelationshipType.java
@@ -18,6 +18,8 @@
 
 package org.apache.falcon.metadata;
 
+import org.apache.falcon.entity.v0.EntityType;
+
 /**
  * Enumerates Relationship types.
  */
@@ -64,4 +66,16 @@ public enum RelationshipType {
 
         throw new IllegalArgumentException("No constant with value " + value + " found");
     }
+
+    public static RelationshipType fromSchedulableEntityType(String type) {
+        EntityType entityType = EntityType.getEnum(type);
+        switch (entityType) {
+        case FEED:
+            return RelationshipType.FEED_ENTITY;
+        case PROCESS:
+            return RelationshipType.PROCESS_ENTITY;
+        default:
+            throw new IllegalArgumentException("Invalid schedulable entity type: " + entityType.name());
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/common/src/main/java/org/apache/falcon/metadata/GraphUtils.java
----------------------------------------------------------------------
diff --git a/common/src/main/java/org/apache/falcon/metadata/GraphUtils.java b/common/src/main/java/org/apache/falcon/metadata/GraphUtils.java
index 8bec02f..6350e20 100644
--- a/common/src/main/java/org/apache/falcon/metadata/GraphUtils.java
+++ b/common/src/main/java/org/apache/falcon/metadata/GraphUtils.java
@@ -18,9 +18,14 @@
 
 package org.apache.falcon.metadata;
 
+import org.apache.commons.lang3.StringUtils;
+import com.thinkaurelius.titan.core.BaseVertexQuery;
+import com.thinkaurelius.titan.core.Order;
 import com.tinkerpop.blueprints.Direction;
 import com.tinkerpop.blueprints.Edge;
 import com.tinkerpop.blueprints.Graph;
+import com.tinkerpop.blueprints.GraphQuery;
+import com.tinkerpop.blueprints.Query.Compare;
 import com.tinkerpop.blueprints.Vertex;
 import com.tinkerpop.blueprints.util.io.graphson.GraphSONWriter;
 import org.slf4j.Logger;
@@ -28,6 +33,7 @@ import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
 import java.io.OutputStream;
+import java.util.Iterator;
 
 /**
  * Utility class for graph operations.
@@ -81,4 +87,39 @@ public final class GraphUtils {
                 + edge.getVertex(Direction.IN).getProperty("name")
                 + "]";
     }
+
+    public static Vertex findVertex(Graph graph, String name, RelationshipType relationshipType) {
+        LOG.debug("Finding vertex for: name={}, type={}", name, relationshipType.getName());
+        GraphQuery query = graph.query()
+                .has(RelationshipProperty.NAME.getName(), name)
+                .has(RelationshipProperty.TYPE.getName(), relationshipType.getName());
+        Iterator<Vertex> results = query.vertices().iterator();
+        return results.hasNext() ? results.next() : null;  // returning one since name is unique
+    }
+
+    public static BaseVertexQuery addRangeQuery(BaseVertexQuery query,
+                                                RelationshipProperty property, String minValue, String maxValue) {
+        if (StringUtils.isNotEmpty(minValue)) {
+            query.has(property.getName(), Compare.GREATER_THAN_EQUAL, minValue);
+        }
+        if (StringUtils.isNotEmpty(maxValue)) {
+            query.has(property.getName(), Compare.LESS_THAN_EQUAL, maxValue);
+        }
+        return query;
+    }
+
+    public static BaseVertexQuery addEqualityQuery(BaseVertexQuery query, RelationshipProperty property, String value) {
+        if (StringUtils.isNotEmpty(value)) {
+            query.has(property.getName(), value);
+        }
+        return query;
+    }
+
+    public static BaseVertexQuery addOrderLimitQuery(BaseVertexQuery query, String orderBy, int numResults) {
+        if (StringUtils.isNotEmpty(orderBy)) {
+            query.orderBy(orderBy, Order.DESC);
+        }
+        query.limit(numResults);
+        return query;
+    }
 }

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/prism/src/main/java/org/apache/falcon/resource/AbstractEntityManager.java
----------------------------------------------------------------------
diff --git a/prism/src/main/java/org/apache/falcon/resource/AbstractEntityManager.java b/prism/src/main/java/org/apache/falcon/resource/AbstractEntityManager.java
index fde0cd7..7d5945a 100644
--- a/prism/src/main/java/org/apache/falcon/resource/AbstractEntityManager.java
+++ b/prism/src/main/java/org/apache/falcon/resource/AbstractEntityManager.java
@@ -41,6 +41,7 @@ import org.apache.falcon.entity.v0.EntityType;
 import org.apache.falcon.entity.v0.cluster.Cluster;
 import org.apache.falcon.resource.APIResult.Status;
 import org.apache.falcon.resource.EntityList.EntityElement;
+import org.apache.falcon.resource.metadata.AbstractMetadataResource;
 import org.apache.falcon.security.CurrentUser;
 import org.apache.falcon.security.SecurityUtil;
 import org.apache.falcon.util.DeploymentUtil;
@@ -72,7 +73,7 @@ import java.util.Set;
 /**
  * A base class for managing Entity operations.
  */
-public abstract class AbstractEntityManager {
+public abstract class AbstractEntityManager extends AbstractMetadataResource {
     private static final Logger LOG = LoggerFactory.getLogger(AbstractEntityManager.class);
     private static MemoryLocks memoryLocks = MemoryLocks.getInstance();
     protected static final String DO_AS_PARAM = "doAs";
@@ -629,6 +630,15 @@ public abstract class AbstractEntityManager {
                                     String filterType, String filterTags, String filterBy,
                                     String orderBy, String sortOrder, Integer offset,
                                     Integer resultsPerPage, final String doAsUser) {
+        return getEntityList(fieldStr, nameSubsequence, tagKeywords, filterType, filterTags, filterBy,
+                orderBy, sortOrder, offset, resultsPerPage, doAsUser, false);
+    }
+
+
+    public EntityList getEntityList(String fieldStr, String nameSubsequence, String tagKeywords,
+                                    String filterType, String filterTags, String filterBy,
+                                    String orderBy, String sortOrder, Integer offset,
+                                    Integer resultsPerPage, final String doAsUser, boolean isReturnAll) {
         HashSet<String> fields = new HashSet<String>(Arrays.asList(fieldStr.toUpperCase().split(",")));
         Map<String, List<String>> filterByFieldsValues = getFilterByFieldsValues(filterBy);
         for (String key : filterByFieldsValues.keySet()) {
@@ -642,7 +652,8 @@ public abstract class AbstractEntityManager {
                     nameSubsequence, tagKeywords, filterType, filterTags, filterBy, doAsUser);
 
             // sort entities and pagination
-            List<Entity> entitiesReturn = sortEntitiesPagination(entities, orderBy, sortOrder, offset, resultsPerPage);
+            List<Entity> entitiesReturn = sortEntitiesPagination(
+                    entities, orderBy, sortOrder, offset, resultsPerPage, isReturnAll);
 
             // add total number of results
             EntityList entityList = entitiesReturn.size() == 0
@@ -681,16 +692,22 @@ public abstract class AbstractEntityManager {
                         entityType, nameSubsequence, tagKeywords, filterByFieldsValues, "", "", "", doAsUser));
             }
         }
+
         return entities;
     }
 
     protected List<Entity> sortEntitiesPagination(List<Entity> entities, String orderBy, String sortOrder,
                                                   Integer offset, Integer resultsPerPage) {
+        return sortEntitiesPagination(entities, orderBy, sortOrder, offset, resultsPerPage, false);
+    }
+
+    protected List<Entity> sortEntitiesPagination(List<Entity> entities, String orderBy, String sortOrder,
+                                                  Integer offset, Integer resultsPerPage, boolean isReturnAll) {
         // sort entities
         entities = sortEntities(entities, orderBy, sortOrder);
 
         // pagination
-        int pageCount = getRequiredNumberOfResults(entities.size(), offset, resultsPerPage);
+        int pageCount = getRequiredNumberOfResults(entities.size(), offset, resultsPerPage, isReturnAll);
         List<Entity> entitiesReturn = new ArrayList<Entity>();
         if (pageCount > 0) {
             entitiesReturn.addAll(entities.subList(offset, (offset + pageCount)));
@@ -1031,13 +1048,17 @@ public abstract class AbstractEntityManager {
     }
 
     protected int getRequiredNumberOfResults(int arraySize, int offset, int numresults) {
+        return getRequiredNumberOfResults(arraySize, offset, numresults, false);
+    }
+
+    protected int getRequiredNumberOfResults(int arraySize, int offset, int numresults, boolean isReturnAll) {
         /* Get a subset of elements based on offset and count. When returning subset of elements,
               elements[offset] is included. Size 10, offset 10, return empty list.
               Size 10, offset 5, count 3, return elements[5,6,7].
               Size 10, offset 5, count >= 5, return elements[5,6,7,8,9]
               return elements starting from elements[offset] until the end OR offset+numResults*/
 
-        if (numresults < 1) {
+        if (!isReturnAll && numresults < 1) {
             LOG.error("Value for param numResults should be > than 0  : {}", numresults);
             throw FalconWebException.newAPIException("Value for param numResults should be > than 0  : " + numresults);
         }
@@ -1050,7 +1071,7 @@ public abstract class AbstractEntityManager {
         }
 
         int retLen = arraySize - offset;
-        if (retLen > numresults) {
+        if (!isReturnAll && retLen > numresults) {
             retLen = numresults;
         }
         return retLen;

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/prism/src/main/java/org/apache/falcon/resource/AbstractInstanceManager.java
----------------------------------------------------------------------
diff --git a/prism/src/main/java/org/apache/falcon/resource/AbstractInstanceManager.java b/prism/src/main/java/org/apache/falcon/resource/AbstractInstanceManager.java
index 1895ba5..ba183c8 100644
--- a/prism/src/main/java/org/apache/falcon/resource/AbstractInstanceManager.java
+++ b/prism/src/main/java/org/apache/falcon/resource/AbstractInstanceManager.java
@@ -37,6 +37,9 @@ import java.util.Set;
 import javax.servlet.ServletInputStream;
 import javax.servlet.http.HttpServletRequest;
 
+import com.thinkaurelius.titan.core.TitanMultiVertexQuery;
+import com.thinkaurelius.titan.core.TitanVertex;
+import com.thinkaurelius.titan.graphdb.blueprints.TitanBlueprintsGraph;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.falcon.FalconException;
 import org.apache.falcon.FalconWebException;
@@ -59,6 +62,10 @@ import org.apache.falcon.entity.v0.feed.Feed;
 import org.apache.falcon.entity.v0.feed.LocationType;
 import org.apache.falcon.entity.v0.process.Process;
 import org.apache.falcon.logging.LogProvider;
+import org.apache.falcon.metadata.GraphUtils;
+import org.apache.falcon.metadata.RelationshipLabel;
+import org.apache.falcon.metadata.RelationshipProperty;
+import org.apache.falcon.metadata.RelationshipType;
 import org.apache.falcon.resource.InstancesResult.Instance;
 import org.apache.falcon.resource.InstancesSummaryResult.InstanceSummary;
 import org.apache.falcon.util.DeploymentUtil;
@@ -676,6 +683,93 @@ public abstract class AbstractInstanceManager extends AbstractEntityManager {
         }
     }
 
+    public InstancesResult searchInstances(String type, String nameSubsequence, String tagKeywords,
+                                           String nominalStartTime, String nominalEndTime,
+                                           String status, String orderBy, Integer offset, Integer resultsPerPage) {
+        type = org.apache.commons.lang.StringUtils.isEmpty(type) ? "feed,process" : type;
+        resultsPerPage = resultsPerPage == null ? getDefaultResultsPerPage() : resultsPerPage;
+
+        // filter entities
+        EntityList entityList = getEntityList(
+                "", nameSubsequence, tagKeywords, type, "", "", "", "", 0, 0, "", true);
+
+        // search instances with TitanDB
+        TitanBlueprintsGraph titanGraph = (TitanBlueprintsGraph) getGraph();
+        Map<TitanVertex, Iterable<TitanVertex>> instanceMap = titanInstances(
+                titanGraph, entityList, resultsPerPage + offset, nominalStartTime, nominalEndTime, status, orderBy);
+
+        // integrate search results from each entity
+        List<Instance> instances = consolidateTitanInstances(instanceMap);
+
+        // sort by descending order and pagination
+        List<Instance> instancesReturn = sortInstancesPagination(instances, orderBy, "desc", offset, resultsPerPage);
+
+        // output format
+        InstancesResult result = new InstancesResult(APIResult.Status.SUCCEEDED, "Instances Search Results");
+        result.setInstances(instancesReturn.toArray(new Instance[instancesReturn.size()]));
+        titanGraph.commit();
+        return result;
+    }
+
+    private Map<TitanVertex, Iterable<TitanVertex>> titanInstances(TitanBlueprintsGraph titanGraph,
+                                                                   EntityList entityList, int numTopInstances,
+                                                                   String nominalStartTime, String nominalEndTime,
+                                                                   String status, String orderBy) {
+        List<TitanVertex> entityVertices = new ArrayList<TitanVertex>();
+        for (EntityList.EntityElement entityElement : entityList.getElements()) {
+            String entityName = entityElement.name;
+            String entityType = entityElement.type;
+            RelationshipType relationshipType = RelationshipType.fromSchedulableEntityType(entityType);
+            TitanVertex entityVertex = (TitanVertex) GraphUtils.findVertex(titanGraph, entityName, relationshipType);
+            if (entityVertex == null) {
+                LOG.warn("No entity vertex found for type " + entityType + ", name " + entityName);
+            } else {
+                entityVertices.add(entityVertex);
+            }
+        }
+
+        if (entityVertices.isEmpty()) { // Need to add at least one vertex for TitanMultiVertexQuery
+            return new HashMap<>();
+        }
+
+        TitanMultiVertexQuery vertexQuery = titanGraph.multiQuery(entityVertices)
+                .labels(RelationshipLabel.INSTANCE_ENTITY_EDGE.getName());
+        GraphUtils.addRangeQuery(vertexQuery, RelationshipProperty.NOMINAL_TIME, nominalStartTime, nominalEndTime);
+        GraphUtils.addEqualityQuery(vertexQuery, RelationshipProperty.STATUS, status);
+        GraphUtils.addOrderLimitQuery(vertexQuery, orderBy, numTopInstances);
+        return vertexQuery.vertices();
+    }
+
+    private List<Instance> consolidateTitanInstances(Map<TitanVertex, Iterable<TitanVertex>> instanceMap) {
+        List<Instance> instances = new ArrayList<>();
+        for (Iterable<TitanVertex> vertices : instanceMap.values()) {
+            for (TitanVertex vertex : vertices) {
+                Instance instance = new Instance();
+                instance.instance = vertex.getProperty(RelationshipProperty.NAME.getName());
+                String instanceStatus = vertex.getProperty(RelationshipProperty.STATUS.getName());
+                if (StringUtils.isNotEmpty(instanceStatus)) {
+                    instance.status = InstancesResult.WorkflowStatus.valueOf(instanceStatus);
+                }
+                instances.add(instance);
+            }
+        }
+        return instances;
+    }
+
+    protected List<Instance> sortInstancesPagination(List<Instance> instances, String orderBy, String sortOrder,
+                                                     Integer offset, Integer resultsPerPage) {
+        // sort instances
+        instances = sortInstances(instances, orderBy, sortOrder);
+
+        // pagination
+        int pageCount = super.getRequiredNumberOfResults(instances.size(), offset, resultsPerPage);
+        List<Instance> instancesReturn = new ArrayList<Instance>();
+        if (pageCount > 0) {
+            instancesReturn.addAll(instances.subList(offset, (offset + pageCount)));
+        }
+        return instancesReturn;
+    }
+
     private void checkName(String entityName) {
         if (StringUtils.isBlank(entityName)) {
             throw FalconWebException.newAPIException("Instance name is mandatory and shouldn't be blank");

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/prism/src/main/java/org/apache/falcon/resource/metadata/AbstractMetadataResource.java
----------------------------------------------------------------------
diff --git a/prism/src/main/java/org/apache/falcon/resource/metadata/AbstractMetadataResource.java b/prism/src/main/java/org/apache/falcon/resource/metadata/AbstractMetadataResource.java
index 762becb..7e65ef8 100644
--- a/prism/src/main/java/org/apache/falcon/resource/metadata/AbstractMetadataResource.java
+++ b/prism/src/main/java/org/apache/falcon/resource/metadata/AbstractMetadataResource.java
@@ -64,7 +64,7 @@ public abstract class AbstractMetadataResource {
     private void checkIfMetadataMappingServiceIsEnabled() {
         if (service == null) {
             throw FalconWebException.newMetadataResourceException(
-                    "Lineage " + MetadataMappingService.SERVICE_NAME + " is not enabled.", Response.Status.NOT_FOUND);
+                    MetadataMappingService.SERVICE_NAME + " is not enabled.", Response.Status.NOT_FOUND);
         }
     }
 }

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/prism/src/main/java/org/apache/falcon/resource/proxy/InstanceManagerProxy.java
----------------------------------------------------------------------
diff --git a/prism/src/main/java/org/apache/falcon/resource/proxy/InstanceManagerProxy.java b/prism/src/main/java/org/apache/falcon/resource/proxy/InstanceManagerProxy.java
index 4951c4a..1023923 100644
--- a/prism/src/main/java/org/apache/falcon/resource/proxy/InstanceManagerProxy.java
+++ b/prism/src/main/java/org/apache/falcon/resource/proxy/InstanceManagerProxy.java
@@ -603,6 +603,24 @@ public class InstanceManagerProxy extends AbstractInstanceManager {
         }.execute(colo, entityType, entityName);
     }
 
+    @GET
+    @Path("search")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Monitored(event = "instance-search")
+    @Override
+    public InstancesResult searchInstances(
+            @DefaultValue("") @QueryParam("type") String type,
+            @DefaultValue("") @QueryParam("nameseq") String nameSubsequence,
+            @DefaultValue("") @QueryParam("tagkeys") String tagKeywords,
+            @DefaultValue("") @QueryParam("start") String nominalStartTime,
+            @DefaultValue("") @QueryParam("end") String nominalEndTime,
+            @DefaultValue("") @QueryParam("instanceStatus") String status,
+            @DefaultValue("") @QueryParam("orderBy") String orderBy,
+            @DefaultValue("0") @QueryParam("offset") Integer offset,
+            @QueryParam("numResults") Integer resultsPerPage) {
+        return super.searchInstances(type, nameSubsequence, tagKeywords, nominalStartTime, nominalEndTime,
+                status, orderBy, offset, resultsPerPage);
+    }
 
     //RESUME CHECKSTYLE CHECK ParameterNumberCheck
 

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/prism/src/test/java/org/apache/falcon/resource/InstanceManagerTest.java
----------------------------------------------------------------------
diff --git a/prism/src/test/java/org/apache/falcon/resource/InstanceManagerTest.java b/prism/src/test/java/org/apache/falcon/resource/InstanceManagerTest.java
index 29762ab..237868d 100644
--- a/prism/src/test/java/org/apache/falcon/resource/InstanceManagerTest.java
+++ b/prism/src/test/java/org/apache/falcon/resource/InstanceManagerTest.java
@@ -19,15 +19,79 @@
 package org.apache.falcon.resource;
 
 import org.apache.falcon.FalconWebException;
+import org.apache.falcon.resource.metadata.MetadataTestContext;
+import org.testng.Assert;
+import org.testng.annotations.AfterClass;
+import org.testng.annotations.BeforeClass;
 import org.testng.annotations.Test;
 
 /**
- *
+ * Unit tests for org.apache.falcon.resource.AbstractInstanceManager.
  */
-public class InstanceManagerTest extends AbstractInstanceManager {
+public class InstanceManagerTest {
+
+    private static final String PROCESS_NAME_PREFIX = "instance-search-test-process";
+    private static final String PROCESS_NAME_1 = "instance-search-test-process-1";
+    private static final String PROCESS_NAME_2 = "instance-search-test-process-2";
+    private static final String NOMINAL_TIME_1 = "2015-01-01-01-00";
+    private static final String NOMINAL_TIME_2 = "2015-01-02-01-00";
+    private static final String NOMINAL_TIME_3 = "2015-01-03-01-00";
+
+    private MetadataTestContext testContext;
+
+    @BeforeClass
+    public void setUp() throws Exception {
+        testContext = new MetadataTestContext();
+        testContext.setUp();
+    }
+
+    @AfterClass
+    public void tearDown() throws Exception {
+        testContext.tearDown();
+    }
+
+    @Test
+    public void testInstanceSearch() throws Exception {
+        // Note: all the following tests are based on entity name prefix PROCESS_NAME_PREFIX
+        testContext.addProcessEntity(PROCESS_NAME_1);
+        testContext.addInstance(PROCESS_NAME_1, NOMINAL_TIME_1, MetadataTestContext.SUCCEEDED_STATUS);
+        testContext.addInstance(PROCESS_NAME_1, NOMINAL_TIME_2, MetadataTestContext.SUCCEEDED_STATUS);
+        testContext.addInstance(PROCESS_NAME_1, NOMINAL_TIME_3, MetadataTestContext.RUNNING_STATUS);
+        testContext.addProcessEntity(PROCESS_NAME_2);
+        testContext.addInstance(PROCESS_NAME_2, NOMINAL_TIME_1, MetadataTestContext.FAILED_STATUS);
+        testContext.addInstance(PROCESS_NAME_2, NOMINAL_TIME_2, MetadataTestContext.RUNNING_STATUS);
+
+        // list all instances
+        BaseInstanceManager instanceManager = new BaseInstanceManager();
+        InstancesResult result;
+        result = instanceManager.searchInstances("PROCESS", PROCESS_NAME_PREFIX, "", "", "", "", "", 0, 10);
+        Assert.assertEquals(result.getInstances().length, 5);
+
+        // running status
+        result = instanceManager.searchInstances("PROCESS", PROCESS_NAME_PREFIX, "", "", "",
+                MetadataTestContext.RUNNING_STATUS, "", 0, 10);
+        Assert.assertEquals(result.getInstances().length, 2);
+
+        // succeeded status
+        result = instanceManager.searchInstances("PROCESS", PROCESS_NAME_PREFIX, "", "", "",
+                MetadataTestContext.SUCCEEDED_STATUS, "", 0, 10);
+        Assert.assertEquals(result.getInstances().length, 2);
+
+        // failed status
+        result = instanceManager.searchInstances("PROCESS", PROCESS_NAME_PREFIX, "", "", "",
+                MetadataTestContext.FAILED_STATUS, "", 0, 10);
+        Assert.assertEquals(result.getInstances().length, 1);
+
+        // nominal time filter
+        result = instanceManager.searchInstances("PROCESS", PROCESS_NAME_PREFIX, "", NOMINAL_TIME_2, "", "", "", 0, 10);
+        Assert.assertEquals(result.getInstances().length, 3);
+    }
 
     @Test(expectedExceptions = FalconWebException.class)
     public void test() {
-        super.triageInstance("process", "random", "2014-05-07T00:00Z", "default");
+        BaseInstanceManager instanceManager = new BaseInstanceManager();
+        instanceManager.triageInstance("process", "random", "2014-05-07T00:00Z", "default");
     }
+
+    private class BaseInstanceManager extends AbstractInstanceManager {}
 }

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/prism/src/test/java/org/apache/falcon/resource/metadata/MetadataTestContext.java
----------------------------------------------------------------------
diff --git a/prism/src/test/java/org/apache/falcon/resource/metadata/MetadataTestContext.java b/prism/src/test/java/org/apache/falcon/resource/metadata/MetadataTestContext.java
index a382d85..47d6ba1 100644
--- a/prism/src/test/java/org/apache/falcon/resource/metadata/MetadataTestContext.java
+++ b/prism/src/test/java/org/apache/falcon/resource/metadata/MetadataTestContext.java
@@ -55,8 +55,9 @@ import java.util.List;
  */
 public class MetadataTestContext {
     public static final String FALCON_USER = "falcon-user";
-    private static final String LOGS_DIR = "target/log";
-    private static final String NOMINAL_TIME = "2014-01-01-01-00";
+    public static final String SUCCEEDED_STATUS = "SUCCEEDED";
+    public static final String FAILED_STATUS = "FAILED";
+    public static final String RUNNING_STATUS = "RUNNING";
     public static final String OPERATION = "GENERATE";
 
     public static final String CLUSTER_ENTITY_NAME = "primary-cluster";
@@ -74,6 +75,9 @@ public class MetadataTestContext {
     public static final String OUTPUT_INSTANCE_PATHS =
             "jail://global:00/falcon/imp-click-join1/20140101,jail://global:00/falcon/imp-click-join2/20140101";
 
+    private static final String LOGS_DIR = "target/log";
+    private static final String NOMINAL_TIME = "2014-01-01-01-00";
+
     private ConfigurationStore configStore;
     private MetadataMappingService service;
 
@@ -99,8 +103,8 @@ public class MetadataTestContext {
 
         addClusterEntity();
         addFeedEntity();
-        addProcessEntity();
-        addInstance();
+        addProcessEntity(PROCESS_ENTITY_NAME);
+        addInstance(PROCESS_ENTITY_NAME, NOMINAL_TIME, SUCCEEDED_STATUS);
     }
 
     public MetadataMappingService getService() {
@@ -164,9 +168,9 @@ public class MetadataTestContext {
         }
     }
 
-    public void addProcessEntity() throws Exception {
+    public void addProcessEntity(String processEntityName) throws Exception {
         org.apache.falcon.entity.v0.process.Process processEntity =
-                EntityBuilderTestUtil.buildProcess(PROCESS_ENTITY_NAME,
+                EntityBuilderTestUtil.buildProcess(processEntityName,
                 clusterEntity, "classified-as=Critical", "testPipeline");
         EntityBuilderTestUtil.addProcessWorkflow(processEntity, WORKFLOW_NAME, WORKFLOW_VERSION);
 
@@ -198,11 +202,25 @@ public class MetadataTestContext {
         configStore.publish(EntityType.PROCESS, processEntity);
     }
 
-    public void addInstance() throws Exception {
+    public void addInstance(String entityName, String nominalTime, String status) throws Exception {
         createJobCountersFileForTest();
-        WorkflowExecutionContext context = WorkflowExecutionContext.create(getTestMessageArgs(),
+        WorkflowExecutionContext context = WorkflowExecutionContext.create(
+                getTestMessageArgs(entityName, nominalTime, status),
                 WorkflowExecutionContext.Type.POST_PROCESSING);
-        service.onSuccess(context);
+        switch (status) {
+        case SUCCEEDED_STATUS:
+            service.onSuccess(context);
+            break;
+        case FAILED_STATUS:
+            service.onFailure(context);
+            break;
+        case RUNNING_STATUS:
+            service.onStart(context);
+            break;
+        default:
+            // Should not reach here
+            Assert.assertTrue(false, "Adding instance with an unsupported status in test context: " + status);
+        }
     }
 
     private void cleanupGraphStore(Graph graph) {
@@ -240,12 +258,12 @@ public class MetadataTestContext {
         }
     }
 
-    private static String[] getTestMessageArgs() {
+    private static String[] getTestMessageArgs(String entityName, String nominalTime, String status) {
         return new String[]{
             "-" + WorkflowExecutionArgs.CLUSTER_NAME.getName(), CLUSTER_ENTITY_NAME,
             "-" + WorkflowExecutionArgs.ENTITY_TYPE.getName(), ("process"),
-            "-" + WorkflowExecutionArgs.ENTITY_NAME.getName(), PROCESS_ENTITY_NAME,
-            "-" + WorkflowExecutionArgs.NOMINAL_TIME.getName(), NOMINAL_TIME,
+            "-" + WorkflowExecutionArgs.ENTITY_NAME.getName(), entityName,
+            "-" + WorkflowExecutionArgs.NOMINAL_TIME.getName(), nominalTime,
             "-" + WorkflowExecutionArgs.OPERATION.getName(), OPERATION,
             "-" + WorkflowExecutionArgs.INPUT_FEED_NAMES.getName(), INPUT_FEED_NAMES,
             "-" + WorkflowExecutionArgs.INPUT_FEED_PATHS.getName(), INPUT_INSTANCE_PATHS,
@@ -254,8 +272,8 @@ public class MetadataTestContext {
             "-" + WorkflowExecutionArgs.WORKFLOW_ID.getName(), "workflow-01-00",
             "-" + WorkflowExecutionArgs.WORKFLOW_USER.getName(), FALCON_USER,
             "-" + WorkflowExecutionArgs.RUN_ID.getName(), "1",
-            "-" + WorkflowExecutionArgs.STATUS.getName(), "SUCCEEDED",
-            "-" + WorkflowExecutionArgs.TIMESTAMP.getName(), NOMINAL_TIME,
+            "-" + WorkflowExecutionArgs.STATUS.getName(), status,
+            "-" + WorkflowExecutionArgs.TIMESTAMP.getName(), nominalTime,
             "-" + WorkflowExecutionArgs.WF_ENGINE_URL.getName(), "http://localhost:11000/oozie",
             "-" + WorkflowExecutionArgs.USER_SUBFLOW_ID.getName(), "userflow@wf-id",
             "-" + WorkflowExecutionArgs.USER_WORKFLOW_NAME.getName(), WORKFLOW_NAME,

http://git-wip-us.apache.org/repos/asf/falcon/blob/054aa772/webapp/src/main/java/org/apache/falcon/resource/InstanceManager.java
----------------------------------------------------------------------
diff --git a/webapp/src/main/java/org/apache/falcon/resource/InstanceManager.java b/webapp/src/main/java/org/apache/falcon/resource/InstanceManager.java
index 7108597..5b47dc9 100644
--- a/webapp/src/main/java/org/apache/falcon/resource/InstanceManager.java
+++ b/webapp/src/main/java/org/apache/falcon/resource/InstanceManager.java
@@ -306,6 +306,26 @@ public class InstanceManager extends AbstractInstanceManager {
             throw FalconWebException.newAPIException(throwable);
         }
     }
+
+    @GET
+    @Path("search")
+    @Produces(MediaType.APPLICATION_JSON)
+    @Monitored(event = "instance-search")
+    @Override
+    public InstancesResult searchInstances(
+            @DefaultValue("") @QueryParam("type") String type,
+            @DefaultValue("") @QueryParam("nameseq") String nameSubsequence,
+            @DefaultValue("") @QueryParam("tagkeys") String tagKeywords,
+            @DefaultValue("") @QueryParam("start") String nominalStartTime,
+            @DefaultValue("") @QueryParam("end") String nominalEndTime,
+            @DefaultValue("") @QueryParam("instanceStatus") String status,
+            @DefaultValue("") @QueryParam("orderBy") String orderBy,
+            @DefaultValue("0") @QueryParam("offset") Integer offset,
+            @QueryParam("numResults") Integer resultsPerPage) {
+        return super.searchInstances(type, nameSubsequence, tagKeywords, nominalStartTime, nominalEndTime,
+                status, orderBy, offset, resultsPerPage);
+    }
+
     //RESUME CHECKSTYLE CHECK ParameterNumberCheck