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