2021-11-17 10:23:49 -05:00
// Copyright Epic Games, Inc. All Rights Reserved.
using System ;
2021-11-17 08:36:23 -05:00
using System.Collections.Generic ;
using System.ComponentModel.DataAnnotations ;
2022-11-28 11:40:08 -05:00
using System.Linq ;
2023-04-21 05:50:36 -04:00
using System.Text.Json.Serialization ;
2021-11-17 08:36:23 -05:00
using System.Threading ;
using System.Threading.Tasks ;
2022-01-31 13:38:05 -05:00
using EpicGames.Horde.Storage ;
2022-02-24 05:54:09 -05:00
using EpicGames.Serialization ;
2023-02-14 05:16:24 -05:00
using Jupiter.Common ;
2021-11-17 08:36:23 -05:00
using Jupiter.Implementation ;
2022-10-12 06:36:30 -04:00
using Jupiter.Implementation.TransactionLog ;
2021-11-17 08:36:23 -05:00
using Microsoft.AspNetCore.Authorization ;
using Microsoft.AspNetCore.Mvc ;
using Microsoft.Extensions.DependencyInjection ;
using Microsoft.Extensions.Options ;
2022-10-12 06:36:30 -04:00
namespace Jupiter.Controllers
2021-11-17 08:36:23 -05:00
{
2023-07-27 11:20:47 -04:00
[ApiController]
[Route("api/v1/replication-log")]
[InternalApiFilter]
[Authorize]
public class ReplicationLogController : ControllerBase
{
private readonly IServiceProvider _provider ;
private readonly IRequestHelper _requestHelper ;
private readonly IReplicationLog _replicationLog ;
private readonly IOptionsMonitor < SnapshotSettings > _snapshotSettings ;
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
public ReplicationLogController ( IServiceProvider provider , IRequestHelper requestHelper , IReplicationLog replicationLog , IOptionsMonitor < SnapshotSettings > snapshotSettings )
{
_provider = provider ;
_requestHelper = requestHelper ;
_replicationLog = replicationLog ;
_snapshotSettings = snapshotSettings ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
[HttpGet("snapshots/{ns}")]
[ProducesDefaultResponseType]
[ProducesResponseType(type: typeof(ProblemDetails), 400)]
2023-08-10 08:22:50 -04:00
public async Task < IActionResult > GetSnapshotsAsync (
2023-07-27 11:20:47 -04:00
[Required] NamespaceId ns
)
{
2023-08-10 23:09:40 -04:00
ActionResult ? result = await _requestHelper . HasAccessToNamespaceAsync ( User , Request , ns , new [ ] { JupiterAclAction . ReadTransactionLog } ) ;
2023-07-27 11:20:47 -04:00
if ( result ! = null )
{
return result ;
}
2021-11-17 08:36:23 -05:00
2023-08-10 09:29:07 -04:00
return Ok ( new ReplicationLogSnapshots ( await _replicationLog . GetSnapshotsAsync ( ns ) . ToListAsync ( ) ) ) ;
2023-07-27 11:20:47 -04:00
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
[HttpPost("snapshots/{ns}/create")]
[ProducesDefaultResponseType]
[ProducesResponseType(type: typeof(ProblemDetails), 400)]
2023-08-10 08:22:50 -04:00
public async Task < IActionResult > CreateSnapshotAsync (
2023-07-27 11:20:47 -04:00
[Required] NamespaceId ns
)
{
2023-08-10 23:09:40 -04:00
ActionResult ? result = await _requestHelper . HasAccessToNamespaceAsync ( User , Request , ns , new [ ] { JupiterAclAction . WriteTransactionLog } ) ;
2023-07-27 11:20:47 -04:00
if ( result ! = null )
{
return result ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
ReplicationLogSnapshotBuilder builder = ActivatorUtilities . CreateInstance < ReplicationLogSnapshotBuilder > ( _provider ) ;
2023-08-10 08:22:50 -04:00
BlobId snapshotBlob = await builder . BuildSnapshotAsync ( ns , _snapshotSettings . CurrentValue . SnapshotStorageNamespace , CancellationToken . None ) ;
2023-07-27 11:20:47 -04:00
return Ok ( new SnapshotCreatedResponse ( snapshotBlob ) ) ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
[HttpGet("incremental/{ns}")]
[ProducesDefaultResponseType]
[ProducesResponseType(type: typeof(ProblemDetails), 400)]
2023-08-10 08:22:50 -04:00
public async Task < IActionResult > GetIncrementalEventsAsync (
2023-07-27 11:20:47 -04:00
[Required] NamespaceId ns ,
[FromQuery] string? lastBucket ,
[FromQuery] Guid ? lastEvent ,
[FromQuery] int count = 1000
)
{
2023-08-10 23:09:40 -04:00
ActionResult ? result = await _requestHelper . HasAccessToNamespaceAsync ( User , Request , ns , new [ ] { JupiterAclAction . ReadTransactionLog } ) ;
2023-07-27 11:20:47 -04:00
if ( result ! = null )
{
return result ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
if ( ( ( lastBucket = = null & & lastEvent . HasValue ) | | ( lastBucket ! = null & & ! lastEvent . HasValue ) ) & & lastBucket ! = "now" )
{
return BadRequest ( new ProblemDetails
{
Title = $"Both bucket and event has to be specified, or omit both." ,
} ) ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
try
{
2023-08-10 08:22:50 -04:00
IAsyncEnumerable < ReplicationLogEvent > events = _replicationLog . GetAsync ( ns , lastBucket , lastEvent ) ;
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
List < ReplicationLogEvent > l = await events . Take ( count ) . ToListAsync ( ) ;
return Ok ( new ReplicationLogEvents ( l ) ) ;
}
catch ( IncrementalLogNotAvailableException )
{
// failed to resume from the incremental log, check for a snapshot instead
2023-08-10 09:29:07 -04:00
SnapshotInfo ? snapshot = await _replicationLog . GetLatestSnapshotAsync ( ns ) ;
2023-07-27 11:20:47 -04:00
if ( snapshot ! = null )
{
// no log file is available
return BadRequest ( new ProblemDetails
{
Title = $"Log file is not available, use snapshot {snapshot.SnapshotBlob} instead" ,
Type = ProblemTypes . UseSnapshot ,
Extensions = { { "SnapshotId" , snapshot . SnapshotBlob } , { "BlobNamespace" , snapshot . BlobNamespace } , }
} ) ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
// if no snapshot is available we just give up, they can always reset the replication to the default behavior by not sending in lastBucket and lastEvent
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
return BadRequest ( new ProblemDetails
{
Title = $"No snapshot or bucket found for namespace \" { ns } \ "" ,
} ) ;
}
catch ( NamespaceNotFoundException )
{
return NotFound ( new ProblemDetails
{
Title = $"Namespace {ns} was not found" ,
} ) ;
}
}
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
public class SnapshotCreatedResponse
{
public SnapshotCreatedResponse ( )
{
SnapshotBlobId = null ! ;
}
2022-02-24 05:54:09 -05:00
2023-08-09 13:08:12 -04:00
public SnapshotCreatedResponse ( BlobId snapshotBlob )
2023-07-27 11:20:47 -04:00
{
SnapshotBlobId = snapshotBlob ;
}
2022-02-24 05:54:09 -05:00
2023-07-27 11:20:47 -04:00
[CbField("snapshotBlobId")]
2023-08-09 13:08:12 -04:00
public BlobId SnapshotBlobId { get ; set ; }
2023-07-27 11:20:47 -04:00
}
2022-02-24 05:54:09 -05:00
2023-07-27 11:20:47 -04:00
public class ReplicationLogSnapshots
{
public ReplicationLogSnapshots ( )
{
Snapshots = new List < SnapshotInfo > ( ) ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
[JsonConstructor]
public ReplicationLogSnapshots ( List < SnapshotInfo > snapshots )
{
Snapshots = snapshots ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by serialization")]
public List < SnapshotInfo > Snapshots { get ; set ; }
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
public class ReplicationLogEvents
{
public ReplicationLogEvents ( )
{
Events = new List < ReplicationLogEvent > ( ) ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
[JsonConstructor]
public ReplicationLogEvents ( List < ReplicationLogEvent > events )
{
Events = events ;
}
2021-11-17 08:36:23 -05:00
2023-07-27 11:20:47 -04:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by serialization")]
public List < ReplicationLogEvent > Events { get ; set ; }
}
2021-11-17 08:36:23 -05:00
}