PageRenderTime 53ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Bundles/Replication/Controllers/ReplicationController.cs

https://github.com/nwendel/ravendb
C# | 437 lines | 370 code | 62 blank | 5 comment | 51 complexity | 643cd9dcc132368c31fe5e9d616aea50 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.Generic;
  3. using System.ComponentModel.Composition;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Net.Http;
  7. using System.Threading.Tasks;
  8. using System.Web.Http;
  9. using Raven.Abstractions.Data;
  10. using Raven.Abstractions.Extensions;
  11. using Raven.Abstractions.Logging;
  12. using Raven.Abstractions.Replication;
  13. using Raven.Bundles.Replication.Data;
  14. using Raven.Bundles.Replication.Plugins;
  15. using Raven.Bundles.Replication.Responders;
  16. using Raven.Bundles.Replication.Tasks;
  17. using Raven.Database.Bundles.Replication.Plugins;
  18. using Raven.Database.Bundles.Replication.Utils;
  19. using Raven.Database.Server.Controllers;
  20. using Raven.Database.Storage;
  21. using Raven.Database.Util;
  22. using Raven.Json.Linq;
  23. namespace Raven.Database.Bundles.Replication.Controllers
  24. {
  25. public class ReplicationController : BundlesApiController
  26. {
  27. private static readonly ILog log = LogManager.GetCurrentClassLogger();
  28. public override string BundleName
  29. {
  30. get { return "replication"; }
  31. }
  32. private ReplicationTask replicationTask;
  33. public ReplicationTask ReplicationTask
  34. {
  35. get { return replicationTask ?? (replicationTask = Database.StartupTasks.OfType<ReplicationTask>().FirstOrDefault()); }
  36. }
  37. public IEnumerable<AbstractDocumentReplicationConflictResolver> DocsReplicationConflictResolvers
  38. {
  39. get
  40. {
  41. var exported = Database.Configuration.Container.GetExportedValues<AbstractDocumentReplicationConflictResolver>();
  42. var config = GetReplicationConfig();
  43. if (config == null || config.DocumentConflictResolution == StraightforwardConflictResolution.None)
  44. return exported;
  45. var withConfiguredResolvers = exported.ToList();
  46. switch (config.DocumentConflictResolution)
  47. {
  48. case StraightforwardConflictResolution.ResolveToLocal:
  49. withConfiguredResolvers.Add(LocalDocumentReplicationConflictResolver.Instance);
  50. break;
  51. case StraightforwardConflictResolution.ResolveToRemote:
  52. withConfiguredResolvers.Add(RemoteDocumentReplicationConflictResolver.Instance);
  53. break;
  54. case StraightforwardConflictResolution.ResolveToLatest:
  55. withConfiguredResolvers.Add(LatestDocumentReplicationConflictResolver.Instance);
  56. break;
  57. default:
  58. throw new ArgumentOutOfRangeException("config.DocumentConflictResolution");
  59. }
  60. return withConfiguredResolvers;
  61. }
  62. }
  63. public IEnumerable<AbstractAttachmentReplicationConflictResolver> AttachmentReplicationConflictResolvers
  64. {
  65. get
  66. {
  67. var exported = Database.Configuration.Container.GetExportedValues<AbstractAttachmentReplicationConflictResolver>();
  68. var config = GetReplicationConfig();
  69. if (config == null || config.AttachmentConflictResolution == StraightforwardConflictResolution.None)
  70. return exported;
  71. var withConfiguredResolvers = exported.ToList();
  72. switch (config.AttachmentConflictResolution)
  73. {
  74. case StraightforwardConflictResolution.ResolveToLocal:
  75. withConfiguredResolvers.Add(LocalAttachmentReplicationConflictResolver.Instance);
  76. break;
  77. case StraightforwardConflictResolution.ResolveToRemote:
  78. withConfiguredResolvers.Add(RemoteAttachmentReplicationConflictResolver.Instance);
  79. break;
  80. case StraightforwardConflictResolution.ResolveToLatest:
  81. // ignore this resolver for attachments
  82. break;
  83. default:
  84. throw new ArgumentOutOfRangeException("config.AttachmentConflictResolution");
  85. }
  86. return withConfiguredResolvers;
  87. }
  88. }
  89. [HttpPost]
  90. [Route("replication/replicateDocs")]
  91. [Route("databases/{databaseName}/replication/replicateDocs")]
  92. public async Task<HttpResponseMessage> DocReplicatePost()
  93. {
  94. var src = GetQueryStringValue("from");
  95. if (string.IsNullOrEmpty(src))
  96. return GetEmptyMessage(HttpStatusCode.BadRequest);
  97. while (src.EndsWith("/"))
  98. src = src.Substring(0, src.Length - 1);// remove last /, because that has special meaning for Raven
  99. if (string.IsNullOrEmpty(src))
  100. return GetEmptyMessage(HttpStatusCode.BadRequest);
  101. var array = await ReadJsonArrayAsync();
  102. if (ReplicationTask != null)
  103. ReplicationTask.HandleHeartbeat(src);
  104. using (Database.DisableAllTriggersForCurrentThread())
  105. {
  106. Database.TransactionalStorage.Batch(actions =>
  107. {
  108. string lastEtag = Etag.Empty.ToString();
  109. foreach (RavenJObject document in array)
  110. {
  111. var metadata = document.Value<RavenJObject>("@metadata");
  112. if (metadata[Constants.RavenReplicationSource] == null)
  113. {
  114. // not sure why, old document from when the user didn't have replication
  115. // that we suddenly decided to replicate, choose the source for that
  116. metadata[Constants.RavenReplicationSource] = RavenJToken.FromObject(src);
  117. }
  118. lastEtag = metadata.Value<string>("@etag");
  119. var id = metadata.Value<string>("@id");
  120. document.Remove("@metadata");
  121. ReplicateDocument(actions, id, metadata, document, src);
  122. }
  123. var replicationDocKey = Constants.RavenReplicationSourcesBasePath + "/" + src;
  124. var replicationDocument = Database.Documents.Get(replicationDocKey, null);
  125. var lastAttachmentId = Etag.Empty;
  126. if (replicationDocument != null)
  127. {
  128. lastAttachmentId =
  129. replicationDocument.DataAsJson.JsonDeserialization<SourceReplicationInformation>().
  130. LastAttachmentEtag;
  131. }
  132. Guid serverInstanceId;
  133. if (Guid.TryParse(GetQueryStringValue("dbid"), out serverInstanceId) == false)
  134. serverInstanceId = Database.TransactionalStorage.Id;
  135. Database.Documents.Put(replicationDocKey, null,
  136. RavenJObject.FromObject(new SourceReplicationInformation
  137. {
  138. Source = src,
  139. LastDocumentEtag = Etag.Parse(lastEtag),
  140. LastAttachmentEtag = lastAttachmentId,
  141. ServerInstanceId = serverInstanceId
  142. }),
  143. new RavenJObject(), null);
  144. });
  145. }
  146. return GetEmptyMessage();
  147. }
  148. [HttpPost]
  149. [Route("replication/replicateAttachments")]
  150. [Route("databases/{databaseName}/replication/replicateAttachments")]
  151. public async Task<HttpResponseMessage> AttachmentReplicatePost()
  152. {
  153. var src = GetQueryStringValue("from");
  154. if (string.IsNullOrEmpty(src))
  155. return GetEmptyMessage(HttpStatusCode.BadRequest);
  156. while (src.EndsWith("/"))
  157. src = src.Substring(0, src.Length - 1);// remove last /, because that has special meaning for Raven
  158. if (string.IsNullOrEmpty(src))
  159. return GetEmptyMessage(HttpStatusCode.BadRequest);
  160. var array = await ReadBsonArrayAsync();
  161. using (Database.DisableAllTriggersForCurrentThread())
  162. {
  163. Database.TransactionalStorage.Batch(actions =>
  164. {
  165. Etag lastEtag = Etag.Empty;
  166. foreach (RavenJObject attachment in array)
  167. {
  168. var metadata = attachment.Value<RavenJObject>("@metadata");
  169. if (metadata[Constants.RavenReplicationSource] == null)
  170. {
  171. // not sure why, old attachment from when the user didn't have replication
  172. // that we suddenly decided to replicate, choose the source for that
  173. metadata[Constants.RavenReplicationSource] = RavenJToken.FromObject(src);
  174. }
  175. lastEtag = Etag.Parse(attachment.Value<byte[]>("@etag"));
  176. var id = attachment.Value<string>("@id");
  177. ReplicateAttachment(actions, id, metadata, attachment.Value<byte[]>("data"), src);
  178. }
  179. var replicationDocKey = Constants.RavenReplicationSourcesBasePath + "/" + src;
  180. var replicationDocument = Database.Documents.Get(replicationDocKey, null);
  181. Etag lastDocId = null;
  182. if (replicationDocument != null)
  183. {
  184. lastDocId =
  185. replicationDocument.DataAsJson.JsonDeserialization<SourceReplicationInformation>().
  186. LastDocumentEtag;
  187. }
  188. Guid serverInstanceId;
  189. if (Guid.TryParse(GetQueryStringValue("dbid"), out serverInstanceId) == false)
  190. serverInstanceId = Database.TransactionalStorage.Id;
  191. Database.Documents.Put(replicationDocKey, null,
  192. RavenJObject.FromObject(new SourceReplicationInformation
  193. {
  194. Source = src,
  195. LastDocumentEtag = lastDocId,
  196. LastAttachmentEtag = lastEtag,
  197. ServerInstanceId = serverInstanceId
  198. }),
  199. new RavenJObject(), null);
  200. });
  201. }
  202. return GetEmptyMessage();
  203. }
  204. [HttpGet]
  205. [HttpPost]
  206. [Route("replication/info")]
  207. [Route("databases/{databaseName}/replication/info")]
  208. public HttpResponseMessage ReplicationInfoGet()
  209. {
  210. var replicationStatistics = ReplicationUtils.GetReplicationInformation(Database);
  211. return GetMessageWithObject(replicationStatistics);
  212. }
  213. [HttpGet]
  214. [Route("replication/lastEtag")]
  215. [Route("databases/{databaseName}/replication/lastEtag")]
  216. public HttpResponseMessage ReplicationLastEtagGet()
  217. {
  218. string src;
  219. string dbid;
  220. var result = GetValuesForLastEtag(out src, out dbid);
  221. if (result != null)
  222. return result;
  223. using (Database.DisableAllTriggersForCurrentThread())
  224. {
  225. var document = Database.Documents.Get(Constants.RavenReplicationSourcesBasePath + "/" + src, null);
  226. SourceReplicationInformation sourceReplicationInformation;
  227. var serverInstanceId = Database.TransactionalStorage.Id; // this is my id, sent to the remote serve
  228. if (document == null)
  229. {
  230. sourceReplicationInformation = new SourceReplicationInformation()
  231. {
  232. Source = src,
  233. ServerInstanceId = serverInstanceId
  234. };
  235. }
  236. else
  237. {
  238. sourceReplicationInformation = document.DataAsJson.JsonDeserialization<SourceReplicationInformation>();
  239. sourceReplicationInformation.ServerInstanceId = serverInstanceId;
  240. }
  241. var currentEtag = GetQueryStringValue("currentEtag");
  242. Log.Debug(() => string.Format("Got replication last etag request from {0}: [Local: {1} Remote: {2}]", src, sourceReplicationInformation.LastDocumentEtag, currentEtag));
  243. return GetMessageWithObject(sourceReplicationInformation);
  244. }
  245. }
  246. [HttpPut]
  247. [Route("replication/lastEtag")]
  248. [Route("databases/{databaseName}/replication/lastEtag")]
  249. public HttpResponseMessage ReplicationLastEtagPut()
  250. {
  251. string src;
  252. string dbid;
  253. var result = GetValuesForLastEtag(out src, out dbid);
  254. if (result != null)
  255. return result;
  256. using (Database.DisableAllTriggersForCurrentThread())
  257. {
  258. var document = Database.Documents.Get(Constants.RavenReplicationSourcesBasePath + "/" + src, null);
  259. SourceReplicationInformation sourceReplicationInformation;
  260. Etag docEtag = null, attachmentEtag = null;
  261. try
  262. {
  263. docEtag = Etag.Parse(GetQueryStringValue("docEtag"));
  264. }
  265. catch
  266. {
  267. }
  268. try
  269. {
  270. attachmentEtag = Etag.Parse(GetQueryStringValue("attachmentEtag"));
  271. }
  272. catch
  273. {
  274. }
  275. Guid serverInstanceId;
  276. if (Guid.TryParse(dbid, out serverInstanceId) == false)
  277. serverInstanceId = Database.TransactionalStorage.Id;
  278. if (document == null)
  279. {
  280. sourceReplicationInformation = new SourceReplicationInformation()
  281. {
  282. ServerInstanceId = serverInstanceId,
  283. LastAttachmentEtag = attachmentEtag ?? Etag.Empty,
  284. LastDocumentEtag = docEtag ?? Etag.Empty,
  285. Source = src
  286. };
  287. }
  288. else
  289. {
  290. sourceReplicationInformation = document.DataAsJson.JsonDeserialization<SourceReplicationInformation>();
  291. sourceReplicationInformation.ServerInstanceId = serverInstanceId;
  292. sourceReplicationInformation.LastDocumentEtag = docEtag ?? sourceReplicationInformation.LastDocumentEtag;
  293. sourceReplicationInformation.LastAttachmentEtag = attachmentEtag ?? sourceReplicationInformation.LastAttachmentEtag;
  294. }
  295. var etag = document == null ? Etag.Empty : document.Etag;
  296. var metadata = document == null ? new RavenJObject() : document.Metadata;
  297. var newDoc = RavenJObject.FromObject(sourceReplicationInformation);
  298. log.Debug("Updating replication last etags from {0}: [doc: {1} attachment: {2}]", src,
  299. sourceReplicationInformation.LastDocumentEtag,
  300. sourceReplicationInformation.LastAttachmentEtag);
  301. Database.Documents.Put(Constants.RavenReplicationSourcesBasePath + "/" + src, etag, newDoc, metadata, null);
  302. }
  303. return GetEmptyMessage();
  304. }
  305. [HttpPost]
  306. [Route("replication/heartbeat")]
  307. [Route("databases/{databaseName}/replication/heartbeat")]
  308. public HttpResponseMessage HeartbeatPost()
  309. {
  310. var src = GetQueryStringValue("from");
  311. var replicationTask = Database.StartupTasks.OfType<ReplicationTask>().FirstOrDefault();
  312. if (replicationTask == null)
  313. {
  314. return GetMessageWithObject(new
  315. {
  316. Error = "Cannot find replication task setup in the database"
  317. }, HttpStatusCode.NotFound);
  318. }
  319. replicationTask.HandleHeartbeat(src);
  320. return GetEmptyMessage();
  321. }
  322. private HttpResponseMessage GetValuesForLastEtag(out string src, out string dbid)
  323. {
  324. src = GetQueryStringValue("from");
  325. dbid = GetQueryStringValue("dbid");
  326. if (dbid == Database.TransactionalStorage.Id.ToString())
  327. throw new InvalidOperationException("Both source and target databases have database id = " + dbid +
  328. "\r\nDatabase cannot replicate to itself.");
  329. if (string.IsNullOrEmpty(src))
  330. return GetEmptyMessage(HttpStatusCode.BadRequest);
  331. while (src.EndsWith("/"))
  332. src = src.Substring(0, src.Length - 1); // remove last /, because that has special meaning for Raven
  333. if (string.IsNullOrEmpty(src))
  334. return GetEmptyMessage(HttpStatusCode.BadRequest);
  335. return null;
  336. }
  337. private void ReplicateDocument(IStorageActionsAccessor actions, string id, RavenJObject metadata, RavenJObject document, string src)
  338. {
  339. new DocumentReplicationBehavior
  340. {
  341. Actions = actions,
  342. Database = Database,
  343. ReplicationConflictResolvers = DocsReplicationConflictResolvers,
  344. Src = src
  345. }.Replicate(id, metadata, document);
  346. }
  347. private void ReplicateAttachment(IStorageActionsAccessor actions, string id, RavenJObject metadata, byte[] data, string src)
  348. {
  349. new AttachmentReplicationBehavior
  350. {
  351. Actions = actions,
  352. Database = Database,
  353. ReplicationConflictResolvers = AttachmentReplicationConflictResolvers,
  354. Src = src
  355. }.Replicate(id, metadata, data);
  356. }
  357. private ReplicationConfig GetReplicationConfig()
  358. {
  359. var configDoc = Database.Documents.Get(Constants.RavenReplicationConfig, null);
  360. if (configDoc == null)
  361. return null;
  362. ReplicationConfig config;
  363. try
  364. {
  365. config = configDoc.DataAsJson.JsonDeserialization<ReplicationConfig>();
  366. return config;
  367. }
  368. catch (Exception e)
  369. {
  370. Log.Warn("Could not deserialize a replication config", e);
  371. return null;
  372. }
  373. }
  374. }
  375. }