/*
* 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.persistence.qakka.distributed.actors;
import akka.actor.ActorRef;
import akka.actor.UntypedActor;
import akka.cluster.client.ClusterClient;
import akka.pattern.Patterns;
import akka.util.Timeout;
import com.codahale.metrics.Timer;
import com.google.inject.Inject;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.usergrid.persistence.actorsystem.ActorSystemFig;
import org.apache.usergrid.persistence.actorsystem.ActorSystemManager;
import org.apache.usergrid.persistence.qakka.MetricsService;
import org.apache.usergrid.persistence.qakka.QakkaFig;
import org.apache.usergrid.persistence.qakka.distributed.DistributedQueueService;
import org.apache.usergrid.persistence.qakka.distributed.messages.*;
import org.apache.usergrid.persistence.qakka.exceptions.QakkaException;
import org.apache.usergrid.persistence.qakka.exceptions.QakkaRuntimeException;
import org.apache.usergrid.persistence.qakka.serialization.auditlog.AuditLog;
import org.apache.usergrid.persistence.qakka.serialization.auditlog.AuditLogSerialization;
import org.apache.usergrid.persistence.qakka.serialization.transferlog.TransferLogSerialization;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import scala.concurrent.Await;
import scala.concurrent.Future;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
public class QueueSender extends UntypedActor {
private static final Logger logger = LoggerFactory.getLogger( QueueSender.class );
private final String name = RandomStringUtils.randomAlphanumeric( 4 );
private final ActorSystemManager actorSystemManager;
private final TransferLogSerialization transferLogSerialization;
private final AuditLogSerialization auditLogSerialization;
private final ActorSystemFig actorSystemFig;
private final QakkaFig qakkaFig;
private final MetricsService metricsService;
@Inject
public QueueSender(
ActorSystemManager actorSystemManager,
TransferLogSerialization transferLogSerialization,
AuditLogSerialization auditLogSerialization,
ActorSystemFig actorSystemFig,
QakkaFig qakkaFig,
MetricsService metricsService
) {
this.actorSystemManager = actorSystemManager;
this.transferLogSerialization = transferLogSerialization;
this.auditLogSerialization = auditLogSerialization;
this.actorSystemFig = actorSystemFig;
this.qakkaFig = qakkaFig;
this.metricsService = metricsService;
}
@Override
public void onReceive(Object message) {
if ( message instanceof QueueSendRequest) {
QueueSendRequest qa = (QueueSendRequest) message;
// as far as caller is concerned, we are done.
getSender().tell( new QueueSendResponse(
DistributedQueueService.Status.SUCCESS, qa.getQueueName() ), getSender() );
final QueueWriter.WriteStatus writeStatus = sendMessageToRegion(
qa.getQueueName(),
qa.getSourceRegion(),
qa.getDestRegion(),
qa.getMessageId(),
qa.getDeliveryTime(),
qa.getExpirationTime() );
logResponse( writeStatus, qa.getQueueName(), qa.getDestRegion(), qa.getMessageId() );
} else {
unhandled( message );
}
}
QueueWriter.WriteStatus sendMessageToRegion(
String queueName,
String sourceRegion,
String destRegion,
UUID messageId,
Long deliveryTime,
Long expirationTime ) {
Timer.Context timer = metricsService.getMetricRegistry().timer( MetricsService.SEND_TIME_SEND ).time();
try {
int maxRetries = qakkaFig.getMaxSendRetries();
int retries = 0;
QueueWriteRequest request = new QueueWriteRequest(
queueName, sourceRegion, destRegion, messageId, deliveryTime, expirationTime );
while (retries++ < maxRetries) {
try {
Timeout t = new Timeout( qakkaFig.getSendTimeoutSeconds(), TimeUnit.SECONDS );
Future<Object> fut;
if (actorSystemManager.getCurrentRegion().equals( destRegion )) {
logger.trace("{}: Sending queue {} message to local region {}", name, queueName, destRegion );
// send to current region via local clientActor
ActorRef clientActor = actorSystemManager.getClientActor();
fut = Patterns.ask( clientActor, request, t );
} else {
logger.trace("{} Sending queue {} message to remote region {}", name, queueName, destRegion );
// send to remote region via cluster client for that region
ActorRef clusterClient = actorSystemManager.getClusterClient( destRegion );
fut = Patterns.ask(
clusterClient, new ClusterClient.Send( "/user/clientActor", request ), t );
}
// wait for response...
final Object response = Await.result( fut, t.duration() );
if (response != null && response instanceof QueueWriteResponse) {
QueueWriteResponse qarm = (QueueWriteResponse) response;
if (!QueueWriter.WriteStatus.ERROR.equals( qarm.getSendStatus() )) {
if (retries > 1) {
logger.debug( "queueAdd TOTAL_SUCCESS after {} retries", retries );
}
return qarm.getSendStatus();
} else {
logger.debug( "ERROR STATUS adding to queue, retrying {}", retries );
}
} else if (response != null) {
logger.debug( "NULL RESPONSE adding to queue, retrying {}", retries );
} else {
logger.debug( "TIMEOUT adding to queue, retrying {}", retries );
}
} catch (Exception e) {
logger.debug( "ERROR adding to queue, retrying " + retries, e );
}
}
throw new QakkaRuntimeException( "Error adding to queue after " + retries + " retries" );
} finally {
timer.stop();
}
}
void logResponse( QueueWriter.WriteStatus writeStatus, String queueName, String region, UUID messageId ) {
if ( writeStatus != null
&& writeStatus.equals( QueueWriter.WriteStatus.ERROR ) ) {
logger.debug( "ERROR status sending message: {}, {}, {}, {}",
new Object[]{queueName, actorSystemFig.getRegionLocal(), region, messageId} );
auditLogSerialization.recordAuditLog(
AuditLog.Action.SEND,
AuditLog.Status.ERROR,
queueName,
region,
messageId,
null);
} else if ( writeStatus != null
&& writeStatus.equals( QueueWriter.WriteStatus.SUCCESS_XFERLOG_NOTDELETED ) ) {
// queue actor failed to clean up transfer log
try {
transferLogSerialization.removeTransferLog(
queueName, actorSystemFig.getRegionLocal(), region, messageId );
} catch (QakkaException se) {
logger.error( "Unable to delete remove transfer log for {}, {}, {}, {}",
new Object[]{queueName, actorSystemFig.getRegionLocal(), region, messageId} );
logger.debug( "Unable to delete remove transfer log exception is:", se );
}
} else if ( writeStatus != null
&& writeStatus.equals( QueueWriter.WriteStatus.SUCCESS_XFERLOG_DELETED ) ) {
//logger.debug( "Delivery Success: {}, {}, {}, {}",
// new Object[]{queueName, actorSystemFig.getRegionLocal(), region, messageId} );
}
}
}