PageRenderTime 59ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/Raven.Database/Server/RavenFS/Controllers/SynchronizationController.cs

https://github.com/nwendel/ravendb
C# | 902 lines | 713 code | 179 blank | 10 comment | 38 complexity | 599f01f58c4f5a5edf1e9f7c4e04341b MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, BSD-3-Clause, CC-BY-SA-3.0
  1. using System;
  2. using System.Collections.Concurrent;
  3. using System.Collections.Generic;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Net;
  7. using System.Net.Http;
  8. using System.Threading;
  9. using System.Threading.Tasks;
  10. using System.Web.Http;
  11. using Raven.Abstractions.Logging;
  12. using Raven.Database.Server.RavenFS.Extensions;
  13. using Raven.Database.Server.RavenFS.Infrastructure;
  14. using Raven.Database.Server.RavenFS.Storage;
  15. using Raven.Database.Server.RavenFS.Synchronization.Conflictuality;
  16. using Raven.Database.Server.RavenFS.Synchronization.Multipart;
  17. using Raven.Database.Server.RavenFS.Util;
  18. using Raven.Imports.Newtonsoft.Json;
  19. using Raven.Json.Linq;
  20. using Raven.Abstractions.Extensions;
  21. using Raven.Abstractions.FileSystem;
  22. using Raven.Abstractions.FileSystem.Notifications;
  23. using Raven.Abstractions.Data;
  24. namespace Raven.Database.Server.RavenFS.Controllers
  25. {
  26. public class SynchronizationController : RavenFsApiController
  27. {
  28. private static new readonly ILog Log = LogManager.GetCurrentClassLogger();
  29. private static readonly ConcurrentDictionary<Guid, ReaderWriterLockSlim> SynchronizationFinishLocks =
  30. new ConcurrentDictionary<Guid, ReaderWriterLockSlim>();
  31. [HttpPost]
  32. [Route("fs/{fileSystemName}/synchronization/ToDestinations")]
  33. public async Task<HttpResponseMessage> ToDestinations(bool forceSyncingAll)
  34. {
  35. var result = await SynchronizationTask.SynchronizeDestinationsAsync(forceSyncingAll);
  36. return this.GetMessageWithObject(result, HttpStatusCode.OK);
  37. }
  38. [HttpPost]
  39. [Route("fs/{fileSystemName}/synchronization/ToDestination")]
  40. public async Task<HttpResponseMessage> ToDestination(string destination, bool forceSyncingAll)
  41. {
  42. var result = await SynchronizationTask.SynchronizeDestinationAsync(destination + "/fs/" + this.FileSystemName, forceSyncingAll);
  43. return this.GetMessageWithObject(result, HttpStatusCode.OK);
  44. }
  45. [HttpPost]
  46. [Route("fs/{fileSystemName}/synchronization/start/{*fileName}")]
  47. public async Task<HttpResponseMessage> Start(string fileName)
  48. {
  49. var destination = await ReadJsonObjectAsync<SynchronizationDestination>();
  50. Log.Debug("Starting to synchronize a file '{0}' to {1}", fileName, destination.Url);
  51. var result = await SynchronizationTask.SynchronizeFileToAsync(fileName, destination);
  52. return this.GetMessageWithObject(result, HttpStatusCode.OK);
  53. }
  54. [HttpPost]
  55. [Route("fs/{fileSystemName}/synchronization/MultipartProceed")]
  56. public async Task<HttpResponseMessage> MultipartProceed(string fileSystemName)
  57. {
  58. if (!Request.Content.IsMimeMultipartContent())
  59. throw new HttpResponseException(HttpStatusCode.UnsupportedMediaType);
  60. var fileName = Request.Headers.GetValues(SyncingMultipartConstants.FileName).FirstOrDefault();
  61. var tempFileName = RavenFileNameHelper.DownloadingFileName(fileName);
  62. var sourceServerInfo = InnerHeaders.Value<ServerInfo>(SyncingMultipartConstants.SourceServerInfo);
  63. var sourceFileETag = Guid.Parse(InnerHeaders.GetValues(Constants.MetadataEtagField).First().Trim('\"'));
  64. var report = new SynchronizationReport(fileName, sourceFileETag, SynchronizationType.ContentUpdate);
  65. Log.Debug("Starting to process multipart synchronization request of a file '{0}' with ETag {1} from {2}", fileName, sourceFileETag, sourceServerInfo);
  66. StorageStream localFile = null;
  67. var isNewFile = false;
  68. var isConflictResolved = false;
  69. try
  70. {
  71. Storage.Batch(accessor =>
  72. {
  73. AssertFileIsNotBeingSynced(fileName, accessor);
  74. FileLockManager.LockByCreatingSyncConfiguration(fileName, sourceServerInfo, accessor);
  75. });
  76. SynchronizationTask.IncomingSynchronizationStarted(fileName, sourceServerInfo, sourceFileETag, SynchronizationType.ContentUpdate);
  77. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Start);
  78. Storage.Batch(accessor => StartupProceed(fileName, accessor));
  79. RavenJObject sourceMetadata = GetFilteredMetadataFromHeaders(InnerHeaders);
  80. var localMetadata = GetLocalMetadata(fileName);
  81. if (localMetadata != null)
  82. {
  83. AssertConflictDetection(fileName, localMetadata, sourceMetadata, sourceServerInfo, out isConflictResolved);
  84. localFile = StorageStream.Reading(Storage, fileName);
  85. }
  86. else
  87. {
  88. isNewFile = true;
  89. }
  90. Historian.UpdateLastModified(sourceMetadata);
  91. var synchronizingFile = SynchronizingFileStream.CreatingOrOpeningAndWriting(Storage, Search, StorageOperationsTask, tempFileName, sourceMetadata);
  92. var provider = new MultipartSyncStreamProvider(synchronizingFile, localFile);
  93. Log.Debug("Starting to process/read multipart content of a file '{0}'", fileName);
  94. await Request.Content.ReadAsMultipartAsync(provider);
  95. Log.Debug("Multipart content of a file '{0}' was processed/read", fileName);
  96. report.BytesCopied = provider.BytesCopied;
  97. report.BytesTransfered = provider.BytesTransfered;
  98. report.NeedListLength = provider.NumberOfFileParts;
  99. synchronizingFile.PreventUploadComplete = false;
  100. synchronizingFile.Flush();
  101. synchronizingFile.Dispose();
  102. sourceMetadata["Content-MD5"] = synchronizingFile.FileHash;
  103. Storage.Batch(accessor => accessor.UpdateFileMetadata(tempFileName, sourceMetadata));
  104. Storage.Batch(accessor =>
  105. {
  106. StorageOperationsTask.IndicateFileToDelete(fileName);
  107. accessor.RenameFile(tempFileName, fileName);
  108. Search.Delete(tempFileName);
  109. Search.Index(fileName, sourceMetadata);
  110. });
  111. if (isNewFile)
  112. {
  113. Log.Debug("Temporary downloading file '{0}' was renamed to '{1}'. Indexes were updated.", tempFileName, fileName);
  114. }
  115. else
  116. {
  117. Log.Debug("Old file '{0}' was deleted. Indexes were updated.", fileName);
  118. }
  119. if (isConflictResolved)
  120. {
  121. ConflictArtifactManager.Delete(fileName);
  122. Publisher.Publish(new ConflictNotification
  123. {
  124. FileName = fileName,
  125. Status = ConflictStatus.Resolved
  126. });
  127. }
  128. }
  129. catch (Exception ex)
  130. {
  131. report.Exception = ex;
  132. }
  133. finally
  134. {
  135. if (localFile != null)
  136. {
  137. localFile.Dispose();
  138. }
  139. }
  140. if (report.Exception == null)
  141. {
  142. Log.Debug(
  143. "File '{0}' was synchronized successfully from {1}. {2} bytes were transfered and {3} bytes copied. Need list length was {4}",
  144. fileName, sourceServerInfo, report.BytesTransfered, report.BytesCopied, report.NeedListLength);
  145. }
  146. else
  147. {
  148. Log.WarnException(
  149. string.Format("Error has occurred during synchronization of a file '{0}' from {1}", fileName, sourceServerInfo),
  150. report.Exception);
  151. }
  152. FinishSynchronization(fileName, report, sourceServerInfo, sourceFileETag);
  153. PublishFileNotification(fileName, isNewFile ? FileChangeAction.Add : FileChangeAction.Update);
  154. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Finish);
  155. return this.GetMessageWithObject(report, HttpStatusCode.OK);
  156. }
  157. private void FinishSynchronization(string fileName, SynchronizationReport report, ServerInfo sourceServer, Guid sourceFileETag)
  158. {
  159. try
  160. {
  161. // we want to execute those operation in a single batch but we also have to ensure that
  162. // Raven/Synchronization/Sources/sourceServerId config is modified only by one finishing synchronization at the same time
  163. SynchronizationFinishLocks.GetOrAdd(sourceServer.Id, new ReaderWriterLockSlim()).EnterWriteLock();
  164. SynchronizationTask.IncomingSynchronizationFinished(fileName, sourceServer, sourceFileETag);
  165. Storage.Batch(accessor =>
  166. {
  167. SaveSynchronizationReport(fileName, accessor, report);
  168. FileLockManager.UnlockByDeletingSyncConfiguration(fileName, accessor);
  169. if (report.Exception == null)
  170. {
  171. SaveSynchronizationSourceInformation(sourceServer, sourceFileETag, accessor);
  172. }
  173. });
  174. }
  175. catch (Exception ex)
  176. {
  177. Log.ErrorException(
  178. string.Format("Failed to finish synchronization of a file '{0}' from {1}", fileName, sourceServer), ex);
  179. }
  180. finally
  181. {
  182. SynchronizationFinishLocks.GetOrAdd(sourceServer.Id, new ReaderWriterLockSlim()).ExitWriteLock();
  183. }
  184. }
  185. private void AssertConflictDetection(string fileName, RavenJObject localMetadata, RavenJObject sourceMetadata, ServerInfo sourceServer, out bool isConflictResolved)
  186. {
  187. var conflict = ConflictDetector.Check(fileName, localMetadata, sourceMetadata, sourceServer.FileSystemUrl);
  188. isConflictResolved = ConflictResolver.IsResolved(localMetadata, conflict);
  189. if (conflict != null && !isConflictResolved)
  190. {
  191. ConflictArtifactManager.Create(fileName, conflict);
  192. Publisher.Publish(new ConflictNotification
  193. {
  194. FileName = fileName,
  195. SourceServerUrl = sourceServer.FileSystemUrl,
  196. Status = ConflictStatus.Detected,
  197. RemoteFileHeader = new FileHeader(fileName, localMetadata)
  198. });
  199. Log.Debug(
  200. "File '{0}' is in conflict with synchronized version from {1} ({2}). File marked as conflicted, conflict configuration item created",
  201. fileName, sourceServer.FileSystemUrl, sourceServer.Id);
  202. throw new SynchronizationException(string.Format("File {0} is conflicted", fileName));
  203. }
  204. }
  205. private void StartupProceed(string fileName, IStorageActionsAccessor accessor)
  206. {
  207. // remove previous SyncResult
  208. DeleteSynchronizationReport(fileName, accessor);
  209. // remove previous .downloading file
  210. StorageOperationsTask.IndicateFileToDelete(RavenFileNameHelper.DownloadingFileName(fileName));
  211. }
  212. [HttpPost]
  213. [Route("fs/{fileSystemName}/synchronization/UpdateMetadata/{*fileName}")]
  214. public HttpResponseMessage UpdateMetadata(string fileSystemName, string fileName)
  215. {
  216. var sourceServerInfo = InnerHeaders.Value<ServerInfo>(SyncingMultipartConstants.SourceServerInfo);
  217. // REVIEW: (Oren) It works, but it seems to me it is not an scalable solution.
  218. var sourceFileETag = Guid.Parse(InnerHeaders.GetValues(Constants.MetadataEtagField).First().Trim('\"'));
  219. Log.Debug("Starting to update a metadata of file '{0}' with ETag {1} from {2} because of synchronization", fileName,
  220. sourceFileETag, sourceServerInfo);
  221. var report = new SynchronizationReport(fileName, sourceFileETag, SynchronizationType.MetadataUpdate);
  222. try
  223. {
  224. Storage.Batch(accessor =>
  225. {
  226. AssertFileIsNotBeingSynced(fileName, accessor);
  227. FileLockManager.LockByCreatingSyncConfiguration(fileName, sourceServerInfo, accessor);
  228. });
  229. SynchronizationTask.IncomingSynchronizationStarted(fileName, sourceServerInfo, sourceFileETag, SynchronizationType.MetadataUpdate);
  230. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Start);
  231. Storage.Batch(accessor => StartupProceed(fileName, accessor));
  232. var localMetadata = GetLocalMetadata(fileName);
  233. var sourceMetadata = GetFilteredMetadataFromHeaders(InnerHeaders);
  234. bool isConflictResolved;
  235. AssertConflictDetection(fileName, localMetadata, sourceMetadata, sourceServerInfo, out isConflictResolved);
  236. Historian.UpdateLastModified(sourceMetadata);
  237. Storage.Batch(accessor => accessor.UpdateFileMetadata(fileName, sourceMetadata));
  238. Search.Index(fileName, sourceMetadata);
  239. if (isConflictResolved)
  240. {
  241. ConflictArtifactManager.Delete(fileName);
  242. Publisher.Publish(new ConflictNotification
  243. {
  244. FileName = fileName,
  245. Status = ConflictStatus.Resolved
  246. });
  247. }
  248. PublishFileNotification(fileName, FileChangeAction.Update);
  249. }
  250. catch (Exception ex)
  251. {
  252. report.Exception = ex;
  253. Log.WarnException(
  254. string.Format("Error was occurred during metadata synchronization of file '{0}' from {1}", fileName,
  255. sourceServerInfo), ex);
  256. }
  257. finally
  258. {
  259. FinishSynchronization(fileName, report, sourceServerInfo, sourceFileETag);
  260. }
  261. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Finish);
  262. if (report.Exception == null)
  263. {
  264. Log.Debug("Metadata of file '{0}' was synchronized successfully from {1}", fileName, sourceServerInfo);
  265. }
  266. return this.GetMessageWithObject(report, HttpStatusCode.OK);
  267. }
  268. [HttpDelete]
  269. [Route("fs/{fileSystemName}/synchronization")]
  270. public HttpResponseMessage Delete(string fileSystemName, string fileName)
  271. {
  272. var sourceServerInfo = InnerHeaders.Value<ServerInfo>(SyncingMultipartConstants.SourceServerInfo);
  273. var sourceFileETag = Guid.Parse(InnerHeaders.GetValues(Constants.MetadataEtagField).First().Trim('\"'));
  274. Log.Debug("Starting to delete a file '{0}' with ETag {1} from {2} because of synchronization", fileName, sourceFileETag, sourceServerInfo);
  275. var report = new SynchronizationReport(fileName, sourceFileETag, SynchronizationType.Delete);
  276. try
  277. {
  278. Storage.Batch(accessor =>
  279. {
  280. AssertFileIsNotBeingSynced(fileName, accessor);
  281. FileLockManager.LockByCreatingSyncConfiguration(fileName, sourceServerInfo, accessor);
  282. });
  283. SynchronizationTask.IncomingSynchronizationStarted(fileName, sourceServerInfo, sourceFileETag, SynchronizationType.Delete);
  284. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Start);
  285. Storage.Batch(accessor => StartupProceed(fileName, accessor));
  286. var localMetadata = GetLocalMetadata(fileName);
  287. if (localMetadata != null)
  288. {
  289. // REVIEW: Use InnerHeaders for consistency?
  290. var sourceMetadata = GetFilteredMetadataFromHeaders(Request.Headers); // Request.Headers.FilterHeadersToObject();
  291. bool isConflictResolved;
  292. AssertConflictDetection(fileName, localMetadata, sourceMetadata, sourceServerInfo, out isConflictResolved);
  293. Storage.Batch(accessor =>
  294. {
  295. StorageOperationsTask.IndicateFileToDelete(fileName);
  296. var tombstoneMetadata = new RavenJObject
  297. {
  298. {
  299. SynchronizationConstants.RavenSynchronizationHistory,
  300. localMetadata[SynchronizationConstants.RavenSynchronizationHistory]
  301. },
  302. {
  303. SynchronizationConstants.RavenSynchronizationVersion,
  304. localMetadata[SynchronizationConstants.RavenSynchronizationVersion]
  305. },
  306. {
  307. SynchronizationConstants.RavenSynchronizationSource,
  308. localMetadata[SynchronizationConstants.RavenSynchronizationSource]
  309. }
  310. }.WithDeleteMarker();
  311. Historian.UpdateLastModified(tombstoneMetadata);
  312. accessor.PutFile(fileName, 0, tombstoneMetadata, true);
  313. });
  314. PublishFileNotification(fileName, FileChangeAction.Delete);
  315. }
  316. }
  317. catch (Exception ex)
  318. {
  319. report.Exception = ex;
  320. Log.WarnException(string.Format("Error was occurred during deletion synchronization of file '{0}' from {1}", fileName, sourceServerInfo), ex);
  321. }
  322. finally
  323. {
  324. FinishSynchronization(fileName, report, sourceServerInfo, sourceFileETag);
  325. }
  326. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Finish);
  327. if (report.Exception == null)
  328. {
  329. Log.Debug("File '{0}' was deleted during synchronization from {1}", fileName, sourceServerInfo);
  330. }
  331. return this.GetMessageWithObject(report, HttpStatusCode.OK);
  332. }
  333. [HttpPatch]
  334. [Route("fs/{fileSystemName}/synchronization/Rename")]
  335. public HttpResponseMessage Rename(string fileSystemName, string fileName, string rename)
  336. {
  337. var sourceServerInfo = InnerHeaders.Value<ServerInfo>(SyncingMultipartConstants.SourceServerInfo);
  338. var sourceFileETag = Guid.Parse(InnerHeaders.GetValues(Constants.MetadataEtagField).First().Trim('\"'));
  339. var sourceMetadata = GetFilteredMetadataFromHeaders(InnerHeaders);
  340. Log.Debug("Starting to rename a file '{0}' to '{1}' with ETag {2} from {3} because of synchronization", fileName,
  341. rename, sourceFileETag, sourceServerInfo);
  342. var report = new SynchronizationReport(fileName, sourceFileETag, SynchronizationType.Rename);
  343. try
  344. {
  345. Storage.Batch(accessor =>
  346. {
  347. AssertFileIsNotBeingSynced(fileName, accessor);
  348. FileLockManager.LockByCreatingSyncConfiguration(fileName, sourceServerInfo, accessor);
  349. });
  350. SynchronizationTask.IncomingSynchronizationStarted(fileName, sourceServerInfo, sourceFileETag, SynchronizationType.Rename);
  351. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Start);
  352. Storage.Batch(accessor => StartupProceed(fileName, accessor));
  353. var localMetadata = GetLocalMetadata(fileName);
  354. bool isConflictResolved;
  355. AssertConflictDetection(fileName, localMetadata, sourceMetadata, sourceServerInfo, out isConflictResolved);
  356. if (isConflictResolved)
  357. {
  358. ConflictArtifactManager.Delete(fileName);
  359. Publisher.Publish(new ConflictNotification
  360. {
  361. FileName = fileName,
  362. Status = ConflictStatus.Resolved
  363. });
  364. }
  365. StorageOperationsTask.RenameFile(new RenameFileOperation
  366. {
  367. FileSystem = FileSystem.Name,
  368. Name = fileName,
  369. Rename = rename,
  370. MetadataAfterOperation = sourceMetadata.WithETag(sourceFileETag).DropRenameMarkers()
  371. });
  372. }
  373. catch (Exception ex)
  374. {
  375. report.Exception = ex;
  376. Log.WarnException( string.Format("Error was occurred during renaming synchronization of file '{0}' from {1}", fileName, sourceServerInfo), ex);
  377. }
  378. finally
  379. {
  380. FinishSynchronization(fileName, report, sourceServerInfo, sourceFileETag);
  381. }
  382. PublishSynchronizationNotification(fileSystemName, fileName, sourceServerInfo, report.Type, SynchronizationAction.Finish);
  383. if (report.Exception == null)
  384. Log.Debug("File '{0}' was renamed to '{1}' during synchronization from {2}", fileName, rename, sourceServerInfo);
  385. return this.GetMessageWithObject(report, HttpStatusCode.OK);
  386. }
  387. [HttpPost]
  388. [Route("fs/{fileSystemName}/synchronization/Confirm")]
  389. public async Task<HttpResponseMessage> Confirm()
  390. {
  391. var contentStream = await Request.Content.ReadAsStreamAsync();
  392. var confirmingFiles =
  393. new JsonSerializer().Deserialize<IEnumerable<Tuple<string, Guid>>>(
  394. new JsonTextReader(new StreamReader(contentStream)));
  395. var result = confirmingFiles.Select(file => new SynchronizationConfirmation
  396. {
  397. FileName = file.Item1,
  398. Status = CheckSynchronizedFileStatus(file)
  399. });
  400. return this.GetMessageWithObject(result)
  401. .WithNoCache();
  402. }
  403. [HttpGet]
  404. [Route("fs/{fileSystemName}/synchronization/Status")]
  405. public HttpResponseMessage Status(string fileName)
  406. {
  407. var report = GetSynchronizationReport(fileName);
  408. return this.GetMessageWithObject(report)
  409. .WithNoCache();
  410. }
  411. [HttpGet]
  412. [Route("fs/{fileSystemName}/synchronization/Finished")]
  413. public HttpResponseMessage Finished()
  414. {
  415. ItemsPage<SynchronizationReport> page = null;
  416. Storage.Batch(accessor =>
  417. {
  418. var configs = accessor.GetConfigsStartWithPrefix(RavenFileNameHelper.SyncResultNamePrefix,
  419. Paging.Start, Paging.PageSize);
  420. int totalCount = 0;
  421. accessor.GetConfigNamesStartingWithPrefix(RavenFileNameHelper.SyncResultNamePrefix,
  422. Paging.Start, Paging.PageSize, out totalCount);
  423. var reports = configs.Select(config => config.JsonDeserialization<SynchronizationReport>()).ToList();
  424. page = new ItemsPage<SynchronizationReport>(reports, totalCount);
  425. });
  426. return this.GetMessageWithObject(page, HttpStatusCode.OK)
  427. .WithNoCache();
  428. }
  429. [HttpGet]
  430. [Route("fs/{fileSystemName}/synchronization/Active")]
  431. public HttpResponseMessage Active()
  432. {
  433. var result = new ItemsPage<SynchronizationDetails>(SynchronizationTask.Queue.Active
  434. .Skip(Paging.Start)
  435. .Take(Paging.PageSize),
  436. SynchronizationTask.Queue.GetTotalActiveTasks());
  437. return this.GetMessageWithObject(result, HttpStatusCode.OK)
  438. .WithNoCache();
  439. }
  440. [HttpGet]
  441. [Route("fs/{fileSystemName}/synchronization/Pending")]
  442. public HttpResponseMessage Pending()
  443. {
  444. var result = new ItemsPage<SynchronizationDetails>(SynchronizationTask.Queue.Pending
  445. .Skip(Paging.Start)
  446. .Take(Paging.PageSize),
  447. SynchronizationTask.Queue.GetTotalPendingTasks());
  448. return this.GetMessageWithObject(result, HttpStatusCode.OK)
  449. .WithNoCache();
  450. }
  451. [HttpGet]
  452. [Route("fs/{fileSystemName}/synchronization/Incoming")]
  453. public HttpResponseMessage Incoming()
  454. {
  455. var activeIncoming = SynchronizationTask.IncomingQueue;
  456. var result = new ItemsPage<SynchronizationDetails>(activeIncoming.Skip(Paging.Start)
  457. .Take(Paging.PageSize),
  458. activeIncoming.Count());
  459. return this.GetMessageWithObject(result, HttpStatusCode.OK)
  460. .WithNoCache();
  461. }
  462. [HttpGet]
  463. [Route("fs/{fileSystemName}/synchronization/Conflicts")]
  464. public HttpResponseMessage Conflicts()
  465. {
  466. ItemsPage<ConflictItem> page = null;
  467. Storage.Batch(accessor =>
  468. {
  469. var conflicts = accessor.GetConfigurationValuesStartWithPrefix<ConflictItem>(
  470. RavenFileNameHelper.ConflictConfigNamePrefix,
  471. Paging.PageSize * Paging.Start,
  472. Paging.PageSize).ToList();
  473. page = new ItemsPage<ConflictItem>(conflicts, conflicts.Count);
  474. });
  475. return this.GetMessageWithObject(page, HttpStatusCode.OK)
  476. .WithNoCache();
  477. }
  478. [HttpPatch]
  479. [Route("fs/{fileSystemName}/synchronization/ResolveConflict/{*fileName}")]
  480. public HttpResponseMessage ResolveConflict(string fileName, ConflictResolutionStrategy strategy)
  481. {
  482. Log.Debug("Resolving conflict of a file '{0}' by using {1} strategy", fileName, strategy);
  483. if (strategy == ConflictResolutionStrategy.CurrentVersion)
  484. {
  485. StrategyAsGetCurrent(fileName);
  486. }
  487. else if (strategy == ConflictResolutionStrategy.RemoteVersion)
  488. {
  489. StrategyAsGetRemote(fileName);
  490. }
  491. else
  492. {
  493. throw new NotSupportedException("NoOperation conflict resolution strategy, is not supported.");
  494. }
  495. return GetEmptyMessage(HttpStatusCode.NoContent);
  496. }
  497. [HttpPatch]
  498. [Route("fs/{fileSystemName}/synchronization/applyConflict/{*fileName}")]
  499. public async Task<HttpResponseMessage> ApplyConflict(string filename, long remoteVersion, string remoteServerId, string remoteServerUrl)
  500. {
  501. var localMetadata = GetLocalMetadata(filename);
  502. if (localMetadata == null)
  503. throw new HttpResponseException(HttpStatusCode.NotFound);
  504. var contentStream = await Request.Content.ReadAsStreamAsync();
  505. var current = new HistoryItem
  506. {
  507. ServerId = Storage.Id.ToString(),
  508. Version = localMetadata.Value<long>(SynchronizationConstants.RavenSynchronizationVersion)
  509. };
  510. var currentConflictHistory = Historian.DeserializeHistory(localMetadata);
  511. currentConflictHistory.Add(current);
  512. var remote = new HistoryItem
  513. {
  514. ServerId = remoteServerId,
  515. Version = remoteVersion
  516. };
  517. var remoteMetadata = RavenJObject.Load(new JsonTextReader(new StreamReader(contentStream)));
  518. var remoteConflictHistory = Historian.DeserializeHistory(remoteMetadata);
  519. remoteConflictHistory.Add(remote);
  520. var conflict = new ConflictItem
  521. {
  522. CurrentHistory = currentConflictHistory,
  523. RemoteHistory = remoteConflictHistory,
  524. FileName = filename,
  525. RemoteServerUrl = Uri.UnescapeDataString(remoteServerUrl)
  526. };
  527. ConflictArtifactManager.Create(filename, conflict);
  528. Publisher.Publish(new ConflictNotification
  529. {
  530. FileName = filename,
  531. SourceServerUrl = remoteServerUrl,
  532. Status = ConflictStatus.Detected,
  533. RemoteFileHeader = new FileHeader(filename, remoteMetadata)
  534. });
  535. Log.Debug("Conflict applied for a file '{0}' (remote version: {1}, remote server id: {2}).", filename, remoteVersion, remoteServerId);
  536. return GetEmptyMessage(HttpStatusCode.NoContent);
  537. }
  538. [HttpGet]
  539. [Route("fs/{fileSystemName}/synchronization/LastSynchronization")]
  540. public HttpResponseMessage LastSynchronization(Guid from)
  541. {
  542. SourceSynchronizationInformation lastEtag = null;
  543. Storage.Batch(accessor => lastEtag = GetLastSynchronization(from, accessor));
  544. Log.Debug("Got synchronization last ETag request from {0}: [{1}]", from, lastEtag);
  545. return this.GetMessageWithObject(lastEtag, HttpStatusCode.OK)
  546. .WithNoCache();
  547. }
  548. [HttpPost]
  549. [Route("fs/{fileSystemName}/synchronization/IncrementLastETag")]
  550. public HttpResponseMessage IncrementLastETag(Guid sourceServerId, string sourceFileSystemUrl, Guid sourceFileETag)
  551. {
  552. try
  553. {
  554. // we want to execute those operation in a single batch but we also have to ensure that
  555. // Raven/Synchronization/Sources/sourceServerId config is modified only by one finishing synchronization at the same time
  556. SynchronizationFinishLocks.GetOrAdd(sourceServerId, new ReaderWriterLockSlim()).EnterWriteLock();
  557. Storage.Batch(
  558. accessor =>
  559. SaveSynchronizationSourceInformation(new ServerInfo { Id = sourceServerId, FileSystemUrl = sourceFileSystemUrl }, sourceFileETag,
  560. accessor));
  561. }
  562. catch (Exception ex)
  563. {
  564. Log.ErrorException(
  565. string.Format("Failed to update last seen ETag from {0}", sourceServerId), ex);
  566. }
  567. finally
  568. {
  569. SynchronizationFinishLocks.GetOrAdd(sourceServerId, new ReaderWriterLockSlim()).ExitWriteLock();
  570. }
  571. return GetEmptyMessage(HttpStatusCode.OK);
  572. }
  573. private void PublishFileNotification(string fileName, FileChangeAction action)
  574. {
  575. Publisher.Publish(new FileChangeNotification
  576. {
  577. File = FilePathTools.Cannoicalise(fileName),
  578. Action = action
  579. });
  580. }
  581. private void PublishSynchronizationNotification(string fileSystemName, string fileName, ServerInfo sourceServer, SynchronizationType type, SynchronizationAction action)
  582. {
  583. Publisher.Publish(new SynchronizationUpdateNotification
  584. {
  585. FileName = fileName,
  586. SourceFileSystemUrl = sourceServer.FileSystemUrl,
  587. SourceServerId = sourceServer.Id,
  588. Type = type,
  589. Action = action,
  590. Direction = SynchronizationDirection.Incoming
  591. });
  592. }
  593. private void StrategyAsGetCurrent(string fileName)
  594. {
  595. Storage.Batch(accessor =>
  596. {
  597. var conflict = accessor.GetConfigurationValue<ConflictItem>(RavenFileNameHelper.ConflictConfigNameForFile(fileName));
  598. var localMetadata = accessor.GetFile(fileName, 0, 0).Metadata;
  599. var localHistory = Historian.DeserializeHistory(localMetadata);
  600. // incorporate remote version history into local
  601. foreach (var remoteHistoryItem in conflict.RemoteHistory.Where(remoteHistoryItem => !localHistory.Contains(remoteHistoryItem)))
  602. {
  603. localHistory.Add(remoteHistoryItem);
  604. }
  605. localMetadata[SynchronizationConstants.RavenSynchronizationHistory] = Historian.SerializeHistory(localHistory);
  606. accessor.UpdateFileMetadata(fileName, localMetadata);
  607. ConflictArtifactManager.Delete(fileName, accessor);
  608. Publisher.Publish(new ConflictNotification
  609. {
  610. FileName = fileName,
  611. Status = ConflictStatus.Resolved
  612. });
  613. });
  614. }
  615. private void StrategyAsGetRemote(string fileName)
  616. {
  617. Storage.Batch(
  618. accessor =>
  619. {
  620. var localMetadata = accessor.GetFile(fileName, 0, 0).Metadata;
  621. var conflictConfigName = RavenFileNameHelper.ConflictConfigNameForFile(fileName);
  622. var conflictItem = accessor.GetConfig(conflictConfigName).JsonDeserialization<ConflictItem>();
  623. var conflictResolution = new ConflictResolution
  624. {
  625. Strategy = ConflictResolutionStrategy.RemoteVersion,
  626. RemoteServerId = conflictItem.RemoteHistory.Last().ServerId,
  627. Version = conflictItem.RemoteHistory.Last().Version,
  628. };
  629. localMetadata[SynchronizationConstants.RavenSynchronizationConflictResolution] = JsonExtensions.ToJObject(conflictResolution);
  630. accessor.UpdateFileMetadata(fileName, localMetadata);
  631. });
  632. Task.Run(() => SynchronizationTask.SynchronizeDestinationsAsync(true));
  633. }
  634. private FileStatus CheckSynchronizedFileStatus(Tuple<string, Guid> fileInfo)
  635. {
  636. var report = GetSynchronizationReport(fileInfo.Item1);
  637. if (report == null || report.FileETag != fileInfo.Item2)
  638. return FileStatus.Unknown;
  639. return report.Exception == null ? FileStatus.Safe : FileStatus.Broken;
  640. }
  641. private void SaveSynchronizationReport(string fileName, IStorageActionsAccessor accessor, SynchronizationReport report)
  642. {
  643. var name = RavenFileNameHelper.SyncResultNameForFile(fileName);
  644. accessor.SetConfig(name, JsonExtensions.ToJObject(report));
  645. }
  646. private void DeleteSynchronizationReport(string fileName, IStorageActionsAccessor accessor)
  647. {
  648. var name = RavenFileNameHelper.SyncResultNameForFile(fileName);
  649. accessor.DeleteConfig(name);
  650. Search.Delete(name);
  651. }
  652. private SynchronizationReport GetSynchronizationReport(string fileName)
  653. {
  654. SynchronizationReport preResult = null;
  655. Storage.Batch(
  656. accessor =>
  657. {
  658. try
  659. {
  660. var name = RavenFileNameHelper.SyncResultNameForFile(fileName);
  661. preResult = accessor.GetConfig(name).JsonDeserialization<SynchronizationReport>();
  662. }
  663. catch (FileNotFoundException)
  664. {
  665. // just ignore
  666. }
  667. });
  668. return preResult;
  669. }
  670. private RavenJObject GetLocalMetadata(string fileName)
  671. {
  672. RavenJObject result = null;
  673. try
  674. {
  675. Storage.Batch(accessor => { result = accessor.GetFile(fileName, 0, 0).Metadata; });
  676. }
  677. catch (FileNotFoundException)
  678. {
  679. return null;
  680. }
  681. if (result.ContainsKey(SynchronizationConstants.RavenDeleteMarker))
  682. {
  683. return null;
  684. }
  685. return result;
  686. }
  687. private SourceSynchronizationInformation GetLastSynchronization(Guid from, IStorageActionsAccessor accessor)
  688. {
  689. SourceSynchronizationInformation info;
  690. try
  691. {
  692. info = accessor.GetConfig(SynchronizationConstants.RavenSynchronizationSourcesBasePath + "/" + from)
  693. .JsonDeserialization<SourceSynchronizationInformation>();
  694. }
  695. catch (FileNotFoundException)
  696. {
  697. info = new SourceSynchronizationInformation
  698. {
  699. LastSourceFileEtag = Guid.Empty,
  700. DestinationServerId = Storage.Id
  701. };
  702. }
  703. return info;
  704. }
  705. private void SaveSynchronizationSourceInformation(ServerInfo sourceServer, Guid lastSourceEtag, IStorageActionsAccessor accessor)
  706. {
  707. var lastSynchronizationInformation = GetLastSynchronization(sourceServer.Id, accessor);
  708. if (Buffers.Compare(lastSynchronizationInformation.LastSourceFileEtag.ToByteArray(), lastSourceEtag.ToByteArray()) > 0)
  709. {
  710. return;
  711. }
  712. var synchronizationSourceInfo = new SourceSynchronizationInformation
  713. {
  714. LastSourceFileEtag = lastSourceEtag,
  715. SourceServerUrl = sourceServer.FileSystemUrl,
  716. DestinationServerId = Storage.Id
  717. };
  718. var key = SynchronizationConstants.RavenSynchronizationSourcesBasePath + "/" + sourceServer.Id;
  719. accessor.SetConfig(key, JsonExtensions.ToJObject(synchronizationSourceInfo));
  720. Log.Debug("Saved last synchronized file ETag {0} from {1} ({2})", lastSourceEtag, sourceServer.FileSystemUrl, sourceServer.Id);
  721. }
  722. }
  723. }