diff --git a/Framework/Core/Endpoint.cs b/Framework/Core/Endpoint.cs new file mode 100644 index 0000000..28b0c61 --- /dev/null +++ b/Framework/Core/Endpoint.cs @@ -0,0 +1,193 @@ +using Logging; +using Newtonsoft.Json; +using Serialization = Newtonsoft.Json.Serialization; +using System.Net.Http.Headers; +using System.Net.Http.Json; +using Utils; + +namespace Core +{ + public interface IEndpoint + { + string HttpGetString(string route); + T HttpGetJson(string route); + TResponse HttpPostJson(string route, TRequest body); + string HttpPostJson(string route, TRequest body); + TResponse HttpPostString(string route, string body); + string HttpPostStream(string route, Stream stream); + Stream HttpGetStream(string route); + T Deserialize(string json); + } + + internal class Endpoint : IEndpoint + { + private readonly ILog log; + private readonly IHttp http; + private readonly Address address; + private readonly string baseUrl; + private readonly string? logAlias; + + public Endpoint(ILog log, IHttp http, Address address, string baseUrl, string? logAlias) + { + this.log = log; + this.http = http; + this.address = address; + this.baseUrl = baseUrl; + this.logAlias = logAlias; + } + + public string HttpGetString(string route) + { + return http.OnClient(client => + { + return GetString(client, route); + }, $"HTTP-GET:{route}"); + } + + public T HttpGetJson(string route) + { + return http.OnClient(client => + { + var json = GetString(client, route); + return Deserialize(json); + }, $"HTTP-GET:{route}"); + } + + public TResponse HttpPostJson(string route, TRequest body) + { + return http.OnClient(client => + { + var response = PostJson(client, route, body); + var json = Time.Wait(response.Content.ReadAsStringAsync()); + if (!response.IsSuccessStatusCode) + { + throw new HttpRequestException(json); + } + Log(GetUrl() + route, json); + return Deserialize(json); + }, $"HTTP-POST-JSON: {route}"); + } + + public string HttpPostJson(string route, TRequest body) + { + return http.OnClient(client => + { + var response = PostJson(client, route, body); + return Time.Wait(response.Content.ReadAsStringAsync()); + }, $"HTTP-POST-JSON: {route}"); + } + + public TResponse HttpPostString(string route, string body) + { + return http.OnClient(client => + { + var response = PostJsonString(client, route, body); + if (response == null) throw new Exception("Received no response."); + var result = Deserialize(response); + if (result == null) throw new Exception("Failed to deserialize response"); + return result; + }, $"HTTO-POST-JSON: {route}"); + } + + public string HttpPostStream(string route, Stream stream) + { + return http.OnClient(client => + { + var url = GetUrl() + route; + Log(url, "~ STREAM ~"); + var content = new StreamContent(stream); + content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + var response = Time.Wait(client.PostAsync(url, content)); + var str = Time.Wait(response.Content.ReadAsStringAsync()); + Log(url, str); + return str; + }, $"HTTP-POST-STREAM: {route}"); + } + + public Stream HttpGetStream(string route) + { + return http.OnClient(client => + { + var url = GetUrl() + route; + Log(url, "~ STREAM ~"); + return Time.Wait(client.GetStreamAsync(url)); + }, $"HTTP-GET-STREAM: {route}"); + } + + public T Deserialize(string json) + { + var errors = new List(); + var deserialized = JsonConvert.DeserializeObject(json, new JsonSerializerSettings() + { + Error = delegate (object? sender, Serialization.ErrorEventArgs args) + { + if (args.CurrentObject == args.ErrorContext.OriginalObject) + { + errors.Add($""" + Member: '{args.ErrorContext.Member?.ToString() ?? ""}' + Path: {args.ErrorContext.Path} + Error: {args.ErrorContext.Error.Message} + """); + args.ErrorContext.Handled = true; + } + } + }); + if (errors.Count > 0) + { + throw new JsonSerializationException($"Failed to deserialize JSON '{json}' with exception(s): \n{string.Join("\n", errors)}"); + } + else if (deserialized == null) + { + throw new JsonSerializationException($"Failed to deserialize JSON '{json}': resulting deserialized object is null"); + } + return deserialized; + } + + private string GetString(HttpClient client, string route) + { + var url = GetUrl() + route; + Log(url, ""); + var result = Time.Wait(client.GetAsync(url)); + var str = Time.Wait(result.Content.ReadAsStringAsync()); + Log(url, str); + return str; + } + + private HttpResponseMessage PostJson(HttpClient client, string route, TRequest body) + { + var url = GetUrl() + route; + using var content = JsonContent.Create(body); + Log(url, JsonConvert.SerializeObject(body)); + return Time.Wait(client.PostAsync(url, content)); + } + + private string PostJsonString(HttpClient client, string route, string body) + { + var url = GetUrl() + route; + Log(url, body); + var content = new StringContent(body); + content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); + var result = Time.Wait(client.PostAsync(url, content)); + var str = Time.Wait(result.Content.ReadAsStringAsync()); + Log(url, str); + return str; + } + + private string GetUrl() + { + return $"{address.Host}:{address.Port}{baseUrl}"; + } + + private void Log(string url, string message) + { + if (logAlias != null) + { + log.Debug($"({logAlias})({url}) = '{message}'", 3); + } + else + { + log.Debug($"({url}) = '{message}'", 3); + } + } + } +} diff --git a/Framework/Core/Http.cs b/Framework/Core/Http.cs index 252a300..61d13f8 100644 --- a/Framework/Core/Http.cs +++ b/Framework/Core/Http.cs @@ -1,24 +1,13 @@ using Logging; -using Newtonsoft.Json; -using Serialization = Newtonsoft.Json.Serialization; -using System.Net.Http.Headers; -using System.Net.Http.Json; using Utils; namespace Core { public interface IHttp { - string HttpGetString(string route); - T HttpGetJson(string route); - TResponse HttpPostJson(string route, TRequest body); - string HttpPostJson(string route, TRequest body); - TResponse HttpPostString(string route, string body); - string HttpPostStream(string route, Stream stream); - Stream HttpGetStream(string route); - T Deserialize(string json); - T OnClient(Func action); + T OnClient(Func action, string description); + IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null); } internal class Http : IHttp @@ -27,25 +16,27 @@ namespace Core private readonly ILog log; private readonly ITimeSet timeSet; private readonly Action onClientCreated; - private readonly string? logAlias; - internal Http(ILog log, ITimeSet timeSet, string? logAlias = null) - : this(log, timeSet, DoNothing, logAlias) + internal Http(ILog log, ITimeSet timeSet) + : this(log, timeSet, DoNothing) { } - internal Http(ILog log, ITimeSet timeSet, Action onClientCreated, string? logAlias = null) + internal Http(ILog log, ITimeSet timeSet, Action onClientCreated) { this.log = log; this.timeSet = timeSet; this.onClientCreated = onClientCreated; - this.logAlias = logAlias; } public T OnClient(Func action) + { + return OnClient(action, GetDescription()); + } + + public T OnClient(Func action, string description) { var client = GetClient(); - var description = GetDescription(); return LockRetry(() => { @@ -53,172 +44,17 @@ namespace Core }, description); } + public IEndpoint CreateEndpoint(Address address, string baseUrl, string? logAlias = null) + { + return new Endpoint(log, this, address, baseUrl, logAlias); + } + private string GetDescription() { // todo: check this: return DebugStack.GetCallerName(skipFrames: 2); } - public string HttpGetString(string route) - { - return LockRetry(() => - { - return GetString(route); - }, $"HTTP-GET:{route}"); - } - - public T HttpGetJson(string route) - { - return LockRetry(() => - { - var json = GetString(route); - return Deserialize(json); - }, $"HTTP-GET:{route}"); - } - - public TResponse HttpPostJson(string route, TRequest body) - { - return LockRetry(() => - { - var response = PostJson(route, body); - var json = Time.Wait(response.Content.ReadAsStringAsync()); - if (!response.IsSuccessStatusCode) - { - throw new HttpRequestException(json); - } - Log(GetUrl() + route, json); - return Deserialize(json); - }, $"HTTP-POST-JSON: {route}"); - } - - public string HttpPostJson(string route, TRequest body) - { - return LockRetry(() => - { - var response = PostJson(route, body); - return Time.Wait(response.Content.ReadAsStringAsync()); - }, $"HTTP-POST-JSON: {route}"); - } - - public TResponse HttpPostString(string route, string body) - { - return LockRetry(() => - { - var response = PostJsonString(route, body); - if (response == null) throw new Exception("Received no response."); - var result = Deserialize(response); - if (result == null) throw new Exception("Failed to deserialize response"); - return result; - }, $"HTTO-POST-JSON: {route}"); - } - - public string HttpPostStream(string route, Stream stream) - { - return LockRetry(() => - { - using var client = GetClient(); - var url = GetUrl() + route; - Log(url, "~ STREAM ~"); - var content = new StreamContent(stream); - content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); - var response = Time.Wait(client.PostAsync(url, content)); - var str = Time.Wait(response.Content.ReadAsStringAsync()); - Log(url, str); - return str; - }, $"HTTP-POST-STREAM: {route}"); - } - - public Stream HttpGetStream(string route) - { - return LockRetry(() => - { - var client = GetClient(); - var url = GetUrl() + route; - Log(url, "~ STREAM ~"); - return Time.Wait(client.GetStreamAsync(url)); - }, $"HTTP-GET-STREAM: {route}"); - } - - public T Deserialize(string json) - { - var errors = new List(); - var deserialized = JsonConvert.DeserializeObject(json, new JsonSerializerSettings() - { - Error = delegate(object? sender, Serialization.ErrorEventArgs args) - { - if (args.CurrentObject == args.ErrorContext.OriginalObject) - { - errors.Add($""" - Member: '{args.ErrorContext.Member?.ToString() ?? ""}' - Path: {args.ErrorContext.Path} - Error: {args.ErrorContext.Error.Message} - """); - args.ErrorContext.Handled = true; - } - } - }); - if (errors.Count > 0) - { - throw new JsonSerializationException($"Failed to deserialize JSON '{json}' with exception(s): \n{string.Join("\n", errors)}"); - } - else if (deserialized == null) - { - throw new JsonSerializationException($"Failed to deserialize JSON '{json}': resulting deserialized object is null"); - } - return deserialized; - } - - private string GetString(string route) - { - using var client = GetClient(); - var url = GetUrl() + route; - Log(url, ""); - var result = Time.Wait(client.GetAsync(url)); - var str = Time.Wait(result.Content.ReadAsStringAsync()); - Log(url, str); - return str; - } - - private HttpResponseMessage PostJson(string route, TRequest body) - { - using var client = GetClient(); - var url = GetUrl() + route; - using var content = JsonContent.Create(body); - Log(url, JsonConvert.SerializeObject(body)); - return Time.Wait(client.PostAsync(url, content)); - } - - private string PostJsonString(string route, string body) - { - using var client = GetClient(); - var url = GetUrl() + route; - Log(url, body); - var content = new StringContent(body); - content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json"); - var result = Time.Wait(client.PostAsync(url, content)); - var str = Time.Wait(result.Content.ReadAsStringAsync()); - Log(url, str); - return str; - } - - private string GetUrl() - { - //return $"{address.Host}:{address.Port}{baseUrl}"; - return "--Obsolete--"; - } - - private void Log(string url, string message) - { - if (logAlias != null) - { - log.Debug($"({logAlias})({url}) = '{message}'", 3); - } - else - { - log.Debug($"({url}) = '{message}'", 3); - } - } - private T LockRetry(Func operation, string description) { lock (httpLock) diff --git a/Framework/Core/PluginTools.cs b/Framework/Core/PluginTools.cs index a493563..0b9b87d 100644 --- a/Framework/Core/PluginTools.cs +++ b/Framework/Core/PluginTools.cs @@ -1,7 +1,6 @@ using FileUtils; using KubernetesWorkflow; using Logging; -using Utils; namespace Core { diff --git a/ProjectPlugins/MetricsPlugin/MetricsQuery.cs b/ProjectPlugins/MetricsPlugin/MetricsQuery.cs index 427d35d..96d81aa 100644 --- a/ProjectPlugins/MetricsPlugin/MetricsQuery.cs +++ b/ProjectPlugins/MetricsPlugin/MetricsQuery.cs @@ -7,14 +7,16 @@ namespace MetricsPlugin { public class MetricsQuery { - private readonly IHttp http; + private readonly IEndpoint endpoint; private readonly ILog log; public MetricsQuery(IPluginTools tools, RunningContainer runningContainer) { RunningContainer = runningContainer; log = tools.GetLog(); - http = tools.CreateHttp(RunningContainer.GetAddress(log, PrometheusContainerRecipe.PortTag), "api/v1"); + endpoint = tools + .CreateHttp() + .CreateEndpoint(RunningContainer.GetAddress(log, PrometheusContainerRecipe.PortTag), "api/v1"); } public RunningContainer RunningContainer { get; } @@ -51,7 +53,7 @@ namespace MetricsPlugin public Metrics? GetAllMetricsForNode(IMetricsScrapeTarget target) { - var response = http.HttpGetJson($"query?query={GetInstanceStringForNode(target)}{GetQueryTimeRange()}"); + var response = endpoint.HttpGetJson($"query?query={GetInstanceStringForNode(target)}{GetQueryTimeRange()}"); if (response.status != "success") return null; var result = MapResponseToMetrics(response); Log(target, result); @@ -60,14 +62,14 @@ namespace MetricsPlugin private PrometheusQueryResponse? GetLastOverTime(string metricName, string instanceString) { - var response = http.HttpGetJson($"query?query=last_over_time({metricName}{instanceString}{GetQueryTimeRange()})"); + var response = endpoint.HttpGetJson($"query?query=last_over_time({metricName}{instanceString}{GetQueryTimeRange()})"); if (response.status != "success") return null; return response; } private PrometheusQueryResponse? GetAll(string metricName) { - var response = http.HttpGetJson($"query?query={metricName}{GetQueryTimeRange()}"); + var response = endpoint.HttpGetJson($"query?query={metricName}{GetQueryTimeRange()}"); if (response.status != "success") return null; return response; }