CheckID does not have associated TTL - c#

I'm trying to utilize Consul .NET API to register and fire health checks via TTL. First I'm registering my service with following code:
var address = node.Address;
var id = ServiceId(address);
var registration = new AgentServiceRegistration
{
ID = id,
Name = node.ClusterName,
Address = node.Address.Host,
Port = node.Address.Port.Value,
Check = new AgentServiceCheck
{
TTL = settings.AliveInterval, // 10sec
DeregisterCriticalServiceAfter = settings.AliveTimeout, // 60sec
}
};
// first, try to deregister service, if it has been registered previously
await consul.Agent.ServiceDeregister(registration.ID);
await consul.Agent.ServiceRegister(registration);
Right afterwards, I'm trying to fire a TTL via:
await consul.Agent.PassTTL("service:" + ServiceId(addr), string.Empty);
However, what I end up with is an exception thrown during PassTTL: Consul.ConsulRequestException: Unexpected response, status code InternalServerError: CheckID "service:{service-id}" does not have associated TTL
And the related log from consul agent itself:
[ERR] http: Request PUT /v1/agent/check/pass/service:{service-id}, error: CheckID "service:{service-id}" does not have associated TTL from=127.0.0.1:25419
I'd like to know what I'm doing wrong here.
I'm using consul agent -dev (version: 1.0.1) and Nuget package Consul (version: 0.7.2.3).

Turns out AgentServiceRegistration.Check property is pretty useless. I've achieved the expected result with CheckRegister method.
Here's the code
var registration = new AgentServiceRegistration
{
ID = "serviceId",
Name = node.ClusterName,
Address = node.Address.Host,
Port = node.Address.Port.Value
};
// first, try to deregister service, if it has been registered previously
await consul.Agent.ServiceDeregister(registration.ID);
await consul.Agent.ServiceRegister(registration);
await consul.Agent.CheckRegister(new AgentCheckRegistration()
{
ID = "checkId",
Name = "Check Name",
Status = HealthStatus.Passing,
TTL = settings.AliveInterval,
ServiceID = "serviceId",
DeregisterCriticalServiceAfter = settings.AliveTimeout, // 60sec
})
Now you can pass TTL via
await consul.Agent.PassTTL("checkId", string.Empty);
Just be sure to deregister your check afterwards

It looks like my example was missing a crucial detail here: a ServiceId(address) method was constructing a service ID in form of protocol://service#host:port/ which resulted in Consul complaining about lack of TTL. Changing it to service#host:port seems to fix the error.
I guess in this case a consul error message was very misleading.

Check if id format is:
"service:{service id}:{number}"
In your case, you must pass:
"service:" + ServiceId(addr) + ":1"
as your check id.

Related

EasyNetQ - How to retry failed messages & persist RetryCount in message body/header?

I am using EasyNetQ and need to retry failed messages on the original queue. The problem is: even though I successfully increment the TriedCount variable (in the body of every msg), when EasyNetQ publishes the message to the default error queue after an exception, the updated TriedCount is not in the msg! Presumably because it just dumps the original message to the error queue without the consumer's changes.
The updated TriedCount works for in-process republishes, but not when republished through EasyNetQ Hosepipe or EasyNetQ Management Client. The text files Hosepipe generates do not have the TriedCount updated.
public interface IMsgHandler<T> where T: class, IMessageType
{
Task InvokeMsgCallbackFunc(T msg);
Func<T, Task> MsgCallbackFunc { get; set; }
bool IsTryValid(T msg, string refSubscriptionId); // Calls callback only
// if Retry is valid
}
public interface IMessageType
{
int MsgTypeId { get; }
Dictionary<string, TryInfo> MsgTryInfo {get; set;}
}
public class TryInfo
{
public int TriedCount { get; set; }
/*Other information regarding msg attempt*/
}
public bool SubscribeAsync<T>(Func<T, Task> eventHandler, string subscriptionId)
{
IMsgHandler<T> currMsgHandler = new MsgHandler<T>(eventHandler, subscriptionId);
// Using the msgHandler allows to add a mediator between EasyNetQ and the actual callback function
// The mediator can transmit the retried msg or choose to ignore it
return _defaultBus.SubscribeAsync<T>(subscriptionId, currMsgHandler.InvokeMsgCallbackFunc).Queue != null;
}
I have also tried republishing myself through the Management API (rough code):
var client = new ManagementClient("http://localhost", "guest", "guest");
var vhost = client.GetVhostAsync("/").Result;
var errQueue = client.GetQueueAsync("EasyNetQ_Default_Error_Queue",
vhost).Result;
var crit = new GetMessagesCriteria(long.MaxValue,
Ackmodes.ack_requeue_true);
var errMsgs = client.GetMessagesFromQueueAsync(errQueue,
crit).Result;
foreach (var errMsg in errMsgs)
{
var pubRes = client.PublishAsync(client.GetExchangeAsync(errMsg.Exchange, vhost).Result,
new PublishInfo(errMsg.RoutingKey, errMsg.Payload)).Result;
}
This works but only publishes to the error queue again, not on the original queue. Also, I don't know how to add/update the retry information in the body of the message at this stage.
I have explored this library to add headers to the message but I don't see if the count in the body is not being updated, how/why would the count in the header be updated.
Is there any way to persist the TriedCount without resorting to the Advanced bus (in which case I might use the RabbitMQ .Net client itself)?
Just in case it helps someone else, I eventually implemented my own IErrorMessageSerializer (as opposed to implementing the whole IConsumerErrorStrategy, which seemed like an overkill). The reason I am adding the retry info in the body (instead of the header) is that EasyNetQ doesn't handle complex types in the header (not out-of-the-box anyway). So, using a dictionary gives more control for different consumers. I register the custom serializer at the time of creating the bus like so:
_defaultBus = RabbitHutch.CreateBus(currentConnString, serviceRegister => serviceRegister.Register<IErrorMessageSerializer>(serviceProvider => new RetryEnabledErrorMessageSerializer<IMessageType>(givenSubscriptionId)));
And just implemented the Serialize method like so:
public class RetryEnabledErrorMessageSerializer<T> : IErrorMessageSerializer where T : class, IMessageType
{
public string Serialize(byte[] messageBody)
{
string stringifiedMsgBody = Encoding.UTF8.GetString(messageBody);
var objectifiedMsgBody = JObject.Parse(stringifiedMsgBody);
// Add/update RetryInformation into objectifiedMsgBody here
// I have a dictionary that saves <key:consumerId, val: TryInfoObj>
return JsonConvert.SerializeObject(objectifiedMsgBody);
}
}
The actual retrying is done by a simple console app/windows service periodically via the EasyNetQ Management API:
var client = new ManagementClient(AppConfig.BaseAddress, AppConfig.RabbitUsername, AppConfig.RabbitPassword);
var vhost = client.GetVhostAsync("/").Result;
var aliveRes = client.IsAliveAsync(vhost).Result;
var errQueue = client.GetQueueAsync(Constants.EasyNetQErrorQueueName, vhost).Result;
var crit = new GetMessagesCriteria(long.MaxValue, Ackmodes.ack_requeue_false);
var errMsgs = client.GetMessagesFromQueueAsync(errQueue, crit).Result;
foreach (var errMsg in errMsgs)
{
var innerMsg = JsonConvert.DeserializeObject<Error>(errMsg.Payload);
var pubInfo = new PublishInfo(innerMsg.RoutingKey, innerMsg.Message);
pubInfo.Properties.Add("type", innerMsg.BasicProperties.Type);
pubInfo.Properties.Add("correlation_id", innerMsg.BasicProperties.CorrelationId);
pubInfo.Properties.Add("delivery_mode", innerMsg.BasicProperties.DeliveryMode);
var pubRes = client.PublishAsync(client.GetExchangeAsync(innerMsg.Exchange, vhost).Result,
pubInfo).Result;
}
Whether retry is enabled or not is known by my consumer itself, giving it more control so it can choose to handle the retried msg or just ignore it. Once ignored, the msg will obviously not be tried again; that's how EasyNetQ works.

Error Account with Id = "xxxxxx" does not exist

I have a custo workflow that creates an account and opportunities.
Sometimes I have this error: Account with Id = "xxxxxx" does not exist.
I don't know what's wrong in my code knowing that I find the account in the CRM.
Here are the steps of my plugin code:
Find the account by num (if it doesn't exist, I create them)
Get the account = Account
Create an opportunity with Opportunity["parentaccountid"] = Account;
Error message !
Code:
//Get opportunity
Guid id = retrieveOpportunity<string>("opportunity", "new_numero", numero, service);
Entity eOpportunity;
if (id != Guid.Empty)
{
eOpportunity = new Entity("opportunity", id);
}
else
{
eOpportunity = new Entity("opportunity");
}
//Get account
EntityReference eAccount = retrieveAccount<string>(accountCode, "account", "new_code", service);
if (eAccount == null)
{
eAccount = new Entity("account", "new_code", accountCode);
eAccount["name"] = "name";
UpsertRequest usMessage = new UpsertRequest()
{
Target = eAccount
};
//create account
UpsertResponse usResponse = (UpsertResponse)this._service.Execute(usMessage);
eOpportunity["parentaccountid"] = usResponse.Target;
}
else
{
eOpportunity["parentaccountid"] = eAccount;
}
UpsertRequest req = new UpsertRequest()
{
Target = eOpportunity
};
//upsert opportunity
UpsertResponse resp = (UpsertResponse)service.Execute(req);
if (resp.RecordCreated)
tracer.Trace("New opportunity");
else
tracer.Trace("Opportunity updated");
Sometimes there are several workflows that are started at the same time and that do the same thing (creating other opportunities)
You haven't shown us the entire plugin, so this is just a guess, but you're probably sharing your IOrganizationService at the class level, which is causing race conditions in your code, and one thread creates a new account in a different context, then its service gets overwritten by another thread, which is in a different database transaction that doesn't have the newly created account and it's erroring.
Don't share your IOrganziationService across threads!
Whenever you are trying to consume the created record in the same transaction, convert the plugin into Asynchronous mode - this will work.

Twilio Gather/Play Recordings in loop

I am building a Twilio IVR using WebAPI and hit a bit of a snag. I am trying to loop through the recordings and at the end of each one offer the option to press 1 to delete the recording.
I can't find any examples anywhere of this in a C# WebAPI implementation and the following won't work and I am not sure how to go about this.
What I know;
Need to GATHER the digits entered
Somehow pass back the recording sid to the api to know which recording to delete.
Here is the code I've written so far:
public HttpResponseMessage Messages() {
string baseUrl = Url.Request.RequestUri.GetComponents(UriComponents.SchemeAndServer, UriFormat.Unescaped);
var twilio = new TwilioRestClient(_accountSid, _authToken);
var recordings = twilio.ListRecordings(null, DateTime.Today, null, null);
var twilioResponse = new TwilioResponse();
if (recordings != null && recordings.Recordings.Count > 0)
{
var msgCount = 1;
var msgTotal = recordings.Recordings.Count();
foreach (var recording in recordings.Recordings)
{
var caller = twilio.GetCall(recording.CallSid);
var callerNumber = Regex.Replace(caller.From, #"([0-9]{1})", "$1,");
var callDate = recording.DateCreated.ToString("dddd MMMM d");
var callTime = recording.DateCreated.ToString("h m t");
twilioResponse.Say(string.Format("Playing message {0} of {1}, from {2} on {3} at {4} M.", msgCount, msgTotal, callerNumber, callDate, callTime),
new { voice = "woman" });
var voiceFile = string.Format("{0}2010-04-01/Accounts/{1}/Recordings/{2}.mp3", twilio.BaseUrl, _accountSid, recording.Sid);
twilioResponse.Play(voiceFile);
//twilioResponse.BeginGather(new
// {
// action = baseUrl + "/api/Recording/Delete",
// numDigits = 1,
// });
//twilioResponse.Say("TO DELETE THIS MESSAGE PRESS 1");
//twilioResponse.EndGather();
msgCount++;
}
}
return Request.CreateResponse(HttpStatusCode.OK, twilioResponse.Element, Configuration.Formatters.XmlFormatter);
}
I've worked with Twilio's API before but this is a new one for me and I can't really see if this is possible. The PHP example they have is doing it though so maybe I am simply missing the mark because I should be going a different way about this.
Twilio evangelist here.
What you have looks really close. Lets look at passing back the Recording SID in scenarios where the user presses one since thats relatively easy to do by using the action URL your setting on the Gather verb to hold some state for you:
action = baseUrl + "/api/Recording/Delete?recordingSid=" + recording.Sid;
Now when the user presses one, Twilio will make a request to the Action URL which includes the recordingSid parameter.
Once you've deleted the Recording, you can just redirect back to your Messages endpoint to continue to listen to more recordings. In order to keep track of which recordings you've listened to already, you might need to pass some additional parameters in the action URL that can you can pass through your delete workflow and back into the listen workflow.
Hope that helps.

trouble running simple aditi scheduler tutorial

I am trying to follow simple Aditi Scheduler tutorial but I am getting error. Here is my code.
What am I doing wrong?
Error: The input is not a valid Base-64 string as it contains a
non-base 64 character, more than two padding characters, or an illegal
character among the padding characters.
[TestMethod]
public void ScheduledSMS()
{
var tenantId = "xxxxxxxxxxxmyid";
var secretKey = "xxxxxxxxxxxmykey";
var scheduledTasks = new ScheduledTasks(tenantId, secretKey);
// create a task
var task = new TaskModel
{
Name = "My first Scheduler job",
JobType = JobType.Webhook,
// use predefined CommonCronExpressions or build your own CRON expressions here http://cronmaker.com/
CronExpression = CommonCronExpressions.EveryMinute,
// use builders to set job properties for webhooks and azure queue
Params = ParamBuilderFactory
.WebHookBuilder("http://localhost:1901/SMS/SendText")
.Build()
};
var operationId = scheduledTasks.CreateTask(task); <------ Error happens here..
// all operations in the api follow fire and forget approach, once an operation like create/update/delete
// is requested it returns an operationId(Guid) which can be used to fetch the operation status
// operation status can be fetched in two ways:
// method 1: (without polling) returns the status without polling
var operationStatus = scheduledTasks.GetOperationStatus(operationId);
// method 2: (with polling) polls until the operation status changes to success/error or a timeout occurs
// var operationStatus = scheduledTasks.GetOperationStatus(operationId, true);
// get the task
TaskModel newTask = null;
if (operationStatus.Status == StatusCode.Success)
{
dynamic resultData = operationStatus.Data;
var newTaskId = resultData["Id"];
newTask = scheduledTasks.GetTask(Guid.Parse(newTaskId));
}
}
Maybe the problem is with the "localhost" in the url you pass to the WebHookBuilder?
This is a couple months old, but I had this same problem until I realized I had the tenantId and secretKey reversed (stupid, but easy mistake). Once I swapped them it worked fine.

How to change email subject in C# using Exchange Web Services

I have a piece of code where I am connecting via Exchange Web Services, and I have the message ID. I need to change the subject of this email to the string passed into my method for successful processing later in my workflow. However, I am a bit confused as to how to use the exchange web services classes, my code is below:
public bool SetEmailCorrectSubject(string msgID, string subject)
{
bool bSuccess = true;
if (String.IsNullOrEmpty(msgID))
{
return false;
}
try
{
ItemIdType messageId = new ItemIdType();
messageId.Id = msgID;
ItemChangeDescriptionType desc = new ItemChangeDescriptionType();
// Not sure how to set this up
ItemChangeType itemChange = new ItemChangeType();
itemChange.Item = messageId;
UpdateItemType updateItem = new UpdateItemType();
}
catch (Exception e)
{
_logger.Error("error with resending email with title", e);
return false;
}
return bSuccess;
}
From what I understand, the UpdateItemType class is the way to go, but I'm not clear on how to tell it that I want to change the email subject to the subject parameter.
Any ideas? Is using UpdateItemType even the best way to do this?
Instead of EWS you should use the EWS Managed API as it's more simple to use:
EWS Managed API - Download: http://www.microsoft.com/download/en/details.aspx?id=13480
EWS Managed API - SDK: http://msdn.microsoft.com/en-us/library/dd633710(v=exchg.80).aspx
Apart from that, you don't use the ItemChangeType and UpdaetItemType to modify items. Use the Item.Bind() method to bind to the item, change the subject and update it:
var service = new ExchangeService(ExchangeVersion.Exchange2010_SP1)
{
UseDefaultCredentials = true,
Url = new Uri("https://casserver/ews/exchange.asmx")
};
Item item = Item.Bind(service, new Itemid(msgid));
item.Subject = "test";
item.Update(ConflictResolutionMode.AutoResolve);
Btw, are you sure you have the ItemId? Or do you have an RFC 822 Message-Id? Those two are different.

Categories