Skip to content
This repository has been archived by the owner on Oct 11, 2023. It is now read-only.

Status API updates [Sync changes in RM] #323

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Services/IStatusService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using System.Threading.Tasks;

namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
public interface IStatusService
{
Task<StatusServiceModel> GetStatusAsync();
}
}
22 changes: 22 additions & 0 deletions Services/Models/StatusResultServiceModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) Microsoft. All rights reserved.

using Newtonsoft.Json;

namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
{
public class StatusResultServiceModel
{
[JsonProperty(PropertyName = "IsHealthy")]
public bool IsHealthy { get; set; }

[JsonProperty(PropertyName = "Message")]
public string Message { get; set; }

[JsonConstructor]
public StatusResultServiceModel(bool isHealthy, string message)
{
IsHealthy = isHealthy;
Message = message;
}
}
}
26 changes: 26 additions & 0 deletions Services/Models/StatusServiceModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright (c) Microsoft. All rights reserved.

using Newtonsoft.Json;
using System.Collections.Generic;

namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models
{
public class StatusServiceModel
{
[JsonProperty(PropertyName = "Status")]
public StatusResultServiceModel Status { get; set; }

[JsonProperty(PropertyName = "Properties")]
public Dictionary<string, string> Properties { get; set; }

[JsonProperty(PropertyName = "Dependencies")]
public Dictionary<string, StatusResultServiceModel> Dependencies { get; set; }

public StatusServiceModel(bool isHealthy, string message)
{
this.Status = new StatusResultServiceModel(isHealthy, message);
this.Dependencies = new Dictionary<string, StatusResultServiceModel>();
this.Properties = new Dictionary<string, string>();
}
}
}
14 changes: 8 additions & 6 deletions Services/PreprovisionedIotHub.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
public interface IPreprovisionedIotHub
{
// Ping the registry to see if the connection is healthy
Task<Tuple<bool, string>> PingRegistryAsync();
Task<StatusResultServiceModel> PingRegistryAsync();
}

public class PreprovisionedIotHub : IPreprovisionedIotHub
Expand All @@ -37,21 +37,23 @@ public PreprovisionedIotHub(
}

// Ping the registry to see if the connection is healthy
public async Task<Tuple<bool, string>> PingRegistryAsync()
public async Task<StatusResultServiceModel> PingRegistryAsync()
{
if (this.registry == null) await this.InitAsync();

var result = new StatusResultServiceModel(false, "IoTHub check failed");
try
{
await this.InitAsync();
await this.registry.GetDeviceAsync("healthcheck");
return new Tuple<bool, string>(true, "OK");
result.IsHealthy = true;
result.Message = "Alive and Well!";
}
catch (Exception e)
{
this.log.Error("Device registry test failed", e);
return new Tuple<bool, string>(false, e.Message);
}

return result;
}

// This call can throw an exception, which is fine when the exception happens during
Expand Down
198 changes: 198 additions & 0 deletions Services/StatusService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
// Copyright (c) Microsoft. All rights reserved.

using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Threading.Tasks;

namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services
{
class StatusService : IStatusService
{
private const string JSON_TRUE = "true";
private const string JSON_FALSE = "false";

private const string SIMULATION_RUNNING_KEY = "SimulationRunning";
private const string PREPROVISIONED_IOTHUB_KEY = "PreprovisionedIoTHub";

private const bool ALLOW_INSECURE_SSL_SERVER = true;
private readonly int timeoutMS = 10000;

private readonly IPreprovisionedIotHub preprovisionedIotHub;
private readonly ISimulations simulations;
private readonly IHttpClient httpClient;
private readonly ILogger log;
private readonly IServicesConfig servicesConfig;

public StatusService(
ILogger logger,
IPreprovisionedIotHub preprovisionedIotHub,
ISimulations simulations,
IHttpClient httpClient,
IServicesConfig servicesConfig
)
{
this.log = logger;
this.preprovisionedIotHub = preprovisionedIotHub;
this.simulations = simulations;
this.httpClient = httpClient;
this.servicesConfig = servicesConfig;
}

public async Task<StatusServiceModel> GetStatusAsync()
{
var result = new StatusServiceModel(true, "Alive and well!");
var errors = new List<string>();

string storageAdapterName = "StorageAdapter";
string diagnosticsName = "Diagnostics";

// Simulation status
var simulationIsRunning = await this.CheckIsSimulationRunningAsync(errors);
var isRunning = simulationIsRunning.HasValue && simulationIsRunning.Value;

// Check access to StorageAdapter
var storageAdapterResult = await this.PingServiceAsync(
storageAdapterName,
this.servicesConfig.StorageAdapterApiUrl);
SetServiceStatus(storageAdapterName, storageAdapterResult, result, errors);

// Check access to Diagnostics
var diagnosticsResult = await this.PingServiceAsync(
diagnosticsName,
this.servicesConfig.DiagnosticsEndpointUrl);
// Note: Overall simulation service status is independent of diagnostics service
// Hence not using SetServiceStatus on diagnosticsResult
result.Dependencies.Add(diagnosticsName, diagnosticsResult);

// Preprovisioned IoT hub status
var isHubPreprovisioned = this.IsHubConnectionStringConfigured();

if (isHubPreprovisioned)
{
var preprovisionedHubResult = await this.preprovisionedIotHub.PingRegistryAsync();
SetServiceStatus("IoTHub", preprovisionedHubResult, result, errors);
}

result.Properties.Add(SIMULATION_RUNNING_KEY,
simulationIsRunning.HasValue
? (isRunning ? JSON_TRUE : JSON_FALSE)
: "unknown");
result.Properties.Add(PREPROVISIONED_IOTHUB_KEY,
isHubPreprovisioned
? JSON_TRUE
: JSON_FALSE);

if (errors.Count > 0)
{
result.Status.Message = string.Join("; ", errors);
}

result.Properties.Add("DiagnosticsEndpointUrl", this.servicesConfig?.DiagnosticsEndpointUrl);
result.Properties.Add("StorageAdapterApiUrl", this.servicesConfig?.StorageAdapterApiUrl);
this.log.Info(
"Service status request",
() => new
{
Healthy = result.Status.IsHealthy,
result.Status.Message
});
return result;
}

private void SetServiceStatus(
string dependencyName,
StatusResultServiceModel serviceResult,
StatusServiceModel result,
List<string> errors
)
{
if (!serviceResult.IsHealthy)
{
errors.Add(dependencyName + " check failed");
result.Status.IsHealthy = false;
}
result.Dependencies.Add(dependencyName, serviceResult);
}

// Check whether the simulation is running, and populate errors if any
private async Task<bool?> CheckIsSimulationRunningAsync(List<string> errors)
{
bool? simulationRunning = null;
try
{
var simulationList = await this.simulations.GetListAsync();
var runningSimulation = simulationList.FirstOrDefault(s => s.ShouldBeRunning);
simulationRunning = (runningSimulation != null);
}
catch (Exception e)
{
var msg = "Unable to fetch simulation status";
errors.Add(msg);
this.log.Error(msg, e);
}

return simulationRunning;
}

// Check whether the configuration contains a connection string
private bool IsHubConnectionStringConfigured()
{
var cs = this.servicesConfig?.IoTHubConnString?.ToLowerInvariant().Trim();
return (!string.IsNullOrEmpty(cs)
&& cs.Contains("hostname=")
&& cs.Contains("sharedaccesskeyname=")
&& cs.Contains("sharedaccesskey="));
}

private async Task<StatusResultServiceModel> PingServiceAsync(string serviceName, string serviceURL)
{
var result = new StatusResultServiceModel(false, $"{serviceName} check failed");
try
{
var response = await this.httpClient.GetAsync(this.PrepareRequest($"{serviceURL}/status"));
if (response.IsError)
{
result.Message = $"Status code: {response.StatusCode}; Response: {response.Content}";
}
else
{
var data = JsonConvert.DeserializeObject<StatusServiceModel>(response.Content);
result = data.Status;
}
}
catch (Exception e)
{
this.log.Error(result.Message, () => new { e });
}

return result;
}

private HttpRequest PrepareRequest(string path)
{
var request = new HttpRequest();
request.AddHeader(HttpRequestHeader.Accept.ToString(), "application/json");
request.AddHeader(HttpRequestHeader.CacheControl.ToString(), "no-cache");
request.AddHeader(HttpRequestHeader.Referer.ToString(), "ASA Manager " + this.GetType().FullName);
request.SetUriFromString(path);
request.Options.EnsureSuccess = false;
request.Options.Timeout = this.timeoutMS;
if (path.ToLowerInvariant().StartsWith("https:"))
{
request.Options.AllowInsecureSSLServer = ALLOW_INSECURE_SSL_SERVER;
}

this.log.Debug("Prepare Request", () => new { request });

return request;
}
}
}
32 changes: 1 addition & 31 deletions Services/StorageAdapter/StorageAdapterClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Diagnostics;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Exceptions;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Http;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Models;
using Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.Runtime;
using Newtonsoft.Json;

namespace Microsoft.Azure.IoTSolutions.DeviceSimulation.Services.StorageAdapter
{
public interface IStorageAdapterClient
{
Task<Tuple<bool, string>> PingAsync();
Task<ValueListApiModel> GetAllAsync(string collectionId);
Task<ValueApiModel> GetAsync(string collectionId, string key);
Task<ValueApiModel> CreateAsync(string collectionId, string value);
Expand Down Expand Up @@ -50,36 +50,6 @@ public StorageAdapterClient(
this.diagnosticsLogger = diagnosticsLogger;
}

public async Task<Tuple<bool, string>> PingAsync()
{
var status = false;
var message = "";

try
{
var response = await this.httpClient.GetAsync(this.PrepareRequest($"status"));
if (response.IsError)
{
message = "Status code: " + response.StatusCode;
}
else
{
var data = JsonConvert.DeserializeObject<Dictionary<string, object>>(response.Content);
message = data["Status"].ToString();
status = data["Status"].ToString().StartsWith("OK:");
}
}
catch (Exception e)
{
const string MSG = "Storage adapter check failed";
this.log.Error(MSG, e);
this.diagnosticsLogger.LogServiceError(MSG, e.Message);
message = e.Message;
}

return new Tuple<bool, string>(status, message);
}

public async Task<ValueListApiModel> GetAllAsync(string collectionId)
{
var response = await this.httpClient.GetAsync(
Expand Down
Loading