// Copyright Epic Games, Inc. All Rights Reserved. using System; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.Json.Serialization; using Jupiter.Implementation; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Options; namespace Jupiter.Controllers { [ApiController] [Route("api/v1/status")] [Authorize] public class StatusController : Controller { private readonly VersionFile _versionFile; private readonly IOptionsMonitor _jupiterSettings; private readonly IOptionsMonitor _clusterSettings; private readonly IPeerStatusService _statusService; public StatusController(VersionFile versionFile, IOptionsMonitor jupiterSettings, IOptionsMonitor clusterSettings, IPeerStatusService statusService) { _versionFile = versionFile; _jupiterSettings = jupiterSettings; _clusterSettings = clusterSettings; _statusService = statusService; } /// /// Fetch information about Jupiter /// /// /// General information about the service, which version it is running and more. /// /// [HttpGet("")] [ProducesResponseType(type: typeof(StatusResponse), 200)] public IActionResult Status() { IEnumerable attrs = typeof(StatusController).Assembly.GetCustomAttributes(); string srcControlIdentifier = "Unknown"; AssemblyMetadataAttribute? gitHashAttribute = attrs.FirstOrDefault(attr => attr.Key == "GitHash"); if (gitHashAttribute?.Value != null && !string.IsNullOrEmpty(gitHashAttribute.Value)) { srcControlIdentifier = gitHashAttribute.Value; } AssemblyMetadataAttribute? p4ChangeAttribute = attrs.FirstOrDefault(attr => attr.Key == "PerforceChangelist"); if (p4ChangeAttribute?.Value != null && !string.IsNullOrEmpty(p4ChangeAttribute.Value)) { srcControlIdentifier = p4ChangeAttribute.Value; } return Ok(new StatusResponse(_versionFile.VersionString ?? "Unknown", srcControlIdentifier, GetCapabilities(), _jupiterSettings.CurrentValue.CurrentSite)); } private static string[] GetCapabilities() { return new string[] { "transactionlog", "ddc" }; } /// /// Fetch information about other deployments /// /// /// General information about the Jupiter service, which version it is running and more. /// /// [HttpGet("peers")] [ProducesResponseType(type: typeof(PeersResponse), 200)] public IActionResult Peers([FromQuery] bool includeInternalEndpoints = false) { return Ok(new PeersResponse(_jupiterSettings, _clusterSettings, includeInternalEndpoints, _statusService)); } } public class PeersResponse { [JsonConstructor] public PeersResponse(string currentSite, List peers) { CurrentSite = currentSite; Peers = peers; } public PeersResponse(IOptionsMonitor jupiterSettings, IOptionsMonitor clusterSettings, bool includeInternalEndpoints, IPeerStatusService peerStatusService) { CurrentSite = jupiterSettings.CurrentValue.CurrentSite; Peers = clusterSettings.CurrentValue.Peers.Select(settings => new KnownPeer(settings, includeInternalEndpoints, peerStatusService)).ToList(); } public string CurrentSite { get; set; } = null!; [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by serialization")] public List Peers { get; set; } = new List(); } public class KnownPeer { [JsonConstructor] public KnownPeer(string site, string fullName, List endpoints, int latency) { Site = site; FullName = fullName; Endpoints = endpoints; Latency = latency; } public KnownPeer(PeerSettings peerSettings, bool includeInternalEndpoints, IPeerStatusService statusService) { Site = peerSettings.Name; FullName = peerSettings.FullName; IEnumerable endpoints = peerSettings.Endpoints; if (!includeInternalEndpoints) { endpoints = endpoints.Where(s => !s.IsInternal); } Endpoints = endpoints.Select(e => e.Url).ToList(); PeerStatus? peerStatus = statusService.GetPeerStatus(peerSettings.Name); if (peerStatus != null) { Latency = peerStatus.Latency; } } public string Site { get; set; } public string FullName { get; set; } public int Latency { get; set; } [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by serialization")] public List Endpoints { get; set; } } public class StatusResponse { public StatusResponse(string version, string gitHash, string[] capabilities, string siteIdentifier) { Version = version; GitHash = gitHash; Capabilities = capabilities; SiteIdentifier = siteIdentifier; } public string Version { get; set; } public string GitHash { get; set; } public string[] Capabilities { get; set; } public string SiteIdentifier { get; set; } } }