/*************************************************************************
* Copyright 2009-2015 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.backend;
import com.eucalyptus.auth.Permissions;
import com.eucalyptus.auth.principal.OwnerFullName;
import com.eucalyptus.cloudwatch.common.CloudWatchBackend;
import com.eucalyptus.cloudwatch.common.CloudWatchMetadata;
import com.eucalyptus.cloudwatch.common.backend.msgs.AlarmHistoryItem;
import com.eucalyptus.cloudwatch.common.backend.msgs.AlarmHistoryItems;
import com.eucalyptus.cloudwatch.common.backend.msgs.DeleteAlarmsResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DeleteAlarmsType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DescribeAlarmHistoryResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DescribeAlarmHistoryType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DescribeAlarmsForMetricResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DescribeAlarmsForMetricType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DescribeAlarmsResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DescribeAlarmsType;
import com.eucalyptus.cloudwatch.common.backend.msgs.Dimension;
import com.eucalyptus.cloudwatch.common.backend.msgs.DisableAlarmActionsResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.DisableAlarmActionsType;
import com.eucalyptus.cloudwatch.common.backend.msgs.EnableAlarmActionsResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.EnableAlarmActionsType;
import com.eucalyptus.cloudwatch.common.backend.msgs.MetricAlarm;
import com.eucalyptus.cloudwatch.common.backend.msgs.MetricAlarms;
import com.eucalyptus.cloudwatch.common.backend.msgs.PutMetricAlarmResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.PutMetricAlarmType;
import com.eucalyptus.cloudwatch.common.backend.msgs.SetAlarmStateResponseType;
import com.eucalyptus.cloudwatch.common.backend.msgs.SetAlarmStateType;
import com.eucalyptus.cloudwatch.common.config.CloudWatchConfigProperties;
import com.eucalyptus.cloudwatch.common.internal.domain.InvalidTokenException;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmEntity;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmEntity.ComparisonOperator;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmEntity.StateValue;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmEntity.Statistic;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmHistory;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmHistory.HistoryItemType;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmManager;
import com.eucalyptus.cloudwatch.common.internal.domain.alarms.AlarmNotFoundException;
import com.eucalyptus.cloudwatch.common.internal.domain.metricdata.Units;
import com.eucalyptus.cloudwatch.common.policy.CloudWatchPolicySpec;
import com.eucalyptus.cloudwatch.workflow.DBCleanupService;
import com.eucalyptus.cloudwatch.workflow.alarms.AlarmStateEvaluationDispatcher;
import com.eucalyptus.component.Faults;
import com.eucalyptus.component.annotation.ComponentNamed;
import com.eucalyptus.context.Context;
import com.eucalyptus.context.Contexts;
import com.eucalyptus.system.Threads;
import com.eucalyptus.util.EucalyptusCloudException;
import com.eucalyptus.util.Exceptions;
import com.eucalyptus.util.RestrictedTypes;
import com.google.common.base.Predicates;
import com.google.common.collect.Collections2;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import org.apache.log4j.Logger;
import java.util.Collection;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
@ComponentNamed
public class CloudWatchBackendService {
static {
// TODO: make this configurable
ExecutorService fixedThreadPool = Executors.newFixedThreadPool(
5,
Threads.threadFactory( "cloudwatch-alarm-work-pool-%d" ));
ScheduledExecutorService alarmWorkerService = Executors
.newSingleThreadScheduledExecutor( Threads.threadFactory( "cloudwatch-alarm-eval-pool-%d" ) );
alarmWorkerService.scheduleAtFixedRate(new AlarmStateEvaluationDispatcher(
fixedThreadPool), 0, 1, TimeUnit.MINUTES);
ScheduledExecutorService dbCleanupService = Executors
.newSingleThreadScheduledExecutor( Threads.threadFactory( "cloudwatch-db-cleanup-pool-%d" ) );
dbCleanupService.scheduleAtFixedRate(new DBCleanupService(), 1, 24,
TimeUnit.HOURS);
}
private static final Logger LOG = Logger.getLogger(CloudWatchBackendService.class);
public PutMetricAlarmResponseType putMetricAlarm(PutMetricAlarmType request)
throws CloudWatchException {
PutMetricAlarmResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
// IAM Action Check
checkActionPermission(CloudWatchPolicySpec.CLOUDWATCH_PUTMETRICALARM, ctx);
if (CloudWatchConfigProperties.isDisabledCloudWatchService()) {
faultDisableCloudWatchServiceIfNecessary();
throw new ServiceDisabledException("Service Disabled");
}
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ownerFullName.getAccountNumber();
final Boolean actionsEnabled = CloudWatchBackendServiceFieldValidator.validateActionsEnabled(request.getActionsEnabled(), true);
// we do this here as the check is not done at AWS on describe alarms for metric for some reason
Set<String> seenDimensionNames = Sets.newHashSet();
if (request.getDimensions() != null && request.getDimensions().getMember() != null) {
for (Dimension dimension: request.getDimensions().getMember()) {
if (seenDimensionNames.contains(dimension.getName())) {
throw new InvalidParameterValueException("Dimension names must be unique.");
} else {
seenDimensionNames.add(dimension.getName());
}
}
}
final Map<String, String> dimensionMap = TransformationFunctions.DimensionsToMap.INSTANCE
.apply(CloudWatchBackendServiceFieldValidator.validateDimensions(request.getDimensions()));
final Collection<String> alarmActions = CloudWatchBackendServiceFieldValidator.validateActions(
request.getAlarmActions(), dimensionMap, "AlarmActions");
final String alarmDescription = CloudWatchBackendServiceFieldValidator.validateAlarmDescription(
request.getAlarmDescription());
final String alarmName = CloudWatchBackendServiceFieldValidator.validateAlarmName(request.getAlarmName(), true);
final ComparisonOperator comparisonOperator = CloudWatchBackendServiceFieldValidator.validateComparisonOperator(
request.getComparisonOperator(), true);
Integer evaluationPeriods = CloudWatchBackendServiceFieldValidator.validateEvaluationPeriods(
request.getEvaluationPeriods(), true);
final Integer period = CloudWatchBackendServiceFieldValidator.validatePeriod(request.getPeriod(), true);
CloudWatchBackendServiceFieldValidator.validatePeriodAndEvaluationPeriodsNotAcrossDays(period, evaluationPeriods);
final Collection<String> insufficientDataActions = CloudWatchBackendServiceFieldValidator.validateActions(
request.getInsufficientDataActions(), dimensionMap,
"InsufficientDataActions");
final String metricName = CloudWatchBackendServiceFieldValidator.validateMetricName(request.getMetricName(),
true);
final String namespace = CloudWatchBackendServiceFieldValidator.validateNamespace(request.getNamespace(), true);
final Collection<String> okActions = CloudWatchBackendServiceFieldValidator.validateActions(
request.getOkActions(), dimensionMap, "OKActions");
final Statistic statistic = CloudWatchBackendServiceFieldValidator.validateStatistic(request.getStatistic(),
true);
final Double threshold = CloudWatchBackendServiceFieldValidator.validateThreshold(request.getThreshold(), true);
final Units unit = CloudWatchBackendServiceFieldValidator.validateUnits(request.getUnit(), true);
if (AlarmManager.countMetricAlarms(accountId) >= 5000) {
throw new LimitExceededException("The maximum limit of 5000 alarms would be exceeded.");
}
AlarmManager.putMetricAlarm(accountId, actionsEnabled, alarmActions,
alarmDescription, alarmName, comparisonOperator, dimensionMap,
evaluationPeriods, insufficientDataActions, metricName,
CloudWatchBackendServiceFieldValidator.getMetricTypeFromNamespace(namespace), namespace, okActions, period,
statistic, threshold, unit);
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public DisableAlarmActionsResponseType disableAlarmActions(
final DisableAlarmActionsType request
) throws EucalyptusCloudException {
final DisableAlarmActionsResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
if (CloudWatchConfigProperties.isDisabledCloudWatchService()) {
faultDisableCloudWatchServiceIfNecessary();
throw new ServiceDisabledException("Service Disabled");
}
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ownerFullName.getAccountNumber();
final Collection<String> alarmNames =
CloudWatchBackendServiceFieldValidator.validateAlarmNames(request.getAlarmNames(), true);
if ( !AlarmManager.disableAlarmActions(
accountId,
alarmNames,
RestrictedTypes.<CloudWatchMetadata.AlarmMetadata>filterPrivileged( ) ) ) {
throw new EucalyptusCloudException("User does not have permission");
}
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public DescribeAlarmsResponseType describeAlarms(
final DescribeAlarmsType request
) throws CloudWatchException {
final DescribeAlarmsResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
final boolean showAll = request.getAlarms().remove( "verbose" );
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ctx.isAdministrator() && showAll ? null : ownerFullName.getAccountNumber();
final String actionPrefix = CloudWatchBackendServiceFieldValidator.validateActionPrefix(
request.getActionPrefix(), false);
final String alarmNamePrefix = CloudWatchBackendServiceFieldValidator.validateAlarmNamePrefix(
request.getAlarmNamePrefix(), false);
final Collection<String> alarmNames = CloudWatchBackendServiceFieldValidator.validateAlarmNames(
request.getAlarmNames(), false);
CloudWatchBackendServiceFieldValidator.validateNotBothAlarmNamesAndAlarmNamePrefix(alarmNames, alarmNamePrefix);
final Integer maxRecords = CloudWatchBackendServiceFieldValidator.validateMaxRecords(request.getMaxRecords());
final String nextToken = request.getNextToken();
final StateValue stateValue = CloudWatchBackendServiceFieldValidator.validateStateValue(request.getStateValue(),
false);
final List<AlarmEntity> results;
try {
results = AlarmManager.describeAlarms(accountId,
actionPrefix, alarmNamePrefix, alarmNames, maxRecords, stateValue,
nextToken, RestrictedTypes.filteringFor(CloudWatchMetadata.AlarmMetadata.class).byPrivileges().buildPredicate());
} catch (InvalidTokenException ex) {
throw new InvalidNextTokenException(ex.getMessage());
}
if (maxRecords != null && results.size() == maxRecords) {
reply.getDescribeAlarmsResult().setNextToken(
results.get(results.size() - 1).getNaturalId());
}
final MetricAlarms metricAlarms = new MetricAlarms();
metricAlarms.setMember(Lists.newArrayList(Collections2
.<AlarmEntity, MetricAlarm> transform(results,
TransformationFunctions.AlarmEntityToMetricAlarm.INSTANCE)));
reply.getDescribeAlarmsResult().setMetricAlarms(metricAlarms);
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public DescribeAlarmsForMetricResponseType describeAlarmsForMetric(
final DescribeAlarmsForMetricType request
) throws CloudWatchException {
final DescribeAlarmsForMetricResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ownerFullName.getAccountNumber();
final Map<String, String> dimensionMap = TransformationFunctions.DimensionsToMap.INSTANCE
.apply(CloudWatchBackendServiceFieldValidator.validateDimensions(request.getDimensions()));
final String metricName = CloudWatchBackendServiceFieldValidator.validateMetricName(request.getMetricName(),
true);
final String namespace = CloudWatchBackendServiceFieldValidator.validateNamespace(request.getNamespace(), true);
final Integer period = CloudWatchBackendServiceFieldValidator.validatePeriod(request.getPeriod(), false);
final Statistic statistic = CloudWatchBackendServiceFieldValidator.validateStatistic(request.getStatistic(),
false);
final Units unit = CloudWatchBackendServiceFieldValidator.validateUnits(request.getUnit(), true);
final Collection<AlarmEntity> results = AlarmManager.describeAlarmsForMetric(
accountId, dimensionMap, metricName, namespace, period, statistic,
unit, RestrictedTypes.filteringFor( CloudWatchMetadata.AlarmMetadata.class ).byPrivileges( ).buildPredicate( ) );
final MetricAlarms metricAlarms = new MetricAlarms();
metricAlarms.setMember(Lists.newArrayList(Collections2
.<AlarmEntity, MetricAlarm> transform(results,
TransformationFunctions.AlarmEntityToMetricAlarm.INSTANCE)));
reply.getDescribeAlarmsForMetricResult().setMetricAlarms(metricAlarms);
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public DescribeAlarmHistoryResponseType describeAlarmHistory(
final DescribeAlarmHistoryType request
) throws CloudWatchException {
final DescribeAlarmHistoryResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ownerFullName.getAccountNumber();
final String alarmName = CloudWatchBackendServiceFieldValidator.validateAlarmName(request.getAlarmName(), false);
final Date endDate = CloudWatchBackendServiceFieldValidator.validateEndDate(request.getEndDate(), false);
final Date startDate = CloudWatchBackendServiceFieldValidator.validateStartDate(request.getStartDate(), false);
CloudWatchBackendServiceFieldValidator.validateDateOrder(startDate, endDate, "StartDate", "EndDate", false,
false);
final HistoryItemType historyItemType = CloudWatchBackendServiceFieldValidator.validateHistoryItemType(
request.getHistoryItemType(), false);
final Integer maxRecords = CloudWatchBackendServiceFieldValidator.validateMaxRecords(request.getMaxRecords());
final String nextToken = request.getNextToken();
final List<AlarmHistory> results;
try {
results = AlarmManager.describeAlarmHistory(
accountId, alarmName, endDate, historyItemType,
maxRecords, startDate, nextToken, Predicates.compose(
RestrictedTypes.filteringFor(CloudWatchMetadata.AlarmMetadata.class).byPrivileges().buildPredicate(),
TransformationFunctions.AlarmHistoryToAlarmMetadata.INSTANCE));
} catch (InvalidTokenException e) {
throw new InvalidNextTokenException(e.getMessage());
}
if (maxRecords != null && results.size() == maxRecords) {
reply.getDescribeAlarmHistoryResult().setNextToken(
results.get(results.size() - 1).getNaturalId());
}
final AlarmHistoryItems alarmHistoryItems = new AlarmHistoryItems();
alarmHistoryItems
.setMember(Lists.newArrayList(Collections2
.<AlarmHistory, AlarmHistoryItem> transform(
results,
TransformationFunctions.AlarmHistoryToAlarmHistoryItem.INSTANCE)));
reply.getDescribeAlarmHistoryResult().setAlarmHistoryItems(
alarmHistoryItems);
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public EnableAlarmActionsResponseType enableAlarmActions(
EnableAlarmActionsType request) throws CloudWatchException {
final EnableAlarmActionsResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
if (CloudWatchConfigProperties.isDisabledCloudWatchService()) {
faultDisableCloudWatchServiceIfNecessary();
throw new ServiceDisabledException("Service Disabled");
}
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ownerFullName.getAccountNumber();
final Collection<String> alarmNames =
CloudWatchBackendServiceFieldValidator.validateAlarmNames(request.getAlarmNames(), true);
if ( !AlarmManager.enableAlarmActions(
accountId,
alarmNames,
RestrictedTypes.<CloudWatchMetadata.AlarmMetadata>filterPrivileged( )) ) {
throw new EucalyptusCloudException("User does not have permission");
}
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public DeleteAlarmsResponseType deleteAlarms(DeleteAlarmsType request)
throws CloudWatchException {
DeleteAlarmsResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
if (CloudWatchConfigProperties.isDisabledCloudWatchService()) {
faultDisableCloudWatchServiceIfNecessary();
throw new ServiceDisabledException("Service Disabled");
}
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ownerFullName.getAccountNumber();
final Collection<String> alarmNames =
CloudWatchBackendServiceFieldValidator.validateAlarmNames(request.getAlarmNames(), true);
if ( !AlarmManager.deleteAlarms(
accountId,
alarmNames,
RestrictedTypes.<CloudWatchMetadata.AlarmMetadata>filterPrivileged( ) ) ) {
throw new EucalyptusCloudException("User does not have permission");
}
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
public SetAlarmStateResponseType setAlarmState(SetAlarmStateType request)
throws CloudWatchException {
final SetAlarmStateResponseType reply = request.getReply();
final Context ctx = Contexts.lookup();
try {
if (CloudWatchConfigProperties.isDisabledCloudWatchService()) {
faultDisableCloudWatchServiceIfNecessary();
throw new ServiceDisabledException("Service Disabled");
}
final OwnerFullName ownerFullName = ctx.getUserFullName();
final String accountId = ownerFullName.getAccountNumber();
final String alarmName = CloudWatchBackendServiceFieldValidator.validateAlarmName(request.getAlarmName(), true);
final String stateReason = CloudWatchBackendServiceFieldValidator.validateStateReason(request.getStateReason(), true);
final String stateReasonData = CloudWatchBackendServiceFieldValidator.validateStateReasonData(request.getStateReasonData(), false);
final StateValue stateValue = CloudWatchBackendServiceFieldValidator.validateStateValue(request.getStateValue(), true);
try {
AlarmManager.setAlarmState(
accountId, alarmName, stateReason, stateReasonData, stateValue,
RestrictedTypes.<CloudWatchMetadata.AlarmMetadata>filterPrivileged( ) );
} catch (AlarmNotFoundException ex) {
throw new ResourceNotFoundException(ex.getMessage());
}
} catch (Exception ex) {
handleException(ex);
}
return reply;
}
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(CloudWatchBackend.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;
}
}