/***************************************************************************
* Copyright (c) 2013 VMware, Inc. All Rights Reserved.
* Licensed 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.
***************************************************************************/
/***************************************************************************
* Copyright (c) 2012 VMware, Inc. All Rights Reserved.
* Licensed 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 com.vmware.vhadoop.vhm.rabbit;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.rabbitmq.client.QueueingConsumer;
import com.rabbitmq.client.ShutdownSignalException;
import com.vmware.vhadoop.api.vhm.MQClient;
import com.vmware.vhadoop.api.vhm.QueueClient;
import com.vmware.vhadoop.api.vhm.events.EventConsumer;
import com.vmware.vhadoop.util.ExternalizedParameters;
import com.vmware.vhadoop.util.VhmLevel;
import com.vmware.vhadoop.vhm.events.SerengetiLimitInstruction;
import com.vmware.vhadoop.vhm.rabbit.RabbitConnection.RabbitCredentials;
/**
* Message queue client implementation for RabbitMQ
*
*/
public class RabbitAdaptor implements MQClient {
private final RabbitConnection _connection;
private EventConsumer _eventConsumer;
private volatile boolean _started;
private Thread _mainThread;
long _startTime = System.currentTimeMillis();
boolean _deliberateFailureTriggered = false;
private static long CONNECTION_SHUTDOWN_TIMEOUT_MILLIS = ExternalizedParameters.get().getLong("RABBITMQ_CONNECTION_SHUTDOWN_TIMEOUT_MILLIS");
private static final Logger _log = Logger.getLogger(RabbitAdaptor.class.getName());
@SuppressWarnings("unused")
private void deliberatelyFail(long afterTimeMillis) {
if (!_deliberateFailureTriggered && (System.currentTimeMillis() > (_startTime + afterTimeMillis))) {
_deliberateFailureTriggered = true;
throw new RuntimeException("Deliberate failure!!");
}
}
public static class RabbitConnectionCallback implements QueueClient {
private String _routeKey;
private RabbitConnection _innerConnection;
public RabbitConnectionCallback(String routeKey, RabbitConnection connection) {
_routeKey = routeKey;
_innerConnection = connection;
}
@Override
public void sendMessage(byte[] data) throws CannotConnectException {
if (_routeKey == null) {
_innerConnection.sendMessage(data);
} else {
_innerConnection.sendMessage(_routeKey, data);
}
}
}
public RabbitAdaptor(RabbitCredentials rabbitCredentials) {
_connection = new RabbitConnection(rabbitCredentials);
}
@Override
public void sendMessage(byte[] data) throws CannotConnectException {
_connection.sendMessage(data);
}
@Override
public void registerEventConsumer(EventConsumer eventConsumer) {
_eventConsumer = eventConsumer;
}
@Override
public void start(final EventProducerStartStopCallback startStopCallback) {
_started = true;
_mainThread = new Thread(new Runnable() {
@Override
public void run() {
_log.info("RabbitAdaptor starting...");
startStopCallback.notifyStarted(RabbitAdaptor.this);
while (_started) {
/* wait for a rabbit instance to talk to */
while (!_connection.connect()) {
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
_log.warning("VHM: unexpected interruption while waiting for connection to RabbitMQ broker to complete");
if (!_started) {
/* We could have never properly initialized and are then being told to shut down - break the loop */
break;
}
}
}
if (_started) {
_log.info("Connected to Rabbit server");
}
try {
while (_started) {
_log.info("Rabbit queue waiting for message");
QueueingConsumer.Delivery delivery = _connection.getConsumer().nextDelivery();
_log.finest("Raw JSON: "+new String(delivery.getBody()));
VHMJsonInputMessage message = new VHMJsonInputMessage(delivery.getBody());
SerengetiLimitInstruction event = new SerengetiLimitInstruction(message.getClusterId(), message.getAction(), message.getInstanceNum(), new RabbitConnectionCallback(message.getRouteKey(), _connection));
/* log that we've received an explicit action from the serengeti client */
_log.log(VhmLevel.USER, "VHM: <%C"+message.getClusterId()+"%C> - instruction received from Serengeti client: "+event.toString());
_eventConsumer.placeEventOnQueue(event);
/* acknowledge receipt of the instruction immediately so that Serengeti isn't left hanging if the cluster scale thread is blocked by an in-flight operation */
event.acknowledgeReceipt();
}
} catch (InterruptedException e) {
/* Almost certainly stop() was invoked */
} catch (ShutdownSignalException e) {
_log.info("Rabbit queue shutting down");
/* This is not fatal, this is an expected expection when shutting down */
} catch (Throwable t) {
_log.log(Level.SEVERE, "VHM: unexpected exception from RabbitMQ queue - "+ t.getMessage());
_log.log(Level.INFO, "VHM: unexpected exception from Rabbit queue ", t);
startStopCallback.notifyFailed(RabbitAdaptor.this);
}
}
_log.info("RabbitAdaptor stopping...");
waitForConnectionShutdown(CONNECTION_SHUTDOWN_TIMEOUT_MILLIS);
startStopCallback.notifyStopped(RabbitAdaptor.this);
}}, "MQClientImpl");
_mainThread.start();
}
private void waitForConnectionShutdown(long timeoutMillis) {
int timeoutCountdown = (int)timeoutMillis;
final int sleepTimeMillis = 100;
do {
try {
Thread.sleep(sleepTimeMillis);
} catch (InterruptedException e) {}
} while (!_connection.isShutdown() && ((timeoutCountdown -= sleepTimeMillis) > 0));
}
@Override
public void stop() {
_started = false;
try {
/* TODO: Is this the right approach? */
_connection.getConsumer().getChannel().close();
} catch (Exception e) {
_log.log(Level.INFO, "Error shutting down MQClient "+e.getMessage());
/* If we're in a situation where we fail to shut the queue down cleanly, ensure we interrupt the waiting thread */
_mainThread.interrupt();
}
}
@Override
public boolean isStopped() {
if ((_mainThread == null) || (!_mainThread.isAlive())) {
return true;
}
return false;
}
}