| /* |
| * 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.usergrid.rest; |
| |
| |
| import com.fasterxml.jackson.databind.node.JsonNodeFactory; |
| import com.fasterxml.jackson.databind.node.ObjectNode; |
| import com.fasterxml.jackson.jaxrs.json.annotation.JSONP; |
| import com.google.common.collect.BiMap; |
| import com.google.inject.Injector; |
| import com.yammer.metrics.Metrics; |
| import com.yammer.metrics.core.*; |
| import com.yammer.metrics.stats.Snapshot; |
| import org.apache.commons.lang.StringUtils; |
| import org.apache.shiro.authz.UnauthorizedException; |
| import org.apache.usergrid.corepersistence.asyncevents.AsyncEventService; |
| import org.apache.usergrid.persistence.core.util.Health; |
| import org.apache.usergrid.persistence.index.query.Identifier; |
| import org.apache.usergrid.rest.applications.ApplicationResource; |
| import org.apache.usergrid.rest.exceptions.NoOpException; |
| import org.apache.usergrid.rest.organizations.OrganizationResource; |
| import org.apache.usergrid.rest.security.annotations.RequireSystemAccess; |
| import org.apache.usergrid.system.UsergridSystemMonitor; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| import org.springframework.beans.factory.annotation.Autowired; |
| import org.springframework.context.annotation.Scope; |
| import org.springframework.stereotype.Component; |
| |
| import javax.ws.rs.*; |
| import javax.ws.rs.core.Context; |
| import javax.ws.rs.core.MediaType; |
| import javax.ws.rs.core.Response; |
| import javax.ws.rs.core.Response.ResponseBuilder; |
| import javax.ws.rs.core.UriInfo; |
| import java.io.IOException; |
| import java.net.URI; |
| import java.net.URISyntaxException; |
| import java.util.Map; |
| import java.util.SortedMap; |
| import java.util.UUID; |
| |
| |
| /** @author ed@anuff.com */ |
| @Path("/") |
| @Component |
| @Scope("singleton") |
| @Produces({ |
| MediaType.APPLICATION_JSON, "application/javascript", "application/x-javascript", "text/ecmascript", |
| "application/ecmascript", "text/jscript" |
| }) |
| public class RootResource extends AbstractContextResource implements MetricProcessor<RootResource.MetricContext> { |
| |
| static final class MetricContext { |
| final boolean showFullSamples; |
| final ObjectNode objectNode; |
| |
| |
| MetricContext( ObjectNode objectNode, boolean showFullSamples ) { |
| this.objectNode = objectNode; |
| this.showFullSamples = showFullSamples; |
| } |
| } |
| |
| |
| private static final Logger logger = LoggerFactory.getLogger( RootResource.class ); |
| |
| long started = System.currentTimeMillis(); |
| |
| @Autowired |
| private UsergridSystemMonitor usergridSystemMonitor; |
| |
| @Autowired |
| private Injector injector; |
| |
| |
| public RootResource() { |
| } |
| |
| |
| @RequireSystemAccess |
| @GET |
| @Path("applications") |
| @JSONP |
| @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) |
| public ApiResponse getAllApplications( |
| @Context UriInfo ui, |
| @QueryParam("deleted") @DefaultValue("false") Boolean deleted, |
| @QueryParam("callback") @DefaultValue("callback") String callback ) throws URISyntaxException { |
| |
| if (logger.isTraceEnabled()) { |
| logger.trace("RootResource.getData"); |
| } |
| |
| ApiResponse response = createApiResponse(); |
| response.setAction( "get applications" ); |
| |
| Map<String, UUID> applications = null; |
| try { |
| if ( deleted ) { |
| applications = emf.getDeletedApplications(); |
| } else { |
| applications = emf.getApplications(); |
| } |
| response.setSuccess(); |
| response.setApplications( applications ); |
| } |
| catch ( Exception e ) { |
| logger.info( "Unable to retrieve applications", e ); |
| response.setError( "Unable to retrieve applications" ); |
| } |
| |
| return response; |
| } |
| |
| |
| @RequireSystemAccess |
| @GET |
| @Path("apps") |
| @JSONP |
| @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) |
| public ApiResponse getAllApplications2( @Context UriInfo ui, |
| @QueryParam("callback") @DefaultValue("callback") String callback ) |
| throws URISyntaxException { |
| return getAllApplications( ui, false, callback ); |
| } |
| |
| |
| @GET |
| public Response getRoot( @Context UriInfo ui ) throws URISyntaxException { |
| |
| String redirect_root = properties.getRedirectRoot(); |
| if ( StringUtils.isNotBlank( redirect_root ) ) { |
| ResponseBuilder response = Response.temporaryRedirect( new URI( redirect_root ) ); |
| return response.build(); |
| } |
| else { |
| ResponseBuilder response = Response.temporaryRedirect( new URI( "/status" ) ); |
| return response.build(); |
| } |
| } |
| |
| |
| /** |
| * Return status of this Usergrid instance in JSON format. |
| * |
| * By Default this end-point will ignore errors but if you call it with ignore_status=false |
| * then it will return HTTP 500 if either the Entity store or the Index for the management |
| * application are in a bad state. |
| * |
| * @param ignoreError Ignore any errors and return status no matter what. |
| */ |
| @GET |
| @Path("status") |
| @JSONP |
| @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) |
| public ApiResponse getStatus( |
| @QueryParam("ignore_error") @DefaultValue("true") Boolean ignoreError, |
| @QueryParam("callback") @DefaultValue("callback") String callback ) { |
| |
| ApiResponse response = createApiResponse(); |
| |
| if ( !ignoreError ) { |
| |
| if ( !emf.getEntityStoreHealth().equals( Health.GREEN )) { |
| throw new RuntimeException("Error connecting to datastore"); |
| } |
| |
| |
| if ( emf.getIndexHealth().equals( Health.RED) ) { |
| throw new RuntimeException("Management app index is status RED"); |
| } |
| } |
| |
| ObjectNode node = JsonNodeFactory.instance.objectNode(); |
| node.put( "started", started ); |
| node.put( "uptime", System.currentTimeMillis() - started ); |
| node.put( "version", usergridSystemMonitor.getBuildNumber()); |
| |
| // Hector status, for backwards compatibility |
| node.put("cassandraAvailable", usergridSystemMonitor.getIsCassandraAlive()); |
| |
| // Core Persistence Collections module status |
| node.put( "cassandraStatus", emf.getEntityStoreHealth().toString() ); |
| |
| // Core Persistence Query Index module status for Management App Index |
| node.put( "managementAppIndexStatus", emf.getIndexHealth().toString() ); |
| |
| |
| dumpMetrics(node); |
| response.setProperty( "status", node ); |
| return response; |
| } |
| |
| |
| @GET |
| @Path("lb-status") |
| @JSONP |
| @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) |
| public Response getLbStatus() { |
| ResponseBuilder response; |
| if ( usergridSystemMonitor.getIsCassandraAlive() ) { |
| response = Response.noContent().status( Response.Status.OK ); |
| } |
| else { |
| response = Response.noContent().status( Response.Status.SERVICE_UNAVAILABLE ); |
| } |
| return response.build(); |
| } |
| |
| @GET |
| @Path("/status/queue") |
| @JSONP |
| @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) |
| public ApiResponse getQueueDepth(){ |
| |
| ApiResponse response = createApiResponse(); |
| AsyncEventService eventService = injector.getInstance(AsyncEventService.class); |
| |
| ObjectNode node = JsonNodeFactory.instance.objectNode(); |
| |
| String provider = "LOCAL"; |
| String queueManagerClass = eventService.getQueueManagerClass(); |
| |
| if(queueManagerClass.contains("SNS") || queueManagerClass.contains("SQS")){ |
| provider = "AWS"; |
| } |
| |
| node.put( "provider", provider ); |
| node.put( "depth", eventService.getQueueDepth() ); |
| |
| response.setProperty( "status", node ); |
| return response; |
| |
| } |
| |
| @GET |
| @Path("/status/heap") |
| @JSONP |
| @Produces({MediaType.APPLICATION_JSON, "application/javascript"}) |
| public ApiResponse getHeapStats(){ |
| |
| ApiResponse response = createApiResponse(); |
| |
| ObjectNode node = JsonNodeFactory.instance.objectNode(); |
| |
| long heapAllocatedSize = Runtime.getRuntime().totalMemory(); |
| long heapMaxSize = Runtime.getRuntime().maxMemory(); |
| long heapFreeSize = Runtime.getRuntime().freeMemory(); |
| long heapUsedSize = heapAllocatedSize - heapFreeSize; |
| |
| node.put( "used", org.apache.usergrid.utils.StringUtils.readableByteSize(heapUsedSize) ); |
| node.put( "free", org.apache.usergrid.utils.StringUtils.readableByteSize(heapFreeSize) ); |
| node.put( "allocated", org.apache.usergrid.utils.StringUtils.readableByteSize(heapAllocatedSize) ); |
| node.put( "max", org.apache.usergrid.utils.StringUtils.readableByteSize(heapMaxSize) ); |
| |
| response.setProperty( "status", node ); |
| return response; |
| |
| } |
| |
| |
| |
| private void dumpMetrics( ObjectNode node ) { |
| MetricsRegistry registry = Metrics.defaultRegistry(); |
| |
| for ( Map.Entry<String, SortedMap<MetricName, Metric>> entry : registry.groupedMetrics().entrySet() ) { |
| |
| ObjectNode meterNode = JsonNodeFactory.instance.objectNode(); |
| |
| for ( Map.Entry<MetricName, Metric> subEntry : entry.getValue().entrySet() ) { |
| ObjectNode metricNode = JsonNodeFactory.instance.objectNode(); |
| |
| try { |
| subEntry.getValue().processWith( this, subEntry.getKey(), new MetricContext( metricNode, true ) ); |
| } |
| catch ( Exception e ) { |
| logger.warn( "Error writing out {}", subEntry.getKey(), e ); |
| } |
| meterNode.put( subEntry.getKey().getName(), metricNode ); |
| } |
| node.put( entry.getKey(), meterNode ); |
| } |
| } |
| |
| |
| @Path("{applicationId: [A-Fa-f0-9]{8}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{4}-[A-Fa-f0-9]{12}}") |
| public ApplicationResource getApplicationById( @PathParam("applicationId") String applicationIdStr ) |
| throws Exception { |
| |
| if ( "options".equalsIgnoreCase( request.getMethod() ) ) { |
| throw new NoOpException(); |
| } |
| |
| UUID applicationId = UUID.fromString( applicationIdStr ); |
| if ( applicationId == null ) { |
| return null; |
| } |
| |
| return appResourceFor( applicationId ); |
| } |
| |
| |
| private ApplicationResource appResourceFor( UUID applicationId ) throws Exception { |
| if ( applicationId.equals( emf.getManagementAppId() ) ) { |
| throw new UnauthorizedException(); |
| } |
| |
| return getSubResource( ApplicationResource.class ).init( applicationId ); |
| } |
| |
| |
| @Path("applications/"+APPLICATION_ID_PATH) |
| public ApplicationResource getApplicationById2( @PathParam("applicationId") String applicationId ) |
| throws Exception { |
| return getApplicationById( applicationId ); |
| } |
| |
| |
| @Path("apps/"+APPLICATION_ID_PATH) |
| public ApplicationResource getApplicationById3( @PathParam("applicationId") String applicationId ) |
| throws Exception { |
| return getApplicationById( applicationId ); |
| } |
| |
| public static final String APPLICATION_ID_PATH = "{applicationId: " + Identifier.UUID_REX + "}"; |
| public static final String ORGANIZATION_ID_PATH = "{organizationId: " + Identifier.UUID_REX + "}"; |
| public static final String USER_ID_PATH = "{userId: " + Identifier.UUID_REX + "}"; |
| public static final String ENTITY_ID_PATH = "{entityId: " + Identifier.UUID_REX + "}"; |
| public static final String EMAIL_PATH = "{email: " + Identifier.EMAIL_REX + "}"; |
| |
| @Path(ORGANIZATION_ID_PATH+"/"+APPLICATION_ID_PATH) |
| public ApplicationResource getApplicationByUuids( @PathParam("organizationId") String organizationIdStr, |
| @PathParam("applicationId") String applicationIdStr ) |
| |
| throws Exception { |
| |
| UUID applicationId = UUID.fromString( applicationIdStr ); |
| UUID organizationId = UUID.fromString( organizationIdStr ); |
| if ( applicationId == null || organizationId == null ) { |
| return null; |
| } |
| BiMap<UUID, String> apps = management.getApplicationsForOrganization( organizationId ); |
| if ( apps.get( applicationId ) == null ) { |
| return null; |
| } |
| return appResourceFor( applicationId ); |
| } |
| |
| |
| private OrganizationResource orgResourceFor( String organizationName ) throws Exception { |
| |
| return getSubResource( OrganizationResource.class ).init( organizationName ); |
| } |
| |
| |
| @Path("{organizationName}") |
| public OrganizationResource getOrganizationByName( @PathParam("organizationName") String organizationName ) |
| throws Exception { |
| |
| if ( "options".equalsIgnoreCase( request.getMethod() ) ) { |
| throw new NoOpException(); |
| } |
| |
| return orgResourceFor( organizationName ); |
| } |
| |
| |
| @Path("organizations/{organizationName}") |
| public OrganizationResource getOrganizationByName2( @PathParam("organizationName") String organizationName ) |
| throws Exception { |
| return getOrganizationByName( organizationName ); |
| } |
| |
| |
| @Path("orgs/{organizationName}") |
| public OrganizationResource getOrganizationByName3( @PathParam("organizationName") String organizationName ) |
| throws Exception { |
| if (logger.isTraceEnabled()) { |
| logger.trace("getOrganizationByName3"); |
| } |
| |
| return getOrganizationByName( organizationName ); |
| } |
| |
| |
| @Path("o/{organizationName}") |
| public OrganizationResource getOrganizationByName4( @PathParam("organizationName") String organizationName ) |
| throws Exception { |
| return getOrganizationByName( organizationName ); |
| } |
| |
| |
| @Override |
| public void processHistogram( MetricName name, Histogram histogram, MetricContext context ) throws Exception { |
| final ObjectNode node = context.objectNode; |
| node.put( "type", "histogram" ); |
| node.put( "count", histogram.count() ); |
| writeSummarizable( histogram, node ); |
| writeSampling( histogram, node ); |
| } |
| |
| |
| @Override |
| public void processCounter( MetricName name, Counter counter, MetricContext context ) throws Exception { |
| final ObjectNode node = context.objectNode; |
| node.put( "type", "counter" ); |
| node.put( "count", counter.count() ); |
| } |
| |
| |
| @Override |
| public void processGauge( MetricName name, Gauge<?> gauge, MetricContext context ) throws Exception { |
| final ObjectNode node = context.objectNode; |
| node.put( "type", "gauge" ); |
| node.put( "vale", "[disabled]" ); |
| } |
| |
| |
| @Override |
| public void processMeter( MetricName name, Metered meter, MetricContext context ) throws Exception { |
| final ObjectNode node = context.objectNode; |
| node.put( "type", "meter" ); |
| node.put( "event_type", meter.eventType() ); |
| writeMeteredFields( meter, node ); |
| } |
| |
| |
| @Override |
| public void processTimer( MetricName name, Timer timer, MetricContext context ) throws Exception { |
| final ObjectNode node = context.objectNode; |
| |
| node.put( "type", "timer" ); |
| // json.writeFieldName("duration"); |
| node.put( "unit", timer.durationUnit().toString().toLowerCase() ); |
| ObjectNode durationNode = JsonNodeFactory.instance.objectNode(); |
| writeSummarizable( timer, durationNode ); |
| writeSampling( timer, durationNode ); |
| node.put( "duration", durationNode ); |
| writeMeteredFields( timer, node ); |
| } |
| |
| |
| private static void writeSummarizable( Summarizable metric, ObjectNode mNode ) throws IOException { |
| mNode.put( "min", metric.min() ); |
| mNode.put( "max", metric.max() ); |
| mNode.put( "mean", metric.mean() ); |
| mNode.put( "std_dev", metric.stdDev() ); |
| } |
| |
| |
| private static void writeSampling( Sampling metric, ObjectNode mNode ) throws IOException { |
| |
| final Snapshot snapshot = metric.getSnapshot(); |
| mNode.put( "median", snapshot.getMedian() ); |
| mNode.put( "p75", snapshot.get75thPercentile() ); |
| mNode.put( "p95", snapshot.get95thPercentile() ); |
| mNode.put( "p98", snapshot.get98thPercentile() ); |
| mNode.put( "p99", snapshot.get99thPercentile() ); |
| mNode.put( "p999", snapshot.get999thPercentile() ); |
| } |
| |
| |
| private static void writeMeteredFields( Metered metered, ObjectNode node ) throws IOException { |
| ObjectNode mNode = JsonNodeFactory.instance.objectNode(); |
| mNode.put( "unit", metered.rateUnit().toString().toLowerCase() ); |
| mNode.put( "count", metered.count() ); |
| mNode.put( "mean", metered.meanRate() ); |
| mNode.put( "m1", metered.oneMinuteRate() ); |
| mNode.put( "m5", metered.fiveMinuteRate() ); |
| mNode.put( "m15", metered.fifteenMinuteRate() ); |
| node.put( "rate", mNode ); |
| } |
| } |