PageRenderTime 40ms CodeModel.GetById 12ms RepoModel.GetById 1ms app.codeStats 0ms

/Raven.Database/Server/Tenancy/AbstractLandlord.cs

https://github.com/nwendel/ravendb
C# | 304 lines | 264 code | 35 blank | 5 comment | 47 complexity | c2a31a4489e5639f6094cae45e6ab405 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.Collections.Specialized;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Security.Cryptography;
  8. using System.Security.Principal;
  9. using System.Text;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using Raven.Abstractions;
  13. using Raven.Abstractions.Data;
  14. using Raven.Abstractions.Logging;
  15. using Raven.Abstractions.Util;
  16. using Raven.Database.Config;
  17. using Raven.Database.Extensions;
  18. using Raven.Database.Impl;
  19. using Raven.Database.Server.Connections;
  20. using Raven.Database.Server.Security;
  21. using Raven.Database.Util;
  22. using Raven.Json.Linq;
  23. namespace Raven.Database.Server.Tenancy
  24. {
  25. public abstract class AbstractLandlord<TResource> : IDisposable
  26. where TResource : IDisposable
  27. {
  28. protected static readonly ILog Logger = LogManager.GetCurrentClassLogger();
  29. public event Action<InMemoryRavenConfiguration> SetupTenantConfiguration = delegate { };
  30. protected readonly ConcurrentSet<string> Locks = new ConcurrentSet<string>(StringComparer.OrdinalIgnoreCase);
  31. public readonly AtomicDictionary<Task<TResource>> ResourcesStoresCache =
  32. new AtomicDictionary<Task<TResource>>(StringComparer.OrdinalIgnoreCase);
  33. public readonly ConcurrentDictionary<string, DateTime> LastRecentlyUsed = new ConcurrentDictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase);
  34. public event Action<string> CleanupOccured;
  35. protected readonly ConcurrentDictionary<string, TransportState> ResourseTransportStates = new ConcurrentDictionary<string, TransportState>(StringComparer.OrdinalIgnoreCase);
  36. public abstract string ResourcePrefix { get; }
  37. public IEnumerable<TransportState> GetUserAllowedTransportStates(IPrincipal user, DocumentDatabase systemDatabase, AnonymousUserAccessMode annonymouseUserAccessMode, MixedModeRequestAuthorizer mixedModeRequestAuthorizer, string authHeader)
  38. {
  39. foreach (var resourceName in GetUserAllowedResourcesByPrefix(user, systemDatabase, annonymouseUserAccessMode, mixedModeRequestAuthorizer, authHeader))
  40. {
  41. TransportState curTransportState;
  42. if (ResourseTransportStates.TryGetValue(resourceName, out curTransportState))
  43. yield return curTransportState;
  44. }
  45. }
  46. public string[] GetUserAllowedResourcesByPrefix( IPrincipal user, DocumentDatabase systemDatabase, AnonymousUserAccessMode annonymouseUserAccessMode, MixedModeRequestAuthorizer mixedModeRequestAuthorizer, string authHeader)
  47. {
  48. List<string> approvedResources = null;
  49. var nextPageStart = 0;
  50. var resources = systemDatabase.Documents
  51. .GetDocumentsWithIdStartingWith(ResourcePrefix, null, null, 0,
  52. systemDatabase.Configuration.MaxPageSize, CancellationToken.None, ref nextPageStart);
  53. var reourcesNames = resources
  54. .Select(database =>
  55. database.Value<RavenJObject>("@metadata").Value<string>("@id").Replace(ResourcePrefix, string.Empty)).ToArray();
  56. if (annonymouseUserAccessMode == AnonymousUserAccessMode.None)
  57. {
  58. if (user == null)
  59. return null;
  60. bool isAdministrator=false;
  61. if (user is MixedModeRequestAuthorizer.OneTimetokenPrincipal)
  62. {
  63. var oneTimePrincipal = user as MixedModeRequestAuthorizer.OneTimetokenPrincipal;
  64. if (oneTimePrincipal != null)
  65. {
  66. isAdministrator = oneTimePrincipal.IsAdministratorInAnonymouseMode;
  67. }
  68. }
  69. else
  70. {
  71. isAdministrator = user.IsAdministrator(annonymouseUserAccessMode);
  72. }
  73. if (isAdministrator == false)
  74. {
  75. var authorizer = mixedModeRequestAuthorizer;
  76. approvedResources = authorizer.GetApprovedResources(user, authHeader, reourcesNames);
  77. }
  78. }
  79. if (approvedResources != null)
  80. {
  81. reourcesNames = reourcesNames.Where(resourceName => approvedResources.Contains(resourceName)).ToArray();
  82. }
  83. return reourcesNames;
  84. }
  85. public void Unprotect(DatabaseDocument databaseDocument)
  86. {
  87. if (databaseDocument.SecuredSettings == null)
  88. {
  89. databaseDocument.SecuredSettings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  90. return;
  91. }
  92. foreach (var prop in databaseDocument.SecuredSettings.ToList())
  93. {
  94. if (prop.Value == null)
  95. continue;
  96. var bytes = Convert.FromBase64String(prop.Value);
  97. var entrophy = Encoding.UTF8.GetBytes(prop.Key);
  98. try
  99. {
  100. var unprotectedValue = ProtectedData.Unprotect(bytes, entrophy, DataProtectionScope.CurrentUser);
  101. databaseDocument.SecuredSettings[prop.Key] = Encoding.UTF8.GetString(unprotectedValue);
  102. }
  103. catch (Exception e)
  104. {
  105. Logger.WarnException("Could not unprotect secured db data " + prop.Key + " setting the value to '<data could not be decrypted>'", e);
  106. databaseDocument.SecuredSettings[prop.Key] = "<data could not be decrypted>";
  107. }
  108. }
  109. }
  110. public void Cleanup(string resource, bool skipIfActive, Func<TResource,bool> shouldSkip = null, DocumentChangeTypes notificationType = DocumentChangeTypes.None)
  111. {
  112. using (ResourcesStoresCache.WithAllLocks())
  113. {
  114. DateTime time;
  115. Task<TResource> resourceTask;
  116. if (ResourcesStoresCache.TryGetValue(resource, out resourceTask) == false)
  117. {
  118. LastRecentlyUsed.TryRemove(resource, out time);
  119. return;
  120. }
  121. if (resourceTask.Status == TaskStatus.Faulted || resourceTask.Status == TaskStatus.Canceled)
  122. {
  123. LastRecentlyUsed.TryRemove(resource, out time);
  124. ResourcesStoresCache.TryRemove(resource, out resourceTask);
  125. return;
  126. }
  127. if (resourceTask.Status != TaskStatus.RanToCompletion)
  128. {
  129. return; // still starting up
  130. }
  131. var database = resourceTask.Result;
  132. if ((skipIfActive &&
  133. (SystemTime.UtcNow - LastWork(database)).TotalMinutes < 10) ||
  134. (shouldSkip != null && shouldSkip(database)))
  135. {
  136. // this document might not be actively working with user, but it is actively doing indexes, we will
  137. // wait with unloading this database until it hasn't done indexing for a while.
  138. // This prevent us from shutting down big databases that have been left alone to do indexing work.
  139. return;
  140. }
  141. try
  142. {
  143. database.Dispose();
  144. }
  145. catch (Exception e)
  146. {
  147. Logger.ErrorException("Could not cleanup tenant database: " + resource, e);
  148. return;
  149. }
  150. LastRecentlyUsed.TryRemove(resource, out time);
  151. ResourcesStoresCache.TryRemove(resource, out resourceTask);
  152. if (notificationType == DocumentChangeTypes.Delete)
  153. {
  154. TransportState transportState;
  155. ResourseTransportStates.TryRemove(resource, out transportState);
  156. if (transportState != null)
  157. {
  158. transportState.Dispose();
  159. }
  160. }
  161. var onResourceCleanupOccured = CleanupOccured;
  162. if (onResourceCleanupOccured != null)
  163. onResourceCleanupOccured(resource);
  164. }
  165. }
  166. protected abstract DateTime LastWork(TResource resource);
  167. public void Protect(DatabaseDocument databaseDocument)
  168. {
  169. if (databaseDocument.SecuredSettings == null)
  170. {
  171. databaseDocument.SecuredSettings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  172. return;
  173. }
  174. foreach (var prop in databaseDocument.SecuredSettings.ToList())
  175. {
  176. if (prop.Value == null)
  177. continue;
  178. var bytes = Encoding.UTF8.GetBytes(prop.Value);
  179. var entrophy = Encoding.UTF8.GetBytes(prop.Key);
  180. var protectedValue = ProtectedData.Protect(bytes, entrophy, DataProtectionScope.CurrentUser);
  181. databaseDocument.SecuredSettings[prop.Key] = Convert.ToBase64String(protectedValue);
  182. }
  183. }
  184. protected InMemoryRavenConfiguration CreateConfiguration(
  185. string tenantId,
  186. DatabaseDocument document,
  187. string folderPropName,
  188. InMemoryRavenConfiguration parentConfiguration)
  189. {
  190. var config = new InMemoryRavenConfiguration
  191. {
  192. Settings = new NameValueCollection(parentConfiguration.Settings),
  193. };
  194. SetupTenantConfiguration(config);
  195. config.CustomizeValuesForTenant(tenantId);
  196. config.Settings["Raven/StorageEngine"] = parentConfiguration.DefaultStorageTypeName;
  197. foreach (var setting in document.Settings)
  198. {
  199. config.Settings[setting.Key] = setting.Value;
  200. }
  201. Unprotect(document);
  202. foreach (var securedSetting in document.SecuredSettings)
  203. {
  204. config.Settings[securedSetting.Key] = securedSetting.Value;
  205. }
  206. config.Settings[folderPropName] = config.Settings[folderPropName].ToFullPath(parentConfiguration.DataDirectory);
  207. config.Settings["Raven/VirtualDir"] = config.Settings["Raven/VirtualDir"] + "/" + tenantId;
  208. config.DatabaseName = tenantId;
  209. config.IsTenantDatabase = true;
  210. config.Initialize();
  211. config.CopyParentSettings(parentConfiguration);
  212. return config;
  213. }
  214. public void Lock(string tenantId, Action actionToTake)
  215. {
  216. if (Locks.TryAdd(tenantId) == false)
  217. throw new InvalidOperationException(tenantId + "' is currently locked and cannot be accessed");
  218. try
  219. {
  220. Cleanup(tenantId, false);
  221. actionToTake();
  222. }
  223. finally
  224. {
  225. Locks.TryRemove(tenantId);
  226. }
  227. }
  228. public void Dispose()
  229. {
  230. var exceptionAggregator = new ExceptionAggregator(Logger, "Failure to dispose landlord");
  231. exceptionAggregator.Execute(() =>
  232. {
  233. foreach (var databaseTransportState in ResourseTransportStates)
  234. {
  235. databaseTransportState.Value.Dispose();
  236. }
  237. });
  238. using (ResourcesStoresCache.WithAllLocks())
  239. {
  240. // shut down all databases in parallel, avoid having to wait for each one
  241. Parallel.ForEach(ResourcesStoresCache.Values, dbTask =>
  242. {
  243. if (dbTask.IsCompleted == false)
  244. {
  245. dbTask.ContinueWith(task =>
  246. {
  247. if (task.Status != TaskStatus.RanToCompletion)
  248. return;
  249. try
  250. {
  251. task.Result.Dispose();
  252. }
  253. catch (Exception e)
  254. {
  255. Logger.WarnException("Failure in deferred disposal of a database", e);
  256. }
  257. });
  258. }
  259. else if (dbTask.Status == TaskStatus.RanToCompletion)
  260. {
  261. exceptionAggregator.Execute(dbTask.Result.Dispose);
  262. }
  263. // there is no else, the db is probably faulted
  264. });
  265. ResourcesStoresCache.Clear();
  266. }
  267. }
  268. }
  269. }