akka.net cluster : how to send message from seed node to non-seed node - akka.net

I am new to akka.net. I have 2 .net core2 console apps in a cluster , trying to send message from actor from one console app [ which is seed node ] to remote actor on another console app [which is non-seed node].
After starting/running both the console apps, the cluster is established and both the nodes are Up and seed node knows about the non-seed node and vice-versa, but no message is received by remote actor that is on the non-seed node. I am creating a round-robin router on the seed-node, but not sure what I am missing?
Please guide.
Below is the sample code of the both the apps i.e seed node and non-seed node.
// .net core2 console App with Seed Node
class Program
{
public static ActorSystem ClusterSystem;
private static IActorRef StartActor;
private static void Main(string[] args)
{
var config = ConfigurationFactory.ParseString(#"
akka
{
actor {
provider=cluster
deployment {
/tasker {
router = round-robin-pool # routing strategy
nr-of-instances = 5 # max number of total routees
cluster {
enabled = on
allow-local-routees = off
use-role = tasker
max-nr-of-instances-per-node = 1
}
}
}
}
remote
{
dot-netty.tcp {
port = 8081
hostname = ""localhost""
}
}
cluster {
seed-nodes = [""akka.tcp://ClusterSystem#localhost:8081""]
roles=[""main""]
}
}
ClusterSystem = ActorSystem.Create("ClusterSystem", config);
var taskActor = ClusterSystem.ActorOf(Props.Empty.WithRouter(FromConfig.Instance), "tasker");
StartActor = ClusterSystem.ActorOf(Props.Create(() => new StartActor(taskActor)), "startactor");
StartActor.Tell(new Initiate()); // call local actor
// actor on seed node (local actor)
class StartActor : ReceiveActor, ILogReceive
{
private IActorRef taskActor;
public StartActor(IActorRef router)
{
this.taskActor = router;
Receive<Initiate>(i => Start(i));
}
private void Start(Initiate initiate)
{
taskActor.Tell(new Initiate()); // calling remote actor
}
}
.net core2 Console app with Non seed node
class Program
{
public static ActorSystem ClusterSystem;
public static IActorRef TaskActor;
private static void Main(string[] args)
{
Console.Title = "BackEnd";
var config = ConfigurationFactory.ParseString(#"
akka
{
actor {
provider=cluster
}
remote
{
dot-netty.tcp {
port = 0
hostname = ""localhost""
}
}
cluster {
seed-nodes = [""akka.tcp://ClusterSystem#localhost:8081""]
roles=[""tasker""]
}
}
");
ClusterSystem = ActorSystem.Create("ClusterSystem", config);
TaskActor = ClusterSystem.ActorOf(Props.Create<TaskActor>(), "tasker");
Console.Read();
}
}
// Actor on Non-seed node (Remote Actor)
class TaskActor : ReceiveActor, ILogReceive
{
private readonly IActorRef manager;
public TaskActor()
{
this.Receive<Initiate>(i => this.Init(i));
}
private void Init(Initiate initiate)
{
Console.WriteLine($"Message Received"); //
}
}

I am myself answering to my question. So the first thing is that since the remote actor is created by/in another console application, the deployment configuration needs to be changed with routing strategy to "round robin group"
/tasker {
router = round-robin-group # routing strategy
routees.paths = [""/user/starter""]
nr-of-instances = 5 # max number of total routees
cluster {
enabled = on
allow-local-routees = off
use-role = tasker
}
}
And the the "startActor" from the seed node need to be as below
class StartActor : ReceiveActor, ILogReceive
{
private IActorRef router, self;
public StartActor(IActorRef router)
{
self = Self;
this.router = router;
Receive<Initiate>(i =>
{
var routee = router.Ask<Routees>(new GetRoutees()).ContinueWith(tr =>
{
if (tr.Result.Members.Count() > 0)
{
Start(i);
}
else
{
self.Tell(i);
}
});
});
}
private void Start(Initiate initiate)
{
router.Tell(initiate);
}
}
The above code within the "startActor" looks for the routees which once received then only the message is sent otherwise the message is blasted and not received by the remote actor.

Related

Is there is a way to connect devices from Azure IoT Hub to Azure IoT Central?

i see so many devices that can easily connect to Azure IoT hub via MQTT. But it is NOT as easy to connect those same devices to Azure IoT Central. Is there a way to send those data from Azure IoT Hub to Azure IoT Central?
In the case of sending only a telemetry data to the Azure IoT Central App, you can use the Azure Event Grid integrator, where the device telemetry message is published via the Azure IoT Hub routing feature:
The following code snippet is an example of the webhook subscriber implementation (HttpTrigger Function) for handling all needs such as DPS, etc.
function.json file:
{
"bindings": [
{
"name": "eventGridEvent",
"authLevel": "function",
"methods": [
"post",
"options"
],
"direction": "in",
"type": "httpTrigger"
},
{
"name": "$return",
"type": "http",
"direction": "out"
}
]
}
run.csx file:
#r "Newtonsoft.Json"
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Primitives;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Net;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
public static async Task<ActionResult> Run(JObject eventGridEvent, HttpRequest req, ILogger log)
{
if (req.Method == HttpMethod.Options.ToString())
{
log.LogInformation("CloudEventSchema validation");
req.HttpContext.Response.Headers.Add("Webhook-Allowed-Origin", req.Headers["WebHook-Request-Origin"].FirstOrDefault()?.Trim());
return (ActionResult)new OkResult();
}
// consumer of telemetry (iot central)
uint sasTokenTTLInHrs = 1;
string iotcScopeId = req.Headers["iotc-scopeId"].FirstOrDefault() ?? Environment.GetEnvironmentVariable("AzureIoTC_scopeId");
string iotcSasToken = req.Headers["iotc-sasToken"].FirstOrDefault() ?? Environment.GetEnvironmentVariable("AzureIoTC_sasToken");
log.LogInformation($"CloudEvent_Id = {eventGridEvent["id"]}");
log.LogInformation($"AzureIoT_scopeId = {iotcScopeId}");
// mandatory properties
string source = eventGridEvent["data"]?["systemProperties"]?["iothub-message-source"]?.Value<string>();
string deviceId = eventGridEvent["data"]?["systemProperties"]?["iothub-connection-device-id"]?.Value<string>();
if (source == "Telemetry" && !string.IsNullOrEmpty(deviceId) && Regex.IsMatch(deviceId, #"^[a-z0-9\-]+$"))
{
var sysProp = eventGridEvent["data"]["systemProperties"];
var appProp = eventGridEvent["data"]["properties"];
// device model
var component = appProp?["iothub-app-component-name"]?.Value<string>() ?? sysProp["dt-subject"]?.Value<string>() ?? "";
var modelId = appProp?["iothub-app-model-id"]?.Value<string>() ?? sysProp["dt-dataschema"]?.Value<string>();
// creation time
var enqueuedtime = sysProp["iothub-enqueuedtime"]?.Value<DateTime>().ToString("o");
var ctime = appProp?["iothub-creation-time-utc"]?.Value<DateTime>().ToString("o");
// device group (device prefix)
var deviceGroup = appProp?["iothub-app-device-group"]?.Value<string>();
deviceId = $"{(deviceGroup == null ? "" : deviceGroup + "-")}{deviceId}";
// remove sysprop
((JObject)eventGridEvent["data"]).Remove("systemProperties");
try
{
var info = await Connectivity.GetConnectionInfo(deviceId, modelId, iotcScopeId, iotcSasToken, log, sasTokenTTLInHrs);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", info.SasToken);
client.DefaultRequestHeaders.Add("dt-subject", component);
client.DefaultRequestHeaders.Add("iothub-app-iothub-creation-time-utc", ctime ?? enqueuedtime);
var response = await client.PostAsJsonAsync(info.RequestUri, eventGridEvent["data"]);
response.EnsureSuccessStatusCode();
}
log.LogInformation($"POST: {info.RequestUri}\r\n{eventGridEvent["data"]}");
}
catch(Exception ex)
{
log.LogError(ex.InnerException == null ? ex.Message : ex.InnerException.Message);
Connectivity.RemoveDevice(deviceId);
throw ex; // for retrying and deadlettering undeliverable message
}
}
else
{
log.LogWarning($"Wrong event message:\r\n{eventGridEvent}");
}
return (ActionResult)new OkResult();
}
class ConnectivityInfo
{
public string IoTHubName { get; set; }
public string RequestUri { get; set; }
public string SasToken { get; set; }
public ulong SaSExpiry { get; set; }
public string ModelId { get; set; }
public string DeviceConnectionString { get; set; }
}
static class Connectivity
{
static Dictionary<string, ConnectivityInfo> devices = new Dictionary<string, ConnectivityInfo>();
public static async Task<ConnectivityInfo> GetConnectionInfo(string deviceId, string modelId, string iotcScopeId, string iotcSasToken, ILogger log, uint sasTokenTTLInHrs = 24, int retryCounter = 10, int pollingTimeInSeconds = 3)
{
if (devices.ContainsKey(deviceId))
{
if (!string.IsNullOrEmpty(modelId) && devices[deviceId].ModelId != modelId)
{
log.LogWarning($"Reprovissiong device with new model");
devices.Remove(deviceId);
}
else
{
if (!SharedAccessSignatureBuilder.IsValidExpiry(devices[deviceId].SaSExpiry, 100))
{
log.LogWarning($"Refreshing sasToken");
devices[deviceId].SasToken = SharedAccessSignatureBuilder.GetSASTokenFromConnectionString(devices[deviceId].DeviceConnectionString, sasTokenTTLInHrs);
devices[deviceId].SaSExpiry = ulong.Parse(SharedAccessSignatureBuilder.GetExpiry(sasTokenTTLInHrs));
}
return devices[deviceId];
}
}
string deviceKey = SharedAccessSignatureBuilder.ComputeSignature(iotcSasToken, deviceId);
string address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/register?api-version=2021-06-01";
string sas = SharedAccessSignatureBuilder.GetSASToken($"{iotcScopeId}/registrations/{deviceId}", deviceKey, "registration", 1);
using (HttpClient client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Authorization", sas);
client.DefaultRequestHeaders.Add("accept", "application/json");
string jsontext = string.IsNullOrEmpty(modelId) ? null : $"{{ \"modelId\":\"{modelId}\" }}";
var response = await client.PutAsync(address, new StringContent(JsonConvert.SerializeObject(new { registrationId = deviceId, payload = jsontext }), Encoding.UTF8, "application/json"));
var atype = new { errorCode = "", message = "", operationId = "", status = "", registrationState = new JObject() };
do
{
dynamic operationStatus = JsonConvert.DeserializeAnonymousType(await response.Content.ReadAsStringAsync(), atype);
if (!string.IsNullOrEmpty(operationStatus.errorCode))
{
throw new Exception($"{operationStatus.errorCode} - {operationStatus.message}");
}
response.EnsureSuccessStatusCode();
if (operationStatus.status == "assigning")
{
Task.Delay(TimeSpan.FromSeconds(pollingTimeInSeconds)).Wait();
address = $"https://global.azure-devices-provisioning.net/{iotcScopeId}/registrations/{deviceId}/operations/{operationStatus.operationId}?api-version=2021-06-01";
response = await client.GetAsync(address);
}
else if (operationStatus.status == "assigned")
{
var cinfo = new ConnectivityInfo();
cinfo.ModelId = modelId;
cinfo.IoTHubName = operationStatus.registrationState.assignedHub;
cinfo.DeviceConnectionString = $"HostName={cinfo.IoTHubName};DeviceId={deviceId};SharedAccessKey={deviceKey}";
cinfo.RequestUri = $"https://{cinfo.IoTHubName}/devices/{deviceId}/messages/events?api-version=2021-04-12";
cinfo.SasToken = SharedAccessSignatureBuilder.GetSASToken($"{cinfo.IoTHubName}/{deviceId}", deviceKey, null, sasTokenTTLInHrs);
cinfo.SaSExpiry = ulong.Parse(SharedAccessSignatureBuilder.GetExpiry(sasTokenTTLInHrs));
devices.Add(deviceId, cinfo);
log.LogInformation($"DeviceConnectionString: {cinfo.DeviceConnectionString}");
return cinfo;
}
else
{
throw new Exception($"{operationStatus.registrationState.status}: {operationStatus.registrationState.errorCode} - {operationStatus.registrationState.errorMessage}");
}
} while (--retryCounter > 0);
throw new Exception("Registration device status retry timeout exprired, try again.");
}
}
public static void RemoveDevice(string deviceId)
{
if (devices.ContainsKey(deviceId))
devices.Remove(deviceId);
}
}
public sealed class SharedAccessSignatureBuilder
{
public static string GetHostNameNamespaceFromConnectionString(string connectionString)
{
return GetPartsFromConnectionString(connectionString)["HostName"].Split('.').FirstOrDefault();
}
public static string GetSASTokenFromConnectionString(string connectionString, uint hours = 24)
{
var parts = GetPartsFromConnectionString(connectionString);
if (parts.ContainsKey("HostName") && parts.ContainsKey("SharedAccessKey"))
return GetSASToken(parts["HostName"], parts["SharedAccessKey"], parts.Keys.Contains("SharedAccessKeyName") ? parts["SharedAccessKeyName"] : null, hours);
else
return string.Empty;
}
public static string GetSASToken(string resourceUri, string key, string keyName = null, uint hours = 24)
{
try
{
var expiry = GetExpiry(hours);
string stringToSign = System.Web.HttpUtility.UrlEncode(resourceUri) + "\n" + expiry;
var signature = SharedAccessSignatureBuilder.ComputeSignature(key, stringToSign);
var sasToken = keyName == null ?
String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}", System.Web.HttpUtility.UrlEncode(resourceUri), System.Web.HttpUtility.UrlEncode(signature), expiry) :
String.Format(CultureInfo.InvariantCulture, "SharedAccessSignature sr={0}&sig={1}&se={2}&skn={3}", System.Web.HttpUtility.UrlEncode(resourceUri), System.Web.HttpUtility.UrlEncode(signature), expiry, keyName);
return sasToken;
}
catch
{
return string.Empty;
}
}
#region Helpers
public static string ComputeSignature(string key, string stringToSign)
{
using (HMACSHA256 hmac = new HMACSHA256(Convert.FromBase64String(key)))
{
return Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(stringToSign)));
}
}
public static Dictionary<string, string> GetPartsFromConnectionString(string connectionString)
{
return connectionString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Split(new[] { '=' }, 2)).ToDictionary(x => x[0].Trim(), x => x[1].Trim(), StringComparer.OrdinalIgnoreCase);
}
// default expiring = 24 hours
public static string GetExpiry(uint hours = 24)
{
TimeSpan sinceEpoch = DateTime.UtcNow - new DateTime(1970, 1, 1);
return Convert.ToString((ulong)sinceEpoch.TotalSeconds + 3600 * hours);
}
public static DateTime GetDateTimeUtcFromExpiry(ulong expiry)
{
return (new DateTime(1970, 1, 1)).AddSeconds(expiry);
}
public static bool IsValidExpiry(ulong expiry, ulong toleranceInSeconds = 0)
{
return GetDateTimeUtcFromExpiry(expiry) - TimeSpan.FromSeconds(toleranceInSeconds) > DateTime.UtcNow;
}
#endregion
}
The following screen snippet shows part of the subscription for passing requested headers for webhook subscriber:
Note, that the mapping feature can be used at the Azure IoT Central App on the input side, based on the device model.
As the above first picture shows, this solution is based on using the Azure Event Grid feature, where the Azure IoT Hub represents a publisher of the device telemetry data and the Azure IoT Central app is its consumer.
The logical connectivity between the Azure IoT Hub and Azure IoT Central is done via the AEG Subscription with a webhook destination handler such as the HttpTrigger Function (see the above implementation).
Note, that this subscription is configured for delivering an event message (device telemetry data) in the CloudEventSchema.
Device that provisions itself in IoTHub via DPS will work with IoT Central with no change other than ID Scope sent by device during provisioning which identifies DPS service instance. One ID Scope will point it specific IoT Hub configured in DPS enrollment group, while other will point it to an internal IoT Hub in IoT Central application (IoT Central spins additional internal IoT Hubs as needed for auto scaling, which is why it has its own internal DPS).
Use of DPS allows provisioning of the device to specific IoTHub at the first call and subsequently the change can be triggered explicitly for reprovisioning to different IoTHub or IoT Central, which can be used to move device if needed. This functionality allows scenarios where you can force a device to connect to an IoT Hub or IoT Central by implementing ID Scope change direct method and triggering reprovisioning. Use of DPS is highly recommended as it simplifies provisioning and provides this flexibility.
Reprovisioning should be part of retry logic on the device in case if it fails to connect to IoTHub for certain amount of time in addition to on-demand change described above.
What makes you think "But it is NOT as easy to connect those same devices to Azure IoT Central"?
Any device connecting to IoTHub can also connect to IoTCentral, you just need to provision the device using DPS, it will get the IoTHub hostname and everything else will work the same way.

Redisson Connection Listener not getting invoked in version 3.7.0

I am trying to setup a connection listener for my redisson client. It is not getting invoked on both connect/disconnect.
Tried the code mentioned on the redisson github which is as per below:
public void createRedisClient(Handler<AsyncResult<Redis>> handler) {
ConfigRetriever configRetriever = UDSFBootStrapper.getInstance().getConfigRetriever();
configRetriever.getConfig(
config -> {
String redisUrl = config.result().getString("redisip");
redisUrl += ":";
redisUrl += config.result().getInteger("redisport");
Config rconfig = new Config();
rconfig.setTransportMode(TransportMode.EPOLL);
rconfig.useClusterServers()
.addNodeAddress(UdsfConstants.REDIS_CONNECTION_PREFIX + redisUrl);
rclient = Redisson.create(rconfig);
rclient.getNodesGroup().addConnectionListener(new ConnectionListener() {
//#Override
public void onConnect(InetSocketAddress inetSocketAddress) {
logger.info("Redis server connected");
}
//#Override
public void onDisconnect(InetSocketAddress inetSocketAddress) {
logger.info("Redis server disconnected");
}
});
});
}

ASP.NET Core 2.2 - SignalR How to join a group and send message from the server?

I have a set up a basic solution for SignalR where I can send and receive messages to all clients through the browser.
Required solution:
I need to implement the use of groups, BUT with the ability to join the group and send a message from a background service class that is running on the server.
Currently I have only been successful in sending a message to ALL clients from the server.
My background service class (basically a comms server) will be running multiple instances, so each instance will have a unique name such as CommsServer 1, CommsServer 2 etc. Each instance of the comms server will need to output messages to a specific SignalR group of recipients.
In the browser, the user will select which SignalR group they wish to join from a dropdown list that is pulled from the server. The server also has knowledge of this same list and therefore each comms server instance will represent an item from the list.
My code so far:
MessageHub Class:
public class MessageHub : Hub
{
public Task SendMessageToAll(string message)
{
return Clients.All.SendAsync("ReceiveMessage", message);
}
}
Message.js File:
"use strict";
var connection = new signalR.HubConnectionBuilder()
.withUrl("/messages")
.build();
connection.on("ReceiveMessage", function (message) {
var msg = message.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
var div = document.createElement("div");
div.innerHTML = msg + "<hr/>";
document.getElementById("messages").appendChild(div);
});
connection.start().catch(function (err) {
return console.error(err.toString());
});
document.getElementById("sendButton").addEventListener("click", function (event) {
var message = document.getElementById("message").value;
connection.invoke("SendMessageToAll", message).catch(function (err) {
return console.error(err.toString());
});
e.preventDefault();
});
Razor Page:
<textarea name="message" id="message"></textarea>
<input type="button" id="sendButton" value="Send Message" />
<div id="messages"></div>
<script src="~/lib/signalr/dist/browser/signalr.js"></script>
<script src="~/js/message.js"></script>
Comms server class:
public class TcpServer
{
private readonly IHubContext<MessageHub> _hubContext;
public TcpServerTcpServer(IHubContext<MessageHub> hubContext)
{
_hubContext = hubContext;
}
public string inboundIpAddress = "127.0.0.1";
public int inboundLocalPortNumber = 10001;
public void TcpServerIN(string serverName)
{
Task.Run(() =>
{
IPAddress localAddrIN = IPAddress.Parse(inboundIpAddress);
TcpListener listener = new TcpListener(localAddrIN, inboundLocalPortNumber);
listener.Start();
while (true)
{
TcpClient client = listener.AcceptTcpClient();
// Get a stream object for reading and writing
NetworkStream stream = client.GetStream(); // Networkstream is used to send/receive messages
//Buffer for reading data
Byte[] bytes = new Byte[4096];
String data = null;
int i;
// Loop to receive all the data sent by the client.
while ((i = stream.Read(bytes, 0, bytes.Length)) != 0)
{
// Translate data bytes to a ASCII string.
data = System.Text.Encoding.ASCII.GetString(bytes, 0, i);
_hubContext.Clients.All.SendAsync(data);
byte[] msg = System.Text.Encoding.ASCII.GetBytes(data);
stream.Write(msg, 0, data.Length);
}
// Shutdown and end connection
client.Close();
}
});
}
}
I need to select a specific SignalR group name e.g. "CommsServer 1" this will form the name of the SignalR group that the user can select from a dropdown list in the browser so they can monitor events from just this one particular server instance. Other user may wish to monitor events from a different server instance.
One option I was considering is simply send events from all servers instances to ALL browser connected clients and then somehow filter the events on the client side, but this would seem an inefficient way of handling things and not good for network traffic.
If you want your client to decide which group to join (by selecting from a list of dropdowns), then your client should probably notify the server of its subscription to messages aimed at the group via the hub:
Hub:
public class MessageHub : Hub
{
public async Task SendMessageToAll(string message)
{
await Clients.All.SendAsync("ReceiveMessage", message);
}
public async Task Subscribe(string groupName)
{
await Groups.AddToGroupAsync(Context.ConnectionId, groupName);
}
}
Background service:
public class TcpServer
{
private readonly IHubContext<MessageHub> _hubContext;
private readonly string _serviceInstanceName;
public TcpServerTcpServer(IHubContext<MessageHub> hubContext, string serviceInstanceName)
{
_hubContext = hubContext;
_serviceInstanceName = serviceInstanceName;
}
public async Task SendMessageToGroup(string message)
{
await _hubContext.Clients.Group(_serviceInstanceName).SendAsync("ReceiveMessage", message);
}
}
And somewhere in your client, perhaps after user chooses an option from your dropdown:
connection.invoke("subscribe", "CommsServer 1, or whatever").catch(function (err) {
return console.error(err.toString());
});

RabbitMQ MQTT Adapter and Paho MQTT client

I’m using RabbitMQ MQTT Adapter and Paho MQTT client.
RabbitMQ version: {rabbitmq_mqtt,"RabbitMQ MQTT Adapter","3.2.1"}
Paho MQTT client version:
<dependency>
<groupId>org.eclipse.paho</groupId>
<artifactId>org.eclipse.paho.client.mqttv3</artifactId>
<version>RELEASE</version>
</dependency>
Please see code inline.
I’m trying to understand if, the subscriber queue can be durable without expiration time. And If the messages can be durable also.
As I understood from RabbitMQ documentation, each time a subscriber subscribes to a topic
RabbitMQ will create a queue with this naming convention:
mqtt-subscription-<ClientName>qos<ClientQOS>
This queue has an expiration time, how can I create a queue without an expiration time? Can I change this queue expiration time to infinite?
As for now each time I run this command: “service rabbitmq-server restart”
The messages in the queue get deleted.
How can I prevent this? Is there a way I can keep the messages in the queue after restart?
In RabbitMQ management UI, I can see under “Publish message” -> “Delivery mode:” which can be “2-persistent”.
If I use management UI to publish messages with Delivery mode = 2-persistent. The messages will be in the queue after service restart.
How can I achieve the same using Paho MQTT Client?
// Heavily based on RabbitMQ MQTT adapter test case code!
// first, import the RabbitMQ Java client
// and the Paho MQTT client classes, plus any other
// requirements
import com.rabbitmq.client.*;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttTopic;
import org.eclipse.paho.client.mqttv3.internal.NetworkModule;
import org.eclipse.paho.client.mqttv3.internal.TCPNetworkModule;
// import org.eclipse.paho.client.mqttv3.internal.trace.Trace;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttOutputStream;
import org.eclipse.paho.client.mqttv3.internal.wire.MqttPublish;
import javax.net.SocketFactory;
import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.util.*;
/***
* MQTT v3.1 tests
* TODO: synchronise access to variables
*/
public class MqttTestClean implements MqttCallback {
// setup some variables which define where the MQTT broker is
private final String host = "0.0.0.0";
private final int port = 1883;
private final String brokerUrl = "tcp://" + host + ":" + port;
private String clientId;
private String clientId3;
private MqttClient client;
private MqttClient client3;
private MqttConnectOptions conOpt;
private ArrayList<MqttMessage> receivedMessages;
// specify a message payload - doesn't matter what this says, but since MQTT expects a byte array
// we convert it from string to byte array here
private final byte[] payload = "This payload was published on MQTT and read using AMQP.".getBytes();
// specify the topic to be used
private final String topic = "topic/proxy/1.0.0/Report/*";
private int testDelay = 2000;
private long lastReceipt;
private boolean expectConnectionFailure;
private ConnectionFactory connectionFactory;
private Connection conn;
private Channel ch;
// override 10s limit
private class MyConnOpts extends MqttConnectOptions {
private int keepAliveInterval = 60;
#Override
public void setKeepAliveInterval(int keepAliveInterval) {
this.keepAliveInterval = keepAliveInterval;
}
#Override
public int getKeepAliveInterval() {
return keepAliveInterval;
}
}
public void setUpMqtt() throws MqttException {
clientId = getClass().getSimpleName() + ((int) (10000*Math.random()));
client = new MqttClient(brokerUrl, clientId);
conOpt = new MyConnOpts();
setConOpts(conOpt);
receivedMessages = new ArrayList<MqttMessage>();
expectConnectionFailure = false;
}
public void tearDownMqtt() throws MqttException {
try {
client.disconnect();
} catch (Exception _) {}
}
private void setUpAmqp() throws Exception {
connectionFactory = new ConnectionFactory();
connectionFactory.setHost(host);
conn = connectionFactory.newConnection();
ch = conn.createChannel();
}
private void tearDownAmqp() throws IOException {
conn.close();
}
private void setConOpts(MqttConnectOptions conOpts) {
conOpts.setCleanSession(true);
conOpts.setKeepAliveInterval(60);
}
private void publish(MqttClient client, String topicName, int qos, byte[] payload) throws MqttException {
MqttTopic topic = client.getTopic(topicName);
MqttMessage message = new MqttMessage(payload);
message.setQos(qos);
MqttDeliveryToken token = topic.publish(message);
token.waitForCompletion();
}
public void connectionLost(Throwable cause) {
if (!expectConnectionFailure)
System.out.println("Connection unexpectedly lost");
}
public void messageArrived(String topic, MqttMessage message) throws Exception {
lastReceipt = System.currentTimeMillis();
System.out.println("-------------------------------------------------");
System.out.println("------------------" + lastReceipt + "-------------------------------");
System.out.println("------------------" + message.toString() + "-------------------------------");
receivedMessages.add(message);
}
public void deliveryComplete(IMqttDeliveryToken token) {
}
public void run() {
try {
setUpMqtt(); // initialise the MQTT connection
setUpAmqp(); // initialise the AMQP connection
connect();
//String queue = ch.queueDeclare().getQueue();
// String queue = ch.queueDeclare("mqtt-subscription-Snabel-3qos1", true, false, false, null).getQueue();
//ch.queueBind(queue, "amq.topic", "sci-topic.sc.proxy_1393.1.0.0.ApReport.*"/*topic*/);
client.connect(conOpt);
publish(client, "topic/proxy/1.0.0/Report/123456789",1, payload); // publish the MQTT message
client.disconnect();
Thread.sleep(testDelay);
tearDownAmqp(); // cleanup AMQP resources
tearDownMqtt(); // cleanup MQTT resources*/
disConnect();
} catch (Exception mqe) {
mqe.printStackTrace();
}
}
private void connect() throws Exception {
clientId3 = "Test-3";
client3 = new MqttClient(brokerUrl, clientId3);
MqttConnectOptions connOpts = new MqttConnectOptions();
connOpts.setCleanSession(false);
client3.connect(connOpts);
client3.setCallback(this);
client3.subscribe(topic);
if(!client3.isConnected()){
System.out.println("Not Connected");
return;
}
System.out.println("Connected");
}
private void disConnect() throws Exception {
try {
client3.disconnect();
} catch (Exception _) {}
}
public static void main(String[] args) {
MqttTest mqt = new MqttTest();
mqt.run();
}
}
This was a RabbitMQ bug:
http://rabbitmq.1065348.n5.nabble.com/MQTT-plugin-message-delivery-mode-td32925.html
It was fixed in:
http://www.rabbitmq.com/release-notes/README-3.2.4.txt

Replying an Ask in a clustered routee

I am creating a project that at this moment has an Actor (User) that calls to another actor (Concert) through a consistent-hash-group router. All works fine but my problem is that from the concert actor I can not answer the Ask message. Somehow the message is lost and nothing happens in the client. I have tried everything with no luck:
Sender.Tell <-- creates a temporal? sender
Passing the User IActorRef by reference in the message and using it.
Here is the full code: https://github.com/pablocastilla/AkkaConcert
The main details are the following:
User actor:
protected IActorRef concertRouter;
public User(IActorRef concertRouter, int eventId)
{
this.concertRouter = concertRouter;
this.eventId = eventId;
JobStarter = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(TimeSpan.FromMilliseconds(20),
TimeSpan.FromMilliseconds(1000), Self, new AttemptToStartJob(), Self);
Receive<AttemptToStartJob>(start =>
{
var self = Self;
concertRouter.Ask<Routees>(new GetRoutees()).ContinueWith(tr =>
{
if (tr.Result.Members.Count() > 0)
{
var m = new GetAvailableSeats() { User = self, ConcertId = eventId };
self.Tell(m);
// JobStarter.Cancel();
}
}, TaskContinuationOptions.ExecuteSynchronously);
});
Receive<GetAvailableSeats>(rs =>
{
rs.User = Self;
//get free seats
concertRouter.Ask(rs).ContinueWith(t=>
{
Console.WriteLine("response received!!");
}
);
});
Client HOCON configuration:
<akka>
<hocon>
<![CDATA[
akka {
actor {
provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"
deployment {
/eventpool {
router = consistent-hashing-group
routees.paths = ["/user/HugeEvent"]
virtual-nodes-factor = 8
cluster {
enabled = on
max-nr-of-instances-per-node = 2
allow-local-routees = off
use-role = cluster
}
}
}
}
remote {
log-remote-lifecycle-events = DEBUG
helios.tcp {
transport-class = "Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
applied-adapters = []
transport-protocol = tcp
#will be populated with a dynamic host-name at runtime if left uncommented
#public-hostname = "POPULATE STATIC IP HERE"
hostname = "127.0.0.1"
port = 0
}
}
cluster {
#will inject this node as a self-seed node at run-time
seed-nodes = ["akka.tcp://akkaconcert#127.0.0.1:8080"] #manually populate other seed nodes here, i.e. "akka.tcp://lighthouse#127.0.0.1:4053", "akka.tcp://lighthouse#127.0.0.1:4044"
roles = [client]
auto-down-unreachable-after = 60s
}
}
]]>
</hocon>
In the backend side:
The actor is created
private ActorSystem actorSystem;
private IActorRef event1;
public bool Start(HostControl hostControl)
{
actorSystem = ActorSystem.Create("akkaconcert");
SqlServerPersistence.Init(actorSystem);
event1 = actorSystem.ActorOf(
Props.Create(() => new Concert(1,100000)), "HugeEvent");
return true;
}
Concert actor message processing
private void ReadyCommands()
{
Command<GetAvailableSeats>(message => GetFreeSeatsHandler(message));
Command<ReserveSeats>(message => ReserveSeatsHandler(message));
Command<BuySeats>(message => Persist(message, BuySeatsHandler));
}
private bool GetFreeSeatsHandler(GetAvailableSeats message)
{
var freeSeats = seats.Where(s => s.Value.State == Actors.Seat.SeatState.Free).Select(s2 => s2.Value).ToList();
//1. Trying passing the user actor
//message.User.Tell(new GetFreeSeatsResponse() { FreeSeats = freeSeats }, Context.Self);
//2. Trying with the sender
Context.Sender.Tell(new GetAvailableSeatsResponse() { FreeSeats = freeSeats }, Context.Self);
printMessagesPerSecond(messagesReceived++);
printfreeSeats(freeSeats);
return true;
}
HOCON config at backend side:
<akka>
<hocon>
<![CDATA[
akka {
actor {
provider = "Akka.Cluster.ClusterActorRefProvider, Akka.Cluster"
}
remote {
log-remote-lifecycle-events = DEBUG
helios.tcp {
transport-class = "Akka.Remote.Transport.Helios.HeliosTcpTransport, Akka.Remote"
applied-adapters = []
transport-protocol = tcp
#will be populated with a dynamic host-name at runtime if left uncommented
#public-hostname = "POPULATE STATIC IP HERE"
hostname = "127.0.0.1"
port = 8080
}
}
cluster {
#will inject this node as a self-seed node at run-time
seed-nodes = ["akka.tcp://akkaconcert#127.0.0.1:8080"] #manually populate other seed nodes here, i.e. "akka.tcp://lighthouse#127.0.0.1:4053", "akka.tcp://lighthouse#127.0.0.1:4044"
roles = [cluster]
auto-down-unreachable-after = 10s
}
}
]]>
</hocon>
Thanks!
The problem comes because of message sizes, the message was too big and it was dropped.
Configuration for receiving bigger messages:
akka {
helios.tcp {
# Maximum frame size: 4 MB
maximum-frame-size = 4000000b
}
}