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 ;
using System.Threading ;
using System.Threading.Tasks ;
2022-09-08 08:42:48 -04:00
using Dasync.Collections ;
2022-01-31 13:38:05 -05:00
using EpicGames.Horde.Storage ;
2022-02-24 05:54:09 -05:00
using EpicGames.Serialization ;
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 ;
using Newtonsoft.Json ;
2022-10-12 06:36:30 -04:00
namespace Jupiter.Controllers
2021-11-17 08:36:23 -05:00
{
[ApiController]
[Route("api/v1/replication-log")]
2022-02-14 10:20:23 -05:00
[InternalApiFilter]
2022-06-20 07:40:23 -04:00
[Authorize]
2021-11-17 08:36:23 -05:00
public class ReplicationLogController : ControllerBase
{
private readonly IServiceProvider _provider ;
2022-05-09 07:07:35 -04:00
private readonly RequestHelper _requestHelper ;
2021-11-17 08:36:23 -05:00
private readonly IReplicationLog _replicationLog ;
private readonly IOptionsMonitor < SnapshotSettings > _snapshotSettings ;
2022-05-09 07:07:35 -04:00
public ReplicationLogController ( IServiceProvider provider , RequestHelper requestHelper , IReplicationLog replicationLog , IOptionsMonitor < SnapshotSettings > snapshotSettings )
2021-11-17 08:36:23 -05:00
{
_provider = provider ;
2022-05-09 07:07:35 -04:00
_requestHelper = requestHelper ;
2021-11-17 08:36:23 -05:00
_replicationLog = replicationLog ;
_snapshotSettings = snapshotSettings ;
}
[HttpGet("snapshots/{ns}")]
[ProducesDefaultResponseType]
[ProducesResponseType(type: typeof(ProblemDetails), 400)]
public async Task < IActionResult > GetSnapshots (
[Required] NamespaceId ns
)
{
2022-06-20 07:40:23 -04:00
ActionResult ? result = await _requestHelper . HasAccessToNamespace ( User , Request , ns , new [ ] { AclAction . ReadTransactionLog } ) ;
2022-05-09 07:07:35 -04:00
if ( result ! = null )
2021-11-17 08:36:23 -05:00
{
2022-05-09 07:07:35 -04:00
return result ;
2021-11-17 08:36:23 -05:00
}
return Ok ( new ReplicationLogSnapshots ( await _replicationLog . GetSnapshots ( ns ) . ToListAsync ( ) ) ) ;
}
[HttpPost("snapshots/{ns}/create")]
[ProducesDefaultResponseType]
[ProducesResponseType(type: typeof(ProblemDetails), 400)]
public async Task < IActionResult > CreateSnapshot (
[Required] NamespaceId ns
)
{
2022-06-20 07:40:23 -04:00
ActionResult ? result = await _requestHelper . HasAccessToNamespace ( User , Request , ns , new [ ] { AclAction . WriteTransactionLog } ) ;
2022-05-09 07:07:35 -04:00
if ( result ! = null )
2021-11-17 08:36:23 -05:00
{
2022-05-09 07:07:35 -04:00
return result ;
2021-11-17 08:36:23 -05:00
}
ReplicationLogSnapshotBuilder builder = ActivatorUtilities . CreateInstance < ReplicationLogSnapshotBuilder > ( _provider ) ;
BlobIdentifier snapshotBlob = await builder . BuildSnapshot ( ns , _snapshotSettings . CurrentValue . SnapshotStorageNamespace , CancellationToken . None ) ;
2022-02-24 05:54:09 -05:00
return Ok ( new SnapshotCreatedResponse ( snapshotBlob ) ) ;
2021-11-17 08:36:23 -05:00
}
[HttpGet("incremental/{ns}")]
[ProducesDefaultResponseType]
[ProducesResponseType(type: typeof(ProblemDetails), 400)]
public async Task < IActionResult > GetIncrementalEvents (
[Required] NamespaceId ns ,
[FromQuery] string? lastBucket ,
[FromQuery] Guid ? lastEvent ,
2022-10-03 05:10:09 -04:00
[FromQuery] int count = 1000
2021-11-17 08:36:23 -05:00
)
{
2022-06-20 07:40:23 -04:00
ActionResult ? result = await _requestHelper . HasAccessToNamespace ( User , Request , ns , new [ ] { AclAction . ReadTransactionLog } ) ;
2022-05-09 07:07:35 -04:00
if ( result ! = null )
2021-11-17 08:36:23 -05:00
{
2022-05-09 07:07:35 -04:00
return result ;
2021-11-17 08:36:23 -05:00
}
2022-01-12 06:01:41 -05:00
if ( ( ( lastBucket = = null & & lastEvent . HasValue ) | | ( lastBucket ! = null & & ! lastEvent . HasValue ) ) & & lastBucket ! = "now" )
2021-11-17 08:36:23 -05:00
{
return BadRequest ( new ProblemDetails
{
Title = $"Both bucket and event has to be specified, or omit both." ,
} ) ;
}
try
{
IAsyncEnumerable < ReplicationLogEvent > events = _replicationLog . Get ( ns , lastBucket , lastEvent ) ;
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
SnapshotInfo ? snapshot = await _replicationLog . GetLatestSnapshot ( ns ) ;
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 } }
} ) ;
}
// 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
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" ,
} ) ;
}
}
}
2022-02-24 05:54:09 -05:00
public class SnapshotCreatedResponse
{
public SnapshotCreatedResponse ( )
{
SnapshotBlobId = null ! ;
}
public SnapshotCreatedResponse ( BlobIdentifier snapshotBlob )
{
SnapshotBlobId = snapshotBlob ;
}
[CbField("snapshotBlobId")]
public BlobIdentifier SnapshotBlobId { get ; set ; }
}
2021-11-17 08:36:23 -05:00
public class ReplicationLogSnapshots
{
public ReplicationLogSnapshots ( )
{
Snapshots = new List < SnapshotInfo > ( ) ;
}
[JsonConstructor]
public ReplicationLogSnapshots ( List < SnapshotInfo > snapshots )
{
Snapshots = snapshots ;
}
2022-05-09 04:21:10 -04:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by serialization")]
2021-11-17 08:36:23 -05:00
public List < SnapshotInfo > Snapshots { get ; set ; }
}
public class ReplicationLogEvents
{
public ReplicationLogEvents ( )
{
Events = new List < ReplicationLogEvent > ( ) ;
}
[JsonConstructor]
public ReplicationLogEvents ( List < ReplicationLogEvent > events )
{
Events = events ;
}
2022-05-09 04:21:10 -04:00
[System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA2227:Collection properties should be read only", Justification = "Used by serialization")]
2021-11-17 08:36:23 -05:00
public List < ReplicationLogEvent > Events { get ; set ; }
}
}