/** * 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.falcon.resource.metadata; import com.tinkerpop.blueprints.Direction; import com.tinkerpop.blueprints.Edge; import com.tinkerpop.blueprints.GraphQuery; import com.tinkerpop.blueprints.Vertex; import com.tinkerpop.blueprints.util.io.graphson.GraphSONMode; import com.tinkerpop.blueprints.util.io.graphson.GraphSONUtility; import org.apache.commons.lang3.StringUtils; import org.apache.falcon.FalconWebException; import org.apache.falcon.entity.EntityUtil; import org.apache.falcon.job.ReplicationJobCountersList; import org.apache.falcon.metadata.RelationshipLabel; import org.apache.falcon.metadata.RelationshipProperty; import org.apache.falcon.metadata.RelationshipType; import org.codehaus.jettison.json.JSONArray; import org.codehaus.jettison.json.JSONException; import org.codehaus.jettison.json.JSONObject; 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.core.MediaType; import javax.ws.rs.core.Response; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.Iterator; import java.util.List; /** * Jersey Resource for metadata operations. */ @Path("metadata/discovery") public class MetadataDiscoveryResource extends AbstractMetadataResource { /** * Get list of dimensions for the given dimension-type. * <p/> * GET http://host/metadata/discovery/dimension-type/list * @param clusterName <optional query param> Show dimensions related to this cluster. * @param dimensionType Valid dimension types are cluster_entity,feed_entity, process_entity, user, colo, tags, * groups, pipelines * @return List of dimensions that match requested type [and cluster]. */ @GET @Path("/{type}/list") @Produces({MediaType.APPLICATION_JSON}) public Response listDimensionValues(@PathParam("type") String dimensionType, @QueryParam("cluster") final String clusterName) { RelationshipType relationshipType = validateAndParseDimensionType(dimensionType.toUpperCase()); GraphQuery query = getGraph().query(); JSONArray dimensionValues = new JSONArray(); if (StringUtils.isEmpty(clusterName)) { query = query.has(RelationshipProperty.TYPE.getName(), relationshipType.getName()); dimensionValues = getDimensionsFromVertices(dimensionValues, query.vertices().iterator()); } else { // Get clusterVertex, get adjacent vertices of clusterVertex that match dimension type. Vertex clusterVertex = getVertex(clusterName, RelationshipType.CLUSTER_ENTITY.getName()); if (clusterVertex != null) { dimensionValues = getDimensionsFromClusterVertex(dimensionValues, clusterVertex, relationshipType); } // else, no cluster found. Return empty results } try { JSONObject response = new JSONObject(); response.put(RESULTS, dimensionValues); response.put(TOTAL_SIZE, dimensionValues.length()); return Response.ok(response).build(); } catch (JSONException e) { throw FalconWebException.newAPIException(e, Response.Status.INTERNAL_SERVER_ERROR); } } /** * Get list of dimensions for the replication metrics. * <p/> * GET http://host/metadata/discovery/replication-metrics/list * @param schedEntityType Type of the schedulable entity * @param schedEntityName Name of the schedulable entity. * @param numResults limit the number of metrics to return sorted in ascending order. * @return List of dimensions that match requested type [and cluster]. */ @GET @Path("/{name}/replication-metrics/list") @Produces({MediaType.APPLICATION_JSON}) public Response listReplicationMetricsDimensionValues(@PathParam("name") final String schedEntityName, @QueryParam("type") final String schedEntityType, @QueryParam("numResults") Integer numResults) { JSONArray dimensionValues = new JSONArray(); int resultsPerQuery = numResults == null ? 10 : numResults; if (StringUtils.isNotBlank(schedEntityName)) { try { EntityUtil.getEntity(schedEntityType, schedEntityName); } catch (Throwable e) { throw FalconWebException.newAPIException(e, Response.Status.BAD_REQUEST); } dimensionValues = getReplicationEntityDimensionValues(schedEntityName, resultsPerQuery); } try { JSONObject response = new JSONObject(); response.put(RESULTS, dimensionValues); response.put(TOTAL_SIZE, dimensionValues.length()); return Response.ok(response).build(); } catch (JSONException e) { throw FalconWebException.newAPIException(e, Response.Status.INTERNAL_SERVER_ERROR); } } public JSONArray getReplicationEntityDimensionValues(final String schedEntityName, final int resultsPerQuery) { // Get schedule entity Vertex, get adjacent vertices of schedule entity Vertex that match dimension type. Vertex schedEntityVertex = getVertexByName(schedEntityName); if (schedEntityVertex == null) { return new JSONArray(); } try { Iterator<Edge> inEdges = schedEntityVertex.query().direction(Direction.IN).edges().iterator(); return getAdjacentVerticesForVertexMetrics(inEdges, Direction.OUT, resultsPerQuery); } catch (JSONException e) { throw FalconWebException.newAPIException(e, Response.Status.INTERNAL_SERVER_ERROR); } } /** * Get relations of a dimension identified by type and name. * * GET http://host/metadata/discovery/dimension-type/dimension-name/relations * @param dimensionName Name of the dimension. * @param dimensionType Valid dimension types are cluster_entity,feed_entity, process_entity, user, colo, tags, * groups, pipelines * @return Get all relations of a specific dimension. */ @GET @Path("/{type}/{name}/relations") @Produces({MediaType.APPLICATION_JSON}) public Response getDimensionRelations(@PathParam("type") String dimensionType, @PathParam("name") String dimensionName) { RelationshipType relationshipType = validateAndParseDimensionType(dimensionType.toUpperCase()); validateDimensionName(dimensionName); Vertex dimensionVertex = getVertex(dimensionName, relationshipType.getName()); if (dimensionVertex == null) { return Response.ok(new JSONObject()).build(); } JSONObject vertexProperties; try { vertexProperties = GraphSONUtility.jsonFromElement( dimensionVertex, getVertexIndexedKeys(), GraphSONMode.NORMAL); // over-write the type - fix this kludge vertexProperties.put(RelationshipProperty.TYPE.getName(), relationshipType.toString()); Iterator<Edge> inEdges = dimensionVertex.query().direction(Direction.IN).edges().iterator(); vertexProperties.put("inVertices", getAdjacentVerticesJson(inEdges, Direction.OUT)); Iterator<Edge> outEdges = dimensionVertex.query().direction(Direction.OUT).edges().iterator(); vertexProperties.put("outVertices", getAdjacentVerticesJson(outEdges, Direction.IN)); } catch (JSONException e) { throw FalconWebException.newAPIException(e, Response.Status.INTERNAL_SERVER_ERROR); } return Response.ok(vertexProperties).build(); } private JSONArray getAdjacentVerticesJson(Iterator<Edge> edges, Direction direction) throws JSONException { JSONArray adjVertices = new JSONArray(); while (edges.hasNext()) { Edge edge = edges.next(); Vertex vertex = edge.getVertex(direction); JSONObject vertexObject = new JSONObject(); vertexObject.put(RelationshipProperty.NAME.getName(), vertex.getProperty(RelationshipProperty.NAME.getName())); vertexObject.put(RelationshipProperty.TYPE.getName(), getVertexRelationshipType(vertex)); vertexObject.put("label", edge.getLabel()); adjVertices.put(vertexObject); } return adjVertices; } private JSONArray getAdjacentVerticesForVertexMetrics(Iterator<Edge> edges, Direction direction, int resultsPerQuery) throws JSONException { JSONArray adjVertices = new JSONArray(); Iterator<Edge> sortedEdge = sortEdgesById(edges, direction); while (sortedEdge.hasNext() && resultsPerQuery!=0) { Edge edge = sortedEdge.next(); Vertex vertex = edge.getVertex(direction); if (vertex.getProperty(ReplicationJobCountersList.BYTESCOPIED.getName()) != null) { JSONObject vertexObject = new JSONObject(); vertexObject.put(RelationshipProperty.NAME.getName(), vertex.getProperty(RelationshipProperty.NAME.getName())); vertexObject.put(ReplicationJobCountersList.TIMETAKEN.getName(), vertex.getProperty(ReplicationJobCountersList.TIMETAKEN.getName())); vertexObject.put(ReplicationJobCountersList.BYTESCOPIED.getName(), vertex.getProperty(ReplicationJobCountersList.BYTESCOPIED.getName())); vertexObject.put(ReplicationJobCountersList.COPY.getName(), vertex.getProperty(ReplicationJobCountersList.COPY.getName())); adjVertices.put(vertexObject); resultsPerQuery--; } } return adjVertices; } Iterator<Edge> sortEdgesById(Iterator<Edge> edges, final Direction direction) { List<Edge> edgeList = new ArrayList<Edge>(); while(edges.hasNext()) { Edge e = edges.next(); edgeList.add(e); } Collections.sort(edgeList, new Comparator<Edge>() { @Override public int compare(Edge e1, Edge e2) { long l1 = (long)e1.getVertex(direction).getId(); long l2 = (long)e2.getVertex(direction).getId(); return l1 > l2 ? -1 : 1; } }); return edgeList.iterator(); } private String getVertexRelationshipType(Vertex vertex) { String type = vertex.getProperty(RelationshipProperty.TYPE.getName()); return RelationshipType.fromString(type).toString(); } private Vertex getVertexByName(String name) { Iterator<Vertex> vertexIterator = getGraph().query() .has(RelationshipProperty.NAME.getName(), name) .vertices().iterator(); return vertexIterator.hasNext() ? vertexIterator.next() : null; } private Vertex getVertex(String name, String type) { Iterator<Vertex> vertexIterator = getGraph().query() .has(RelationshipProperty.TYPE.getName(), type) .has(RelationshipProperty.NAME.getName(), name) .vertices().iterator(); return vertexIterator.hasNext() ? vertexIterator.next() : null; } private JSONArray getDimensionsFromClusterVertex(JSONArray dimensionValues, Vertex clusterVertex, RelationshipType relationshipType) { switch (relationshipType) { case FEED_ENTITY: return getDimensionsFromEdges(dimensionValues, Direction.OUT, clusterVertex.getEdges( Direction.IN, RelationshipLabel.FEED_CLUSTER_EDGE.getName()).iterator(), relationshipType); case PROCESS_ENTITY: return getDimensionsFromEdges(dimensionValues, Direction.OUT, clusterVertex.getEdges( Direction.IN, RelationshipLabel.PROCESS_CLUSTER_EDGE.getName()).iterator(), relationshipType); case COLO: return getDimensionsFromEdges(dimensionValues, Direction.IN, clusterVertex.getEdges( Direction.OUT, RelationshipLabel.CLUSTER_COLO.getName()).iterator(), relationshipType); case CLUSTER_ENTITY: return getDimensionFromVertex(dimensionValues, clusterVertex); default: return dimensionValues; } } private JSONArray getDimensionsFromVertices(JSONArray dimensionValues, Iterator<Vertex> vertexIterator) { while (vertexIterator.hasNext()) { dimensionValues = getDimensionFromVertex(dimensionValues, vertexIterator.next()); } return dimensionValues; } private JSONArray getDimensionsFromEdges(JSONArray dimensionValues, Direction direction, Iterator<Edge> edgeIterator, RelationshipType relationshipType) { while(edgeIterator.hasNext()) { Vertex vertex = edgeIterator.next().getVertex(direction); if (vertex.getProperty(RelationshipProperty.TYPE.getName()) .equals(relationshipType.getName())) { dimensionValues = getDimensionFromVertex(dimensionValues, vertex); } } return dimensionValues; } private JSONArray getDimensionFromVertex(JSONArray dimensionValues, Vertex vertex) { return dimensionValues.put(vertex.getProperty(RelationshipProperty.NAME.getName())); } private RelationshipType validateAndParseDimensionType(String type) { if (StringUtils.isEmpty(type) || type.contains("INSTANCE")) { throw FalconWebException.newMetadataResourceException( "Invalid Dimension type : " + type, Response.Status.BAD_REQUEST); } try { return RelationshipType.valueOf(type); } catch (IllegalArgumentException iae) { throw FalconWebException.newMetadataResourceException( "Invalid Dimension type : " + type, Response.Status.BAD_REQUEST); } } private void validateDimensionName(String name) { if (StringUtils.isEmpty(name)) { throw FalconWebException.newMetadataResourceException( "Dimension name cannot be empty for Relations API", Response.Status.BAD_REQUEST); } } }