From 451c13b1de1e42394a4c92888192da9a9519d9ac Mon Sep 17 00:00:00 2001 From: Sangjin Lee Date: Fri, 21 Aug 2015 19:10:23 -0700 Subject: [PATCH] YARN-3814. REST API implementation for getting raw entities in TimelineReader (Varun Saxena via sjlee) --- .../reader/TimelineReaderManager.java | 41 ++ .../reader/TimelineReaderServer.java | 2 +- .../reader/TimelineReaderWebServices.java | 245 +++++++++- .../storage/FileSystemTimelineReaderImpl.java | 5 + .../reader/TestTimelineReaderWebServices.java | 456 +++++++++++++++++- 5 files changed, 738 insertions(+), 11 deletions(-) diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java index 55731855e2c..7fafd82c4d1 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderManager.java @@ -18,10 +18,18 @@ package org.apache.hadoop.yarn.server.timelineservice.reader; +import java.io.IOException; +import java.util.EnumSet; +import java.util.Map; +import java.util.Set; + import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.service.AbstractService; +import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; import org.apache.hadoop.yarn.server.timelineservice.storage.TimelineReader; +import org.apache.hadoop.yarn.server.timelineservice.storage.TimelineReader.Field; @Private @Unstable @@ -33,4 +41,37 @@ public class TimelineReaderManager extends AbstractService { super(TimelineReaderManager.class.getName()); this.reader = timelineReader; } + + /** + * Get a set of entities matching given predicates. The meaning of each + * argument has been documented with {@link TimelineReader#getEntities}. + * + * @see TimelineReader#getEntities + */ + Set getEntities(String userId, String clusterId, + String flowId, Long flowRunId, String appId, String entityType, + Long limit, Long createdTimeBegin, Long createdTimeEnd, + Long modifiedTimeBegin, Long modifiedTimeEnd, + Map> relatesTo, Map> isRelatedTo, + Map infoFilters, Map configFilters, + Set metricFilters, Set eventFilters, + EnumSet fieldsToRetrieve) throws IOException { + return reader.getEntities(userId, clusterId, flowId, flowRunId, appId, + entityType, limit, createdTimeBegin, createdTimeEnd, modifiedTimeBegin, + modifiedTimeEnd, relatesTo, isRelatedTo, infoFilters, configFilters, + metricFilters, eventFilters, fieldsToRetrieve); + } + + /** + * Get single timeline entity. The meaning of each argument has been + * documented with {@link TimelineReader#getEntity}. + * + * @see TimelineReader#getEntity + */ + public TimelineEntity getEntity(String userId, String clusterId, + String flowId, Long flowRunId, String appId, String entityType, + String entityId, EnumSet fields) throws IOException { + return reader.getEntity(userId, clusterId, flowId, flowRunId, appId, + entityType, entityId, fields); + } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderServer.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderServer.java index 874112c8c47..319cfb08f49 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderServer.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderServer.java @@ -54,7 +54,7 @@ import com.google.common.annotations.VisibleForTesting; public class TimelineReaderServer extends CompositeService { private static final Log LOG = LogFactory.getLog(TimelineReaderServer.class); private static final int SHUTDOWN_HOOK_PRIORITY = 30; - private static final String TIMELINE_READER_MANAGER_ATTR = + static final String TIMELINE_READER_MANAGER_ATTR = "timeline.reader.manager"; private HttpServer2 readerWebServer; diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java index 3655a7284d8..0b5fde0aa2c 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/reader/TimelineReaderWebServices.java @@ -18,42 +18,283 @@ package org.apache.hadoop.yarn.server.timelineservice.reader; +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.ws.rs.GET; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.apache.hadoop.classification.InterfaceAudience.Private; import org.apache.hadoop.classification.InterfaceStability.Unstable; +import org.apache.hadoop.security.UserGroupInformation; import org.apache.hadoop.yarn.api.records.timeline.TimelineAbout; +import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; +import org.apache.hadoop.yarn.server.timeline.GenericObjectMapper; +import org.apache.hadoop.yarn.server.timelineservice.storage.TimelineReader.Field; import org.apache.hadoop.yarn.util.timeline.TimelineUtils; +import org.apache.hadoop.yarn.webapp.BadRequestException; +import org.apache.hadoop.yarn.webapp.NotFoundException; import com.google.inject.Singleton; - /** REST end point for Timeline Reader */ @Private @Unstable @Singleton @Path("/ws/v2/timeline") public class TimelineReaderWebServices { + private static final Log LOG = + LogFactory.getLog(TimelineReaderWebServices.class); + + @Context private ServletContext ctxt; + + private static final String COMMA_DELIMITER = ","; + private static final String COLON_DELIMITER = ":"; private void init(HttpServletResponse response) { response.setContentType(null); } + private static Set parseValuesStr(String str, String delimiter) { + if (str == null || str.isEmpty()) { + return null; + } + Set strSet = new HashSet(); + String[] strs = str.split(delimiter); + for (String aStr : strs) { + strSet.add(aStr.trim()); + } + return strSet; + } + + @SuppressWarnings("unchecked") + private static void parseKeyValues(Map map, String str, + String pairsDelim, String keyValuesDelim, boolean stringValue, + boolean multipleValues) { + String[] pairs = str.split(pairsDelim); + for (String pair : pairs) { + if (pair == null || pair.trim().isEmpty()) { + continue; + } + String[] pairStrs = pair.split(keyValuesDelim); + if (pairStrs.length < 2) { + continue; + } + if (!stringValue) { + try { + Object value = + GenericObjectMapper.OBJECT_READER.readValue(pairStrs[1].trim()); + map.put(pairStrs[0].trim(), (T) value); + } catch (IOException e) { + map.put(pairStrs[0].trim(), (T) pairStrs[1].trim()); + } + } else { + String key = pairStrs[0].trim(); + if (multipleValues) { + Set values = new HashSet(); + for (int i = 1; i < pairStrs.length; i++) { + values.add(pairStrs[i].trim()); + } + map.put(key, (T) values); + } else { + map.put(key, (T) pairStrs[1].trim()); + } + } + } + } + + private static Map> parseKeyStrValuesStr(String str, + String pairsDelim, String keyValuesDelim) { + if (str == null) { + return null; + } + Map> map = new HashMap>(); + parseKeyValues(map, str,pairsDelim, keyValuesDelim, true, true); + return map; + } + + private static Map parseKeyStrValueStr(String str, + String pairsDelim, String keyValDelim) { + if (str == null) { + return null; + } + Map map = new HashMap(); + parseKeyValues(map, str, pairsDelim, keyValDelim, true, false); + return map; + } + + private static Map parseKeyStrValueObj(String str, + String pairsDelim, String keyValDelim) { + if (str == null) { + return null; + } + Map map = new HashMap(); + parseKeyValues(map, str, pairsDelim, keyValDelim, false, false); + return map; + } + + private static EnumSet parseFieldsStr(String str, String delimiter) { + if (str == null) { + return null; + } + String[] strs = str.split(delimiter); + EnumSet fieldList = EnumSet.noneOf(Field.class); + for (String s : strs) { + fieldList.add(Field.valueOf(s.trim().toUpperCase())); + } + return fieldList; + } + + private static Long parseLongStr(String str) { + return str == null ? null : Long.parseLong(str.trim()); + } + + private static String parseStr(String str) { + return str == null ? null : str.trim(); + } + + private static UserGroupInformation getUser(HttpServletRequest req) { + String remoteUser = req.getRemoteUser(); + UserGroupInformation callerUGI = null; + if (remoteUser != null) { + callerUGI = UserGroupInformation.createRemoteUser(remoteUser); + } + return callerUGI; + } + + private TimelineReaderManager getTimelineReaderManager() { + return (TimelineReaderManager) + ctxt.getAttribute(TimelineReaderServer.TIMELINE_READER_MANAGER_ATTR); + } + /** * Return the description of the timeline reader web services. */ @GET - @Produces({ MediaType.APPLICATION_JSON /* , MediaType.APPLICATION_XML */}) + @Produces(MediaType.APPLICATION_JSON) public TimelineAbout about( @Context HttpServletRequest req, @Context HttpServletResponse res) { init(res); return TimelineUtils.createTimelineAbout("Timeline Reader API"); } + + /** + * Return a set of entities that match the given parameters. + */ + @GET + @Path("/entities/{clusterId}/{appId}/{entityType}") + @Produces(MediaType.APPLICATION_JSON) + public Set getEntities( + @Context HttpServletRequest req, + @Context HttpServletResponse res, + @PathParam("clusterId") String clusterId, + @PathParam("appId") String appId, + @PathParam("entityType") String entityType, + @QueryParam("userId") String userId, + @QueryParam("flowId") String flowId, + @QueryParam("flowRunId") String flowRunId, + @QueryParam("limit") String limit, + @QueryParam("createdTimeStart") String createdTimeStart, + @QueryParam("createdTimeEnd") String createdTimeEnd, + @QueryParam("modifiedTimeStart") String modifiedTimeStart, + @QueryParam("modifiedTimeEnd") String modifiedTimeEnd, + @QueryParam("relatesto") String relatesTo, + @QueryParam("isrelatedto") String isRelatedTo, + @QueryParam("infofilters") String infofilters, + @QueryParam("conffilters") String conffilters, + @QueryParam("metricfilters") String metricfilters, + @QueryParam("eventfilters") String eventfilters, + @QueryParam("fields") String fields) { + init(res); + TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + UserGroupInformation callerUGI = getUser(req); + try { + return timelineReaderManager.getEntities( + callerUGI != null && (userId == null || userId.isEmpty()) ? + callerUGI.getUserName().trim() : parseStr(userId), + parseStr(clusterId), parseStr(flowId), + parseLongStr(flowRunId), parseStr(appId), parseStr(entityType), + parseLongStr(limit), parseLongStr(createdTimeStart), + parseLongStr(createdTimeEnd), parseLongStr(modifiedTimeStart), + parseLongStr(modifiedTimeEnd), + parseKeyStrValuesStr(relatesTo, COMMA_DELIMITER, COLON_DELIMITER), + parseKeyStrValuesStr(isRelatedTo, COMMA_DELIMITER, COLON_DELIMITER), + parseKeyStrValueObj(infofilters, COMMA_DELIMITER, COLON_DELIMITER), + parseKeyStrValueStr(conffilters, COMMA_DELIMITER, COLON_DELIMITER), + parseValuesStr(metricfilters, COMMA_DELIMITER), + parseValuesStr(eventfilters, COMMA_DELIMITER), + parseFieldsStr(fields, COMMA_DELIMITER)); + } catch (NumberFormatException e) { + throw new BadRequestException( + "createdTime or modifiedTime start/end or limit or flowId is not" + + " a numeric value."); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Requested Invalid Field."); + } catch (Exception e) { + LOG.error("Error getting entities", e); + throw new WebApplicationException(e, + Response.Status.INTERNAL_SERVER_ERROR); + } + } + + /** + * Return a single entity of the given entity type and Id. + */ + @GET + @Path("/entity/{clusterId}/{appId}/{entityType}/{entityId}/") + @Produces(MediaType.APPLICATION_JSON) + public TimelineEntity getEntity( + @Context HttpServletRequest req, + @Context HttpServletResponse res, + @PathParam("clusterId") String clusterId, + @PathParam("appId") String appId, + @PathParam("entityType") String entityType, + @PathParam("entityId") String entityId, + @QueryParam("userId") String userId, + @QueryParam("flowId") String flowId, + @QueryParam("flowRunId") String flowRunId, + @QueryParam("fields") String fields) { + init(res); + TimelineReaderManager timelineReaderManager = getTimelineReaderManager(); + UserGroupInformation callerUGI = getUser(req); + TimelineEntity entity = null; + try { + entity = timelineReaderManager.getEntity( + callerUGI != null && (userId == null || userId.isEmpty()) ? + callerUGI.getUserName().trim() : parseStr(userId), + parseStr(clusterId), parseStr(flowId), parseLongStr(flowRunId), + parseStr(appId), parseStr(entityType), parseStr(entityId), + parseFieldsStr(fields, COMMA_DELIMITER)); + } catch (NumberFormatException e) { + throw new BadRequestException("flowRunId is not a numeric value."); + } catch (IllegalArgumentException e) { + throw new BadRequestException("Requested Invalid Field."); + } catch (Exception e) { + LOG.error("Error getting entity", e); + throw new WebApplicationException(e, + Response.Status.INTERNAL_SERVER_ERROR); + } + if (entity == null) { + throw new NotFoundException("Timeline entity {id: " + parseStr(entityId) + + ", type: " + parseStr(entityType) + " } is not found"); + } + return entity; + } } \ No newline at end of file diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java index 45ddd1d625d..626c7706f60 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/main/java/org/apache/hadoop/yarn/server/timelineservice/storage/FileSystemTimelineReaderImpl.java @@ -21,6 +21,7 @@ package org.apache.hadoop.yarn.server.timelineservice.storage; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; @@ -397,6 +398,10 @@ public class FileSystemTimelineReaderImpl extends AbstractService new FileInputStream(entityFile), Charset.forName("UTF-8")))) { TimelineEntity entity = readEntityFromFile(reader); return createEntityToBeReturned(entity, fieldsToRetrieve); + } catch (FileNotFoundException e) { + LOG.info("Cannot find entity {id:" + entityId + " , type:" + entityType + + "}. Will send HTTP 404 in response."); + return null; } } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/reader/TestTimelineReaderWebServices.java b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/reader/TestTimelineReaderWebServices.java index a9145d06b28..0f7c22fe99a 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/reader/TestTimelineReaderWebServices.java +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-timelineservice/src/test/java/org/apache/hadoop/yarn/server/timelineservice/reader/TestTimelineReaderWebServices.java @@ -18,25 +18,37 @@ package org.apache.hadoop.yarn.server.timelineservice.reader; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + import java.io.IOException; import java.lang.reflect.UndeclaredThrowableException; import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import java.util.Set; import javax.ws.rs.core.MediaType; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.yarn.api.records.timeline.TimelineAbout; +import org.apache.hadoop.yarn.api.records.timelineservice.TimelineEntity; import org.apache.hadoop.yarn.conf.YarnConfiguration; import org.apache.hadoop.yarn.webapp.YarnJacksonJaxbJsonProvider; +import org.apache.hadoop.yarn.server.timelineservice.storage.TestFileSystemTimelineReaderImpl; import org.junit.After; +import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; +import org.junit.BeforeClass; import org.junit.Test; import com.sun.jersey.api.client.Client; import com.sun.jersey.api.client.ClientResponse; +import com.sun.jersey.api.client.ClientResponse.Status; +import com.sun.jersey.api.client.GenericType; import com.sun.jersey.api.client.config.ClientConfig; import com.sun.jersey.api.client.config.DefaultClientConfig; import com.sun.jersey.client.urlconnection.HttpURLConnectionFactory; @@ -46,12 +58,23 @@ public class TestTimelineReaderWebServices { private int serverPort; private TimelineReaderServer server; + @BeforeClass + public static void setup() throws Exception { + TestFileSystemTimelineReaderImpl.setup(); + } + + @AfterClass + public static void tearDown() throws Exception { + TestFileSystemTimelineReaderImpl.tearDown(); + } + @Before public void init() throws Exception { try { Configuration config = new YarnConfiguration(); config.set(YarnConfiguration.TIMELINE_SERVICE_WEBAPP_ADDRESS, "localhost:0"); + config.set(YarnConfiguration.RM_CLUSTER_ID, "cluster1"); server = new TimelineReaderServer(); server.init(config); server.start(); @@ -69,6 +92,22 @@ public class TestTimelineReaderWebServices { } } + private static TimelineEntity newEntity(String type, String id) { + TimelineEntity entity = new TimelineEntity(); + entity.setIdentifier(new TimelineEntity.Identifier(type, id)); + return entity; + } + + private static void verifyHttpResponse(Client client, URI uri, + Status status) { + ClientResponse resp = + client.resource(uri).accept(MediaType.APPLICATION_JSON) + .type(MediaType.APPLICATION_JSON).get(ClientResponse.class); + assertNotNull(resp); + assertTrue("Response from server should have been " + status, + resp.getClientResponseStatus().equals(status)); + } + private static Client createClient() { ClientConfig cfg = new DefaultClientConfig(); cfg.getClasses().add(YarnJacksonJaxbJsonProvider.class); @@ -76,14 +115,19 @@ public class TestTimelineReaderWebServices { new DummyURLConnectionFactory()), cfg); } - private static ClientResponse getResponse(Client client, URI uri) throws Exception { + private static ClientResponse getResponse(Client client, URI uri) + throws Exception { ClientResponse resp = client.resource(uri).accept(MediaType.APPLICATION_JSON) .type(MediaType.APPLICATION_JSON).get(ClientResponse.class); if (resp == null || resp.getClientResponseStatus() != ClientResponse.Status.OK) { - System.out.println(resp.getClientResponseStatus()); - throw new IOException("Incorrect response from timeline reader."); + String msg = new String(); + if (resp != null) { + msg = resp.getClientResponseStatus().toString(); + } + throw new IOException("Incorrect response from timeline reader. " + + "Status=" + msg); } return resp; } @@ -102,8 +146,7 @@ public class TestTimelineReaderWebServices { } @Test - public void testAbout() - throws IOException { + public void testAbout() throws Exception { URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/"); Client client = createClient(); try { @@ -111,9 +154,406 @@ public class TestTimelineReaderWebServices { TimelineAbout about = resp.getEntity(TimelineAbout.class); Assert.assertNotNull(about); Assert.assertEquals("Timeline Reader API", about.getAbout()); - } catch (Exception re) { - throw new IOException( - "Failed to get the response from timeline reader.", re); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntityDefaultView() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entity/cluster1/app1/app/id_1"); + ClientResponse resp = getResponse(client, uri); + TimelineEntity entity = resp.getEntity(TimelineEntity.class); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entity); + assertEquals("id_1", entity.getId()); + assertEquals("app", entity.getType()); + assertEquals(1425016502000L, entity.getCreatedTime()); + assertEquals(1425016503000L, entity.getModifiedTime()); + // Default view i.e. when no fields are specified, entity contains only + // entity id, entity type, created and modified time. + assertEquals(0, entity.getConfigs().size()); + assertEquals(0, entity.getMetrics().size()); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntityWithUserAndFlowInfo() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entity/cluster1/app1/app/id_1?userId=user1&" + + "flowId=flow1&flowRunId=1"); + ClientResponse resp = getResponse(client, uri); + TimelineEntity entity = resp.getEntity(TimelineEntity.class); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entity); + assertEquals("id_1", entity.getId()); + assertEquals("app", entity.getType()); + assertEquals(1425016502000L, entity.getCreatedTime()); + assertEquals(1425016503000L, entity.getModifiedTime()); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntityCustomFields() throws Exception { + Client client = createClient(); + try { + // Fields are case insensitive. + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entity/cluster1/app1/app/id_1?fields=CONFIGS,Metrics,info"); + ClientResponse resp = getResponse(client, uri); + TimelineEntity entity = resp.getEntity(TimelineEntity.class); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entity); + assertEquals("id_1", entity.getId()); + assertEquals("app", entity.getType()); + assertEquals(3, entity.getConfigs().size()); + assertEquals(3, entity.getMetrics().size()); + assertEquals(1, entity.getInfo().size()); + // No events will be returned as events are not part of fields. + assertEquals(0, entity.getEvents().size()); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntityAllFields() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entity/cluster1/app1/app/id_1?fields=ALL"); + ClientResponse resp = getResponse(client, uri); + TimelineEntity entity = resp.getEntity(TimelineEntity.class); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entity); + assertEquals("id_1", entity.getId()); + assertEquals("app", entity.getType()); + assertEquals(3, entity.getConfigs().size()); + assertEquals(3, entity.getMetrics().size()); + assertEquals(1, entity.getInfo().size()); + assertEquals(2, entity.getEvents().size()); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntityNotPresent() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entity/cluster1/app1/app/id_10"); + verifyHttpResponse(client, uri, Status.NOT_FOUND); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntities() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(4, entities.size()); + assertTrue("Entities id_1, id_2, id_3 and id_4 should have been" + + " present in response", + entities.contains(newEntity("app", "id_1")) && + entities.contains(newEntity("app", "id_2")) && + entities.contains(newEntity("app", "id_3")) && + entities.contains(newEntity("app", "id_4"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesWithLimit() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?limit=2"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(2, entities.size()); + // Entities returned are based on most recent created time. + assertTrue("Entities with id_1 and id_4 should have been present " + + "in response based on entity created time.", + entities.contains(newEntity("app", "id_1")) && + entities.contains(newEntity("app", "id_4"))); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app?limit=3"); + resp = getResponse(client, uri); + entities = resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + // Even though 2 entities out of 4 have same created time, one entity + // is left out due to limit + assertEquals(3, entities.size()); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesBasedOnCreatedTime() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?createdTimeStart=1425016502030&" + + "createdTimeEnd=1425016502060"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_4 should have been present in response.", + entities.contains(newEntity("app", "id_4"))); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app?createdTimeEnd=1425016502010"); + resp = getResponse(client, uri); + entities = resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(3, entities.size()); + assertFalse("Entity with id_4 should not have been present in response.", + entities.contains(newEntity("app", "id_4"))); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app?createdTimeStart=1425016502010"); + resp = getResponse(client, uri); + entities = resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_4 should have been present in response.", + entities.contains(newEntity("app", "id_4"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesBasedOnModifiedTime() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?modifiedTimeStart=1425016502090" + + "&modifiedTimeEnd=1425016503020"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(2, entities.size()); + assertTrue("Entities with id_1 and id_4 should have been" + + " present in response.", + entities.contains(newEntity("app", "id_1")) && + entities.contains(newEntity("app", "id_4"))); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app?modifiedTimeEnd=1425016502090"); + resp = getResponse(client, uri); + entities = resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(2, entities.size()); + assertTrue("Entities with id_2 and id_3 should have been " + + "present in response.", + entities.contains(newEntity("app", "id_2")) && + entities.contains(newEntity("app", "id_3"))); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app?modifiedTimeStart=1425016503005"); + resp = getResponse(client, uri); + entities = resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_4 should have been present in response.", + entities.contains(newEntity("app", "id_4"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesByRelations() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?relatesto=flow:flow1"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_1 should have been present in response.", + entities.contains(newEntity("app", "id_1"))); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app?isrelatedto=type1:tid1_2,type2:" + + "tid2_1%60"); + resp = getResponse(client, uri); + entities = resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_1 should have been present in response.", + entities.contains(newEntity("app", "id_1"))); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app?isrelatedto=type1:tid1_1:tid1_2" + + ",type2:tid2_1%60"); + resp = getResponse(client, uri); + entities = resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_1 should have been present in response.", + entities.contains(newEntity("app", "id_1"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesByConfigFilters() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?conffilters=config_1:123," + + "config_3:abc"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(2, entities.size()); + assertTrue("Entities with id_1 and id_3 should have been present" + + " in response.", + entities.contains(newEntity("app", "id_1")) && + entities.contains(newEntity("app", "id_3"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesByInfoFilters() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?infofilters=info2:3.5"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_3 should have been present in response.", + entities.contains(newEntity("app", "id_3"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesByMetricFilters() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?metricfilters=metric3"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(2, entities.size()); + assertTrue("Entities with id_1 and id_2 should have been present" + + " in response.", + entities.contains(newEntity("app", "id_1")) && + entities.contains(newEntity("app", "id_2"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesByEventFilters() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?eventfilters=event_2,event_4"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(1, entities.size()); + assertTrue("Entity with id_3 should have been present in response.", + entities.contains(newEntity("app", "id_3"))); + } finally { + client.destroy(); + } + } + + @Test + public void testGetEntitiesNoMatch() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?metricfilters=metric7&" + + "isrelatedto=type1:tid1_1;tid1_2,type2:tid2_1%60&relatesto=" + + "flow:flow1&eventfilters=event_2,event_4&infofilters=info2:3.5" + + "&createdTimeStart=1425016502030&createdTimeEnd=1425016502060"); + ClientResponse resp = getResponse(client, uri); + Set entities = + resp.getEntity(new GenericType>(){}); + assertEquals(MediaType.APPLICATION_JSON_TYPE, resp.getType()); + assertNotNull(entities); + assertEquals(0, entities.size()); + } finally { + client.destroy(); + } + } + + @Test + public void testInvalidValuesHandling() throws Exception { + Client client = createClient(); + try { + URI uri = URI.create("http://localhost:" + serverPort + "/ws/v2/" + + "timeline/entities/cluster1/app1/app?flowRunId=a23b"); + verifyHttpResponse(client, uri, Status.BAD_REQUEST); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entity/cluster1/app1/app/id_1?flowRunId=2ab15"); + verifyHttpResponse(client, uri, Status.BAD_REQUEST); + + uri = URI.create("http://localhost:" + serverPort + "/ws/v2/timeline/" + + "entities/cluster1/app1/app/?limit=#$561av"); + verifyHttpResponse(client, uri, Status.BAD_REQUEST); } finally { client.destroy(); }