Commit d8d9e40e authored by Dan Gershony's avatar Dan Gershony

Adding a hearbeat that if enabled will shutdown the node after a trashold

parent 6bb09403
using Microsoft.Extensions.DependencyInjection; using System;
using System.Threading.Tasks;
using Breeze.Api.Models;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Stratis.Bitcoin; using Stratis.Bitcoin;
using Stratis.Bitcoin.Builder; using Stratis.Bitcoin.Builder;
using Stratis.Bitcoin.Builder.Feature; using Stratis.Bitcoin.Builder.Feature;
using Stratis.Bitcoin.Logging; using Stratis.Bitcoin.Logging;
using Stratis.Bitcoin.Utilities;
namespace Breeze.Api namespace Breeze.Api
{ {
...@@ -14,24 +18,69 @@ namespace Breeze.Api ...@@ -14,24 +18,69 @@ namespace Breeze.Api
{ {
private readonly IFullNodeBuilder fullNodeBuilder; private readonly IFullNodeBuilder fullNodeBuilder;
private readonly FullNode fullNode; private readonly FullNode fullNode;
private readonly ApiFeatureOptions apiFeatureOptions;
private readonly IAsyncLoopFactory asyncLoopFactory;
public ApiFeature(IFullNodeBuilder fullNodeBuilder, FullNode fullNode) public ApiFeature(IFullNodeBuilder fullNodeBuilder, FullNode fullNode, ApiFeatureOptions apiFeatureOptions, IAsyncLoopFactory asyncLoopFactory)
{ {
this.fullNodeBuilder = fullNodeBuilder; this.fullNodeBuilder = fullNodeBuilder;
this.fullNode = fullNode; this.fullNode = fullNode;
this.apiFeatureOptions = apiFeatureOptions;
this.asyncLoopFactory = asyncLoopFactory;
} }
public override void Start() public override void Start()
{ {
Logs.FullNode.LogInformation($"Api starting on url {this.fullNode.Settings.ApiUri}"); Logs.FullNode.LogInformation($"Api starting on url {this.fullNode.Settings.ApiUri}");
Program.Initialize(this.fullNodeBuilder.Services, this.fullNode); Program.Initialize(this.fullNodeBuilder.Services, this.fullNode);
this.TryStartHeartbeat();
}
/// <summary>
/// A heartbeat monitor that when enabled will shutdown
/// the node if no external beat was made during the trashold
/// </summary>
public void TryStartHeartbeat()
{
if (this.apiFeatureOptions.HeartbeatMonitor?.HeartbeatInterval.TotalSeconds > 0)
{
this.asyncLoopFactory.Run("ApiFeature.MonitorHeartbeat", token =>
{
// shortened for redability
var monitor = this.apiFeatureOptions.HeartbeatMonitor;
// check the trashold to trigger a shutdown
if (monitor.LastBeat.Add(monitor.HeartbeatInterval) < DateTime.UtcNow)
this.fullNode.Stop();
return Task.CompletedTask;
},
this.fullNode.GlobalCancellation.Cancellation.Token,
repeatEvery: this.apiFeatureOptions.HeartbeatMonitor?.HeartbeatInterval,
startAfter: TimeSpans.Minute);
}
}
}
public class ApiFeatureOptions
{
public HeartbeatMonitor HeartbeatMonitor { get; set; }
public void Heartbeat(TimeSpan timeSpan)
{
this.HeartbeatMonitor = new HeartbeatMonitor {HeartbeatInterval = timeSpan};
} }
} }
public static class ApiFeatureExtension public static class ApiFeatureExtension
{ {
public static IFullNodeBuilder UseApi(this IFullNodeBuilder fullNodeBuilder) public static IFullNodeBuilder UseApi(this IFullNodeBuilder fullNodeBuilder, Action<ApiFeatureOptions> optionsAction = null)
{ {
// TODO: move the options in to the feature builder
var options = new ApiFeatureOptions();
optionsAction?.Invoke(options);
fullNodeBuilder.ConfigureFeature(features => fullNodeBuilder.ConfigureFeature(features =>
{ {
features features
...@@ -39,6 +88,7 @@ namespace Breeze.Api ...@@ -39,6 +88,7 @@ namespace Breeze.Api
.FeatureServices(services => .FeatureServices(services =>
{ {
services.AddSingleton(fullNodeBuilder); services.AddSingleton(fullNodeBuilder);
services.AddSingleton(options);
}); });
}); });
......
using System.ComponentModel.DataAnnotations; using System;
using System.ComponentModel.DataAnnotations;
using Breeze.Api.Models;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using NBitcoin; using NBitcoin;
...@@ -11,10 +13,12 @@ namespace Breeze.Api.Controllers ...@@ -11,10 +13,12 @@ namespace Breeze.Api.Controllers
public class NodeController : Controller public class NodeController : Controller
{ {
private readonly IFullNode fullNode; private readonly IFullNode fullNode;
private readonly ApiFeatureOptions apiFeatureOptions;
public NodeController(IFullNode fullNode) public NodeController(IFullNode fullNode, ApiFeatureOptions apiFeatureOptions)
{ {
this.fullNode = fullNode; this.fullNode = fullNode;
this.apiFeatureOptions = apiFeatureOptions;
} }
/// <summary> /// <summary>
...@@ -41,6 +45,21 @@ namespace Breeze.Api.Controllers ...@@ -41,6 +45,21 @@ namespace Breeze.Api.Controllers
return this.Ok(); return this.Ok();
} }
}
/// <summary>
/// Set the hearbeat flag.
/// </summary>
/// <returns></returns>
[HttpPost]
[Route("heartbeat")]
public IActionResult Heartbeat()
{
if (this.apiFeatureOptions.HeartbeatMonitor == null)
return new ObjectResult("Heartbeat Disabled") {StatusCode = 405}; // (405) Method Not Allowed
this.apiFeatureOptions.HeartbeatMonitor.LastBeat = DateTime.UtcNow;
return this.Ok();
}
}
} }
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
namespace Breeze.Api.Models
{
public class HeartbeatMonitor
{
public DateTime LastBeat { get; set; }
public TimeSpan HeartbeatInterval { get; set; }
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment