/*************************************************************************
* Copyright 2009-2016 Eucalyptus Systems, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3 of the License.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*
* Please contact Eucalyptus Systems, Inc., 6755 Hollister Ave., Goleta
* CA 93117, USA or visit http://www.eucalyptus.com/licenses/ if you need
* additional information or have any questions.
************************************************************************/
package com.eucalyptus.cloudwatch.service;
import com.eucalyptus.auth.AuthContextSupplier;
import static com.eucalyptus.util.RestrictedTypes.getIamActionByMessageType;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.cloudwatch.common.CloudWatch;
import com.eucalyptus.cloudwatch.common.config.CloudWatchConfigProperties;
import com.eucalyptus.cloudwatch.common.internal.domain.InvalidTokenException;
import com.eucalyptus.cloudwatch.common.internal.domain.listmetrics.ListMetric;
import com.eucalyptus.cloudwatch.common.internal.domain.listmetrics.ListMetricManager;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricManager;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricStatistics;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricUtils;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.Units;
import com.eucalyptus.cloudwatch.common.msgs.Datapoint;
import com.eucalyptus.cloudwatch.common.msgs.Datapoints;
import com.eucalyptus.cloudwatch.common.msgs.GetMetricStatisticsResponseType;
import com.eucalyptus.cloudwatch.common.msgs.GetMetricStatisticsType;
import com.eucalyptus.cloudwatch.common.msgs.ListMetricsResponseType;
import com.eucalyptus.cloudwatch.common.msgs.ListMetricsResult;
import com.eucalyptus.cloudwatch.common.msgs.ListMetricsType;
import com.eucalyptus.cloudwatch.common.msgs.Metric;
import com.eucalyptus.cloudwatch.common.msgs.MetricDatum;
import com.eucalyptus.cloudwatch.common.msgs.Metrics;
import com.eucalyptus.cloudwatch.common.msgs.PutMetricDataResponseType;
import com.eucalyptus.cloudwatch.common.msgs.PutMetricDataType;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.MetricEntity.MetricType;
import com.eucalyptus.cloudwatch.common.msgs.Statistics;
import com.eucalyptus.cloudwatch.common.policy.CloudWatchPolicySpec;
import com.eucalyptus.cloudwatch.service.queue.metricdata.MetricDataQueue;
import com.eucalyptus.component.Faults;
import com.eucalyptus.component.annotation.ComponentNamed;
import com.eucalyptus.context.Context;
import com.eucalyptus.util.async.AsyncExceptions;
import com.google.common.base.Optional;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import org.apache.log4j.Logger;
import com.eucalyptus.auth.Permissions;
import com.eucalyptus.cloudwatch.common.CloudWatchBackend;
import com.eucalyptus.cloudwatch.common.backend.msgs.CloudWatchBackendMessage;
import com.eucalyptus.cloudwatch.common.msgs.CloudWatchMessage;
import com.eucalyptus.component.Topology;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.context.ServiceDispatchException;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.async.AsyncRequests;
import com.eucalyptus.util.async.FailedRequestException;
import com.eucalyptus.ws.EucalyptusRemoteFault;
import com.eucalyptus.ws.EucalyptusWebServiceException;
import com.eucalyptus.ws.Role;
import com.google.common.base.Objects;
import edu.ucsb.eucalyptus.msgs.BaseMessage;
import edu.ucsb.eucalyptus.msgs.BaseMessages;
/**
*
*/
@ComponentNamed
public class CloudWatchService {
private static final Logger LOG = Logger.getLogger(CloudWatchService.class);
public PutMetricDataResponseType putMetricData(PutMetricDataType request)
throws CloudWatchException {
PutMetricDataResponseType reply = request.getReply();
long before = System.currentTimeMillis();
final Context ctx = Contexts.lookup();
try {
// IAM Action Check
checkActionPermission(CloudWatchPolicySpec.CLOUDWATCH_PUTMETRICDATA, ctx);
if (CloudWatchConfigProperties.isDisabledCloudWatchService()) {
faultDisableCloudWatchServiceIfNecessary();
throw new ServiceDisabledException("Service Disabled");
}
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String namespace = CloudWatchServiceFieldValidator.validateNamespace(request.getNamespace(), true);
MetricType metricType = CloudWatchServiceFieldValidator.getMetricTypeFromNamespace(namespace);
final Boolean privileged = Contexts.lookup().isPrivileged();
if (metricType == MetricType.System && !privileged) {
throw new InvalidParameterValueException("The value AWS/ for parameter Namespace is invalid.");
}
final List<MetricDatum> metricData = CloudWatchServiceFieldValidator.validateMetricData(request.getMetricData(), metricType);
LOG.trace("Namespace=" + namespace);
LOG.trace("metricData="+metricData);
MetricDataQueue.getInstance().insertMetricData(ownerFullName.getAccountNumber(), namespace, metricData, metricType);
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public ListMetricsResponseType listMetrics(ListMetricsType request)
throws CloudWatchException {
ListMetricsResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
// IAM Action Check
checkActionPermission(CloudWatchPolicySpec.CLOUDWATCH_LISTMETRICS, ctx);
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String namespace = CloudWatchServiceFieldValidator.validateNamespace(request.getNamespace(), false);
final String metricName = CloudWatchServiceFieldValidator.validateMetricName(request.getMetricName(),
false);
final Map<String, String> dimensionMap = TransformationFunctions.DimensionFiltersToMap.INSTANCE
.apply(CloudWatchServiceFieldValidator.validateDimensionFilters(request.getDimensions()));
// take all stats updated after two weeks ago
final Date after = new Date(System.currentTimeMillis() - 2 * 7 * 24 * 60
* 60 * 1000L);
final Date before = null; // no bound on time before stats are updated
// (though maybe 'now')
final Integer maxRecords = 500; // per the API docs
final String nextToken = request.getNextToken();
final List<ListMetric> results;
try {
results = ListMetricManager.listMetrics(
ownerFullName.getAccountNumber(), metricName, namespace,
dimensionMap, after, before, maxRecords, nextToken);
} catch (InvalidTokenException e) {
// not sure why, but this is the message AWS sends (different from the alarm case, different exception too)
throw new InvalidParameterValueException("Invalid nextToken");
}
final Metrics metrics = new Metrics();
metrics.setMember(Lists.newArrayList(Collections2
.<ListMetric, Metric>transform(results,
TransformationFunctions.ListMetricToMetric.INSTANCE)));
final ListMetricsResult listMetricsResult = new ListMetricsResult();
listMetricsResult.setMetrics(metrics);
if (maxRecords != null && results.size() == maxRecords) {
listMetricsResult.setNextToken(results.get(results.size() - 1)
.getNaturalId());
}
reply.setListMetricsResult(listMetricsResult);
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public GetMetricStatisticsResponseType getMetricStatistics(
GetMetricStatisticsType request) throws CloudWatchException {
GetMetricStatisticsResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
// IAM Action Check
checkActionPermission(CloudWatchPolicySpec.CLOUDWATCH_GETMETRICSTATISTICS, ctx);
// TODO: parse statistics separately()?
final OwnerFullName ownerFullName = ctx.getUserFullName();
Statistics statistics = CloudWatchServiceFieldValidator.validateStatistics(request.getStatistics());
final String namespace = CloudWatchServiceFieldValidator.validateNamespace(request.getNamespace(), true);
final String metricName = CloudWatchServiceFieldValidator.validateMetricName(request.getMetricName(),
true);
final Date startTime = MetricUtils.stripSeconds(CloudWatchServiceFieldValidator.validateStartTime(request.getStartTime(), true));
final Date endTime = MetricUtils.stripSeconds(CloudWatchServiceFieldValidator.validateEndTime(request.getEndTime(), true));
final Integer period = CloudWatchServiceFieldValidator.validatePeriod(request.getPeriod(), true);
CloudWatchServiceFieldValidator.validateDateOrder(startTime, endTime, "StartTime", "EndTime", true,
true);
CloudWatchServiceFieldValidator.validateNotTooManyDataPoints(startTime, endTime, period, 1440L);
// TODO: null units here does not mean Units.NONE but basically a
// wildcard.
// Consider this case.
final Units units = CloudWatchServiceFieldValidator.validateUnits(request.getUnit(), false);
final Map<String, String> dimensionMap = TransformationFunctions.DimensionsToMap.INSTANCE
.apply(CloudWatchServiceFieldValidator.validateDimensions(request.getDimensions()));
Collection<MetricStatistics> metrics;
metrics = MetricManager.getMetricStatistics(new MetricManager.GetMetricStatisticsParams(
ownerFullName.getAccountNumber(), metricName, namespace,
dimensionMap, CloudWatchServiceFieldValidator.getMetricTypeFromNamespace(namespace), units,
startTime, endTime, period));
reply.getGetMetricStatisticsResult().setLabel(metricName);
ArrayList<Datapoint> datapoints = CloudWatchServiceFieldValidator.convertMetricStatisticsToDatapoints(
statistics, metrics);
if (datapoints.size() > 0) {
Datapoints datapointsReply = new Datapoints();
datapointsReply.setMember(datapoints);
reply.getGetMetricStatisticsResult().setDatapoints(datapointsReply);
}
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public CloudWatchMessage dispatchAction( final CloudWatchMessage request ) throws EucalyptusCloudException {
final AuthContextSupplier user = Contexts.lookup( ).getAuthContext( );
if ( !Permissions.perhapsAuthorized(CloudWatchPolicySpec.VENDOR_CLOUDWATCH, getIamActionByMessageType( request ), user ) ) {
throw new CloudWatchAuthorizationException( "UnauthorizedOperation", "You are not authorized to perform this operation." );
}
try {
final CloudWatchBackendMessage backendRequest = (CloudWatchBackendMessage) BaseMessages.deepCopy( request, getBackendMessageClass( request ) );
final BaseMessage backendResponse = send( backendRequest );
final CloudWatchMessage response = (CloudWatchMessage) BaseMessages.deepCopy( backendResponse, request.getReply().getClass() );
response.setCorrelationId( request.getCorrelationId( ) );
return response;
} catch ( Exception e ) {
handleRemoteException( e );
Exceptions.findAndRethrow( e, EucalyptusWebServiceException.class, EucalyptusCloudException.class );
throw new EucalyptusCloudException( e );
}
}
private static Class getBackendMessageClass( final BaseMessage request ) throws ClassNotFoundException {
return Class.forName( request.getClass( ).getName( ).replace( ".common.msgs.", ".common.backend.msgs." ) );
}
private static BaseMessage send( final BaseMessage request ) throws Exception {
try {
return AsyncRequests.sendSyncWithCurrentIdentity( Topology.lookup( CloudWatchBackend.class ), request );
} catch ( NoSuchElementException e ) {
throw new CloudWatchUnavailableException( "Service Unavailable" );
} catch ( final FailedRequestException e ) {
if ( request.getReply( ).getClass( ).isInstance( e.getRequest( ) ) ) {
return e.getRequest( );
}
throw e.getRequest( ) == null ?
e :
new CloudWatchException( "InternalError", Role.Receiver, "Internal error " + e.getRequest().getClass().getSimpleName() + ":false" );
}
}
@SuppressWarnings( "ThrowableResultOfMethodCallIgnored" )
private void handleRemoteException( final Exception e ) throws EucalyptusCloudException {
final Optional<AsyncExceptions.AsyncWebServiceError> serviceErrorOption = AsyncExceptions.asWebServiceError( e );
if ( serviceErrorOption.isPresent( ) ) {
final AsyncExceptions.AsyncWebServiceError serviceError = serviceErrorOption.get( );
final String code = serviceError.getCode( );
final String message = serviceError.getMessage( );
switch( serviceError.getHttpErrorCode( ) ) {
case 400:
throw new CloudWatchClientException( code, message );
case 403:
throw new CloudWatchAuthorizationException( code, message );
case 404:
throw new CloudWatchNotFoundException( code, message );
case 503:
throw new CloudWatchUnavailableException( message );
default:
throw new CloudWatchException( code, Role.Receiver, message );
}
}
}
private static final int DISABLED_SERVICE_FAULT_ID = 1500;
private boolean alreadyFaulted = false;
private void faultDisableCloudWatchServiceIfNecessary() {
// TODO Auto-generated method stub
if (!alreadyFaulted) {
Faults.forComponent(CloudWatch.class).havingId(DISABLED_SERVICE_FAULT_ID).withVar("component", "cloudwatch").log();
alreadyFaulted = true;
}
}
private void checkActionPermission(final String actionType, final Context ctx)
throws EucalyptusCloudException {
if (!Permissions.isAuthorized(CloudWatchPolicySpec.VENDOR_CLOUDWATCH, actionType, "",
ctx.getAccount(), actionType, ctx.getAuthContext())) {
throw new EucalyptusCloudException("User does not have permission");
}
}
private static void handleException(final Exception e)
throws CloudWatchException {
final CloudWatchException cause = Exceptions.findCause(e,
CloudWatchException.class);
if (cause != null) {
throw cause;
}
final InternalFailureException exception = new InternalFailureException(
String.valueOf(e.getMessage()));
if (Contexts.lookup().hasAdministrativePrivileges()) {
exception.initCause(e);
}
throw exception;
}
}