Skip to content

Commit 5e1f0db

Browse files
Merge pull request #154 from sandeepsnairms/main
Rebranding + Resolving Conflicts
2 parents bf569a8 + ca0e77d commit 5e1f0db

File tree

13 files changed

+613
-228
lines changed

13 files changed

+613
-228
lines changed

MongoMigrationWebApp/Program.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -118,8 +118,8 @@ CRITICAL ERROR - {source}
118118
return client;
119119
});
120120

121-
122-
builder.Services.AddRazorPages();
121+
builder.Configuration.AddEnvironmentVariables();
122+
builder.Services.AddRazorPages();
123123
builder.Services.AddServerSideBlazor();
124124
builder.Services.AddSingleton(builder.Configuration);
125125
builder.Services.AddSingleton<JobManager>();

MongoMigrationWebApp/Service/JobManager.cs

Lines changed: 50 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,67 @@
1-
using System;
2-
using System.Collections.Generic;
1+
using Microsoft.Extensions.Configuration;
32
using OnlineMongoMigrationProcessor;
3+
using OnlineMongoMigrationProcessor.Helpers;
44
using OnlineMongoMigrationProcessor.Models;
55
using OnlineMongoMigrationProcessor.Workers;
6-
using OnlineMongoMigrationProcessor.Helpers;
6+
using System;
7+
using System.Collections.Generic;
8+
79

810
namespace MongoMigrationWebApp.Service
911
{
1012

1113
public class JobManager
1214
{
13-
private JobList? _jobList;
15+
private JobList? _jobList;
1416
private MigrationWorker? MigrationWorker { get; set; }
1517

1618
private DateTime _lastJobHeartBeat = DateTime.MinValue;
1719
private string _lastJobID = string.Empty;
1820

21+
public JobManager(IConfiguration configuration)
22+
{
23+
System.IO.File.AppendAllText($"{Helper.GetWorkingFolder()}logabc.txt", $"Resuming migration job after application restart...");
24+
var migrationJobs = GetMigrations(out string errorMessage);
25+
26+
System.IO.File.AppendAllText($"{Helper.GetWorkingFolder()}logabc.txt", $"Step 0");
27+
28+
if (migrationJobs != null && migrationJobs.Count == 1)
29+
{
30+
31+
System.IO.File.AppendAllText($"{Helper.GetWorkingFolder()}logabc.txt", $"Step1 : {migrationJobs[0].IsStarted}-{migrationJobs[0].IsCompleted} -{string.IsNullOrEmpty(migrationJobs[0].SourceConnectionString)} - {string.IsNullOrEmpty(migrationJobs[0].TargetConnectionString)} ");
32+
if (migrationJobs[0].IsStarted && !migrationJobs[0].IsCompleted && string.IsNullOrEmpty(migrationJobs[0].SourceConnectionString) && string.IsNullOrEmpty(migrationJobs[0].TargetConnectionString))
33+
{
34+
try
35+
{
36+
System.IO.File.AppendAllText($"{Helper.GetWorkingFolder()}logabc.txt", $"Step2 : before reading config");
37+
38+
39+
var sourceConnectionString = configuration.GetConnectionString("SourceConnectionString");
40+
var targetConnectionString = configuration.GetConnectionString("TargetConnectionString");
41+
42+
if(sourceConnectionString != null && targetConnectionString != null)
43+
System.IO.File.AppendAllText($"{Helper.GetWorkingFolder()}logabc.txt", $"Step 3 :Cluster found" + sourceConnectionString.Contains("cluster"));
44+
45+
var tmpSrcEndpoint = Helper.ExtractHost(sourceConnectionString);
46+
var tmpTgtEndpoint = Helper.ExtractHost(targetConnectionString);
47+
if (migrationJobs[0].SourceEndpoint == tmpSrcEndpoint && migrationJobs[0].TargetEndpoint == tmpTgtEndpoint)
48+
{
49+
50+
migrationJobs[0].SourceConnectionString = sourceConnectionString;
51+
migrationJobs[0].TargetConnectionString = targetConnectionString;
52+
//ViewMigration(migrationJobs[0].Id);
53+
StartMigrationAsync(migrationJobs[0], sourceConnectionString, targetConnectionString, migrationJobs[0].NameSpaces, migrationJobs[0].JobType, Helper.IsOnline(migrationJobs[0]));
54+
}
55+
}
56+
catch(Exception ex)
57+
{
58+
System.IO.File.AppendAllText($"{Helper.GetWorkingFolder()}logabc.txt", $"Exception : {ex}"); //DO NOTHING
59+
60+
}
61+
}
62+
}
63+
}
64+
1965
#region _configuration Management
2066

2167
public bool UpdateConfig(OnlineMongoMigrationProcessor.MigrationSettings updated_config, out string errorMessage)

MongoMigrationWebApp/Shared/MainLayout.razor

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
<div class="page">
66
<main>
77
<div class="top-row px-4 d-flex justify-content-between align-items-center">
8-
<h2 class="text-left">Migrate to Azure Cosmos DB for MongoDB (vCore based)</h2>
8+
<h2 class="text-left">Migrate to Azure DocumentDB</h2>
99
<div class="ms-2 small">v0.9.2.5.2</div>
1010
</div>
1111

OnlineMongoMigrationProcessor/Helpers/AccumulatedChangesTracker.cs

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,23 +14,39 @@ public class AccumulatedChangesTracker
1414
public List<ChangeStreamDocument<BsonDocument>> DocsToBeUpdated { get; private set; } = new();
1515
public List<ChangeStreamDocument<BsonDocument>> DocsToBeDeleted { get; private set; } = new();
1616

17-
17+
1818
// Track the latest resume token for checkpoint on success
19+
public string CollectionKey { get; private set; } = string.Empty;
1920
public string LatestResumeToken { get; private set; } = string.Empty;
2021
public DateTime LatestTimestamp { get; private set; } = DateTime.MinValue;
2122
public ChangeStreamOperationType LatestOperationType { get; private set; }
2223
public string LatestDocumentKey { get; private set; } = string.Empty;
2324

25+
private string _collectionKey = string.Empty;
26+
public AccumulatedChangesTracker(string collectionKey)
27+
{
28+
_collectionKey = collectionKey;
29+
CollectionKey = _collectionKey;
30+
}
31+
32+
2433
public void AddInsert(ChangeStreamDocument<BsonDocument> change)
2534
{
35+
var changeCollectionKey = $"{change.DatabaseNamespace?.DatabaseName}.{change.CollectionNamespace?.CollectionName}";
36+
if (changeCollectionKey != _collectionKey)
37+
return;
38+
2639
var id = change.DocumentKey.ToJson();
2740

41+
if(string.IsNullOrEmpty(id))
42+
return;
43+
2844
// Remove from other lists
29-
DocsToBeUpdated.RemoveAll(c => c.DocumentKey.ToJson() == id);
30-
DocsToBeDeleted.RemoveAll(c => c.DocumentKey.ToJson() == id);
45+
DocsToBeUpdated.RemoveAll(c => c.DocumentKey != null && c.DocumentKey.ToJson() == id);
46+
DocsToBeDeleted.RemoveAll(c => c.DocumentKey != null && c.DocumentKey.ToJson() == id);
3147

3248
// Replace if already exists
33-
DocsToBeInserted.RemoveAll(c => c.DocumentKey.ToJson() == id);
49+
DocsToBeInserted.RemoveAll(c => c.DocumentKey != null && c.DocumentKey.ToJson() == id);
3450
DocsToBeInserted.Add(change);
3551

3652
// Track earliest and latest change metadata for checkpoint updates
@@ -39,13 +55,20 @@ public void AddInsert(ChangeStreamDocument<BsonDocument> change)
3955

4056
public void AddUpdate(ChangeStreamDocument<BsonDocument> change)
4157
{
58+
var changeCollectionKey = $"{change.DatabaseNamespace?.DatabaseName}.{change.CollectionNamespace?.CollectionName}";
59+
if (changeCollectionKey != _collectionKey)
60+
return;
61+
4262
var id = change.DocumentKey.ToJson();
4363

64+
if (string.IsNullOrEmpty(id))
65+
return;
66+
4467
// Remove from delete list
45-
DocsToBeDeleted.RemoveAll(c => c.DocumentKey.ToJson() == id);
68+
DocsToBeDeleted.RemoveAll(c => c.DocumentKey != null && c.DocumentKey != null && c.DocumentKey.ToJson() == id);
4669

4770
// Replace in update list
48-
DocsToBeUpdated.RemoveAll(c => c.DocumentKey.ToJson() == id);
71+
DocsToBeUpdated.RemoveAll(c => c.DocumentKey != null && c.DocumentKey.ToJson() == id);
4972
DocsToBeUpdated.Add(change);
5073

5174
// Don't remove from insert — updates after insert are valid
@@ -56,14 +79,21 @@ public void AddUpdate(ChangeStreamDocument<BsonDocument> change)
5679

5780
public void AddDelete(ChangeStreamDocument<BsonDocument> change)
5881
{
82+
var changeCollectionKey = $"{change.DatabaseNamespace?.DatabaseName}.{change.CollectionNamespace?.CollectionName}";
83+
if (changeCollectionKey != _collectionKey)
84+
return;
85+
5986
var id = change.DocumentKey.ToJson();
6087

88+
if (string.IsNullOrEmpty(id))
89+
return;
90+
6191
// Remove from insert and update
62-
DocsToBeInserted.RemoveAll(c => c.DocumentKey.ToJson() == id);
63-
DocsToBeUpdated.RemoveAll(c => c.DocumentKey.ToJson() == id);
92+
DocsToBeInserted.RemoveAll(c => c.DocumentKey != null && c.DocumentKey != null && c.DocumentKey.ToJson() == id);
93+
DocsToBeUpdated.RemoveAll(c => c.DocumentKey != null && c.DocumentKey != null && c.DocumentKey.ToJson() == id);
6494

6595
// Replace in delete list
66-
DocsToBeDeleted.RemoveAll(c => c.DocumentKey.ToJson() == id);
96+
DocsToBeDeleted.RemoveAll(c => c.DocumentKey != null && c.DocumentKey != null && c.DocumentKey.ToJson() == id);
6797
DocsToBeDeleted.Add(change);
6898

6999
// Track earliest and latest change metadata for checkpoint updates

OnlineMongoMigrationProcessor/Helpers/MongoHelper.cs

Lines changed: 63 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -632,6 +632,7 @@ public async static Task SetChangeStreamResumeTokenAsync(Log log, MongoClient cl
632632
int retryCount = 0;
633633
bool isSucessful = false;
634634
bool resetCS = unit.ResetChangeStream;
635+
bool useServerLevel=false;
635636

636637
while (!isSucessful && retryCount < 10)
637638
{
@@ -642,7 +643,7 @@ public async static Task SetChangeStreamResumeTokenAsync(Log log, MongoClient cl
642643
var collection = database.GetCollection<BsonDocument>(unit.CollectionName);
643644

644645
// Determine if we should use server-level or collection-level processing
645-
bool useServerLevel = job.ChangeStreamLevel == ChangeStreamLevel.Server;
646+
useServerLevel = job.ChangeStreamLevel == ChangeStreamLevel.Server;
646647

647648
// Initialize with safe defaults; will be overridden below
648649
var options = new ChangeStreamOptions { BatchSize = 500, FullDocument = ChangeStreamFullDocumentOption.UpdateLookup };
@@ -696,24 +697,55 @@ public async static Task SetChangeStreamResumeTokenAsync(Log log, MongoClient cl
696697
};
697698
}
698699

699-
//new way to get resume token
700-
//On MongoDB 4.0+, the WatchChangeStreamAsync method opens a change stream and waits for changes.
701-
//On MongoDB 3.6, TailOplogAsync opens a tailable cursor on the oplog, filtering on namespace and timestamp to detect new operations.
702-
if (!string.IsNullOrEmpty(job.SourceServerVersion) && job.SourceServerVersion.StartsWith("3"))
703-
{
704-
if (!await TailOplogAsync(client, unit.DatabaseName, unit.CollectionName, unit, cts) && !resetCS)
700+
//new way to get resume token
701+
//On MongoDB 4.0+, the WatchChangeStreamAsync method opens a change stream and waits for changes.
702+
//On MongoDB 3.6, TailOplogAsync opens a tailable cursor on the oplog, filtering on namespace and timestamp to detect new operations.
703+
if (!string.IsNullOrEmpty(job.SourceServerVersion) && job.SourceServerVersion.StartsWith("3"))
705704
{
706-
//if failed to tail oplog, fallback to watching change stream infinelty. Should be called async only
707-
await WatchChangeStreamUntilChangeAsync(log, client, jobList, job, unit, collection, options, resetCS, -1, cts, useServerLevel);
705+
if (!await TailOplogAsync(client, unit.DatabaseName, unit.CollectionName, unit, cts) && !resetCS)
706+
{
707+
//if failed to tail oplog, fallback to watching change stream infinelty. Should be called async only
708+
await WatchChangeStreamUntilChangeAsync(log, client, jobList, job, unit, collection, options, resetCS, -1, cts, useServerLevel);//don't await
709+
}
710+
}
711+
else
712+
{
713+
//await WatchChangeStreamUntilChangeAsync(log, client, jobList, job, unit, collection, options, resetCS, seconds, cts, useServerLevel);
714+
//end of new way to get resume token
715+
716+
var settings = client.Settings.Clone();
717+
var localClient = new MongoClient(settings);
718+
719+
var result = await MongoSafeTaskExecutor.ExecuteAsync(
720+
operation: async (ct) =>
721+
{
722+
// forward the cancellation token to your watch function
723+
return await WatchChangeStreamUntilChangeAsync(
724+
log,
725+
localClient,
726+
jobList,
727+
job,
728+
unit,
729+
collection,
730+
options,
731+
resetCS,
732+
seconds,
733+
ct,
734+
useServerLevel
735+
);
736+
},
737+
timeoutSeconds: seconds +10, // choose your timeout
738+
operationName: $"WatchChangeStreamUntilChangeAsync({collection.CollectionNamespace.CollectionName})",
739+
logAction: msg => log.WriteLine(msg,LogType.Debug),
740+
externalToken: cts,
741+
clientToKill: localClient // <-- VERY IMPORTANT: kill this client if watch hangs
742+
);
743+
708744
}
709-
}
710-
else
711-
await WatchChangeStreamUntilChangeAsync(log, client, jobList, job, unit, collection, options, resetCS, seconds, cts, useServerLevel);
712-
//end of new way to get resume token
713745

714746
isSucessful = true;
715747
}
716-
catch (OperationCanceledException)
748+
catch (Exception ex) when (ex is OperationCanceledException || ex is TimeoutException)
717749
{
718750
// Distinguish between timeout (no activity) vs manual cancellation
719751
if (resetCS && unit.ResetChangeStream)
@@ -764,12 +796,17 @@ public async static Task SetChangeStreamResumeTokenAsync(Log log, MongoClient cl
764796
finally
765797
{
766798
jobList.Save();
799+
if (useServerLevel)
800+
log.WriteLine($"Exiting Server-level SetChangeStreamResumeTokenAsync for job {job.Id}", LogType.Info);
801+
else
802+
log.WriteLine($"Exiting Collection-level SetChangeStreamResumeToken for {unit.DatabaseName}.{unit.CollectionName}", LogType.Debug);
767803
}
804+
768805
}
769806
return;
770807
}
771808

772-
private static async Task WatchChangeStreamUntilChangeAsync(Log log, MongoClient client, JobList jobList, MigrationJob job, MigrationUnit unit, IMongoCollection<BsonDocument> collection, ChangeStreamOptions options, bool resetCS, int seconds, CancellationToken manualCts, bool useServerLevel = false)
809+
private static async Task<bool> WatchChangeStreamUntilChangeAsync(Log log, MongoClient client, JobList jobList, MigrationJob job, MigrationUnit unit, IMongoCollection<BsonDocument> collection, ChangeStreamOptions options, bool resetCS, int seconds, CancellationToken manualCts, bool useServerLevel = false)
773810
{
774811
var pipeline = new BsonDocument[] { };
775812
if (job.JobType == JobType.RUOptimizedCopy)
@@ -810,13 +847,13 @@ private static async Task WatchChangeStreamUntilChangeAsync(Log log, MongoClient
810847
if (useServerLevel)
811848
{
812849
// Server-level change stream
813-
log.WriteLine($"Setting up server-level change stream resume token for job {job.Id}");
850+
log.WriteLine($"Setting up server-level change stream resume token for job {job.Id}", LogType.Debug);
814851
cursor = await client.WatchAsync<ChangeStreamDocument<BsonDocument>>(pipeline, options, linkedCts.Token);
815852
}
816853
else
817854
{
818855
// Collection-level change stream
819-
log.WriteLine($"Setting up collection-level change stream resume token for {unit.DatabaseName}.{unit.CollectionName}");
856+
log.WriteLine($"Setting up collection-level change stream resume token for {unit.DatabaseName}.{unit.CollectionName}", LogType.Debug);
820857
cursor = await collection.WatchAsync<ChangeStreamDocument<BsonDocument>>(pipeline, options, linkedCts.Token);
821858
}
822859

@@ -839,9 +876,9 @@ private static async Task WatchChangeStreamUntilChangeAsync(Log log, MongoClient
839876
unit.OriginalResumeToken = resumeTokenJson;
840877

841878
}
842-
return;
879+
return true;
843880
}
844-
return;
881+
return true;
845882
}
846883

847884
// Iterate until cancellation or first change detected
@@ -859,7 +896,7 @@ private static async Task WatchChangeStreamUntilChangeAsync(Log log, MongoClient
859896
//if bulk load is complete, no point in continuing to watch
860897
// Use the local resetCS variable captured at method start, not unit.ResetChangeStream
861898
if ((unit.RestoreComplete || job.IsSimulatedRun) && unit.DumpComplete && !resetCS)
862-
return;
899+
return true;
863900

864901

865902
// Handle server-level vs collection-level resume token storage
@@ -881,7 +918,7 @@ private static async Task WatchChangeStreamUntilChangeAsync(Log log, MongoClient
881918

882919
log.WriteLine($"Server-level resume token set for job {job.Id} with collection key {job.ResumeCollectionKey}");
883920
// Exit immediately after first change detected
884-
return;
921+
return true;
885922
}
886923
}
887924
else
@@ -892,16 +929,19 @@ private static async Task WatchChangeStreamUntilChangeAsync(Log log, MongoClient
892929
log.WriteLine($"Collection-level resume token set for {unit.DatabaseName}.{unit.CollectionName}");
893930

894931
// Exit immediately after first change detected
895-
return;
932+
return true;
896933
}
897934

898935
}
899936
}
900-
937+
938+
return false;
901939
}
902940
catch (OperationCanceledException)
903941
{
904942
// Cancellation requested - exit quietly
943+
log.WriteLine($"Collection-level resume token request for {unit.DatabaseName}.{unit.CollectionName} was canceled.", LogType.Debug);
944+
return false;
905945
}
906946
}
907947
}

0 commit comments

Comments
 (0)