PageRenderTime 46ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/ToMigrate/Raven.Database/Server/Tenancy/CountersLandlord.cs

http://github.com/ayende/ravendb
C# | 329 lines | 276 code | 46 blank | 7 comment | 49 complexity | 2d1383786bc23127b2a2316a8a339304 MD5 | raw file
Possible License(s): GPL-3.0, MPL-2.0-no-copyleft-exception, LGPL-2.1, Apache-2.0, BSD-3-Clause, CC-BY-SA-3.0
  1. // -----------------------------------------------------------------------
  2. // <copyright file="CountersLandlord.cs" company="Hibernating Rhinos LTD">
  3. // Copyright (c) Hibernating Rhinos LTD. All rights reserved.
  4. // </copyright>
  5. // -----------------------------------------------------------------------
  6. using System.Diagnostics;
  7. using Newtonsoft.Json.Linq;
  8. using Raven.Abstractions;
  9. using Raven.Abstractions.Counters;
  10. using Raven.Abstractions.Data;
  11. using Raven.Abstractions.Extensions;
  12. using Raven.Abstractions.Logging;
  13. using Raven.Abstractions.Util;
  14. using Raven.Database.Commercial;
  15. using Raven.Database.Config;
  16. using Raven.Database.Counters;
  17. using Raven.Database.Extensions;
  18. using Raven.Database.Server.Connections;
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Collections.Specialized;
  22. using System.Linq;
  23. using System.Security.Cryptography;
  24. using System.Text;
  25. using System.Threading;
  26. using System.Threading.Tasks;
  27. using Raven.Abstractions.Exceptions;
  28. using Raven.Database.Server.Security;
  29. using Raven.Json.Linq;
  30. namespace Raven.Database.Server.Tenancy
  31. {
  32. public class CountersLandlord : AbstractLandlord<CounterStorage>
  33. {
  34. private bool initialized;
  35. public override string ResourcePrefix { get { return Constants.Counter.Prefix; } }
  36. public event Action<RavenConfiguration> SetupTenantConfiguration = delegate { };
  37. public CountersLandlord(DocumentDatabase systemDatabase) : base(systemDatabase)
  38. {
  39. Init();
  40. }
  41. public RavenConfiguration SystemConfiguration { get { return systemDatabase.Configuration; } }
  42. public void Init()
  43. {
  44. if (initialized)
  45. return;
  46. initialized = true;
  47. systemDatabase.Notifications.OnDocumentChange += (database, notification, doc) =>
  48. {
  49. if (notification.Id == null)
  50. return;
  51. if (notification.Id.StartsWith(ResourcePrefix, StringComparison.InvariantCultureIgnoreCase) == false)
  52. return;
  53. var dbName = notification.Id.Substring(ResourcePrefix.Length);
  54. Logger.Info("Shutting down counters {0} because the tenant counter document has been updated or removed", dbName);
  55. Cleanup(dbName, skipIfActiveInDuration: null, notificationType: notification.Type);
  56. };
  57. }
  58. public RavenConfiguration CreateTenantConfiguration(string tenantId, bool ignoreDisabledCounterStorage = false)
  59. {
  60. if (string.IsNullOrWhiteSpace(tenantId))
  61. throw new ArgumentException("tenantId");
  62. var document = GetTenantDatabaseDocument(tenantId, ignoreDisabledCounterStorage);
  63. if (document == null)
  64. return null;
  65. return CreateConfiguration(tenantId, document, RavenConfiguration.GetKey(x => x.Counter.DataDirectory), systemDatabase.Configuration);
  66. }
  67. protected RavenConfiguration CreateConfiguration(
  68. string tenantId,
  69. CounterStorageDocument document,
  70. string folderPropName,
  71. RavenConfiguration parentConfiguration)
  72. {
  73. var config = RavenConfiguration.CreateFrom(parentConfiguration);
  74. SetupTenantConfiguration(config);
  75. config.CustomizeValuesForCounterStorageTenant(tenantId);
  76. foreach (var setting in document.Settings)
  77. {
  78. config.SetSetting(setting.Key, setting.Value);
  79. }
  80. Unprotect(document);
  81. foreach (var securedSetting in document.SecuredSettings)
  82. {
  83. config.SetSetting(securedSetting.Key, securedSetting.Value);
  84. }
  85. config.SetSetting(folderPropName, config.GetSetting(folderPropName).ToFullPath(parentConfiguration.Counter.DataDirectory));
  86. config.SetSetting(RavenConfiguration.GetKey(x => x.Storage.JournalsStoragePath),config.GetSetting(RavenConfiguration.GetKey(x => x.Storage.JournalsStoragePath)).ToFullPath(parentConfiguration.Core.DataDirectory));
  87. config.CounterStorageName = tenantId;
  88. config.Initialize();
  89. config.CopyParentSettings(parentConfiguration);
  90. return config;
  91. }
  92. private CounterStorageDocument GetTenantDatabaseDocument(string tenantId, bool ignoreDisabledCounterStorage = false)
  93. {
  94. JsonDocument jsonDocument;
  95. using (systemDatabase.DisableAllTriggersForCurrentThread())
  96. jsonDocument = systemDatabase.Documents.Get(ResourcePrefix + tenantId);
  97. if (jsonDocument == null || jsonDocument.Metadata == null ||
  98. jsonDocument.Metadata.Value<bool>(Constants.RavenDocumentDoesNotExists) ||
  99. jsonDocument.Metadata.Value<bool>(Constants.RavenDeleteMarker))
  100. return null;
  101. var document = jsonDocument.DataAsJson.JsonDeserialization<CounterStorageDocument>();
  102. if (document.Settings.Keys.Contains(RavenConfiguration.GetKey(x => x.Counter.DataDirectory)) == false)
  103. throw new InvalidOperationException("Could not find " + RavenConfiguration.GetKey(x => x.Counter.DataDirectory));
  104. if (document.Disabled && !ignoreDisabledCounterStorage)
  105. throw new InvalidOperationException("The counter storage has been disabled.");
  106. return document;
  107. }
  108. public override async Task<CounterStorage> GetResourceInternal(string resourceName)
  109. {
  110. Task<CounterStorage> cs;
  111. if (TryGetOrCreateResourceStore(resourceName, out cs))
  112. return await cs.ConfigureAwait(false);
  113. return null;
  114. }
  115. public override bool TryGetOrCreateResourceStore(string tenantId, out Task<CounterStorage> counter)
  116. {
  117. if (Locks.Contains(DisposingLock))
  118. throw new ObjectDisposedException("CountersLandlord", "Server is shutting down, can't access any counters");
  119. if (Locks.Contains(tenantId))
  120. throw new InvalidOperationException("Counters '" + tenantId + "' is currently locked and cannot be accessed");
  121. ManualResetEvent cleanupLock;
  122. if (Cleanups.TryGetValue(tenantId, out cleanupLock) && cleanupLock.WaitOne(MaxTimeForTaskToWaitForDatabaseToLoad) == false)
  123. throw new InvalidOperationException($"Counters '{tenantId}' are currently being restarted and cannot be accessed. We already waited {MaxTimeForTaskToWaitForDatabaseToLoad.TotalSeconds} seconds.");
  124. if (ResourcesStoresCache.TryGetValue(tenantId, out counter))
  125. {
  126. if (counter.IsFaulted || counter.IsCanceled)
  127. {
  128. ResourcesStoresCache.TryRemove(tenantId, out counter);
  129. DateTime time;
  130. LastRecentlyUsed.TryRemove(tenantId, out time);
  131. // and now we will try creating it again
  132. }
  133. else
  134. {
  135. return true;
  136. }
  137. }
  138. var config = CreateTenantConfiguration(tenantId);
  139. if (config == null)
  140. return false;
  141. var hasAcquired = false;
  142. try
  143. {
  144. if (!ResourceSemaphore.Wait(ConcurrentResourceLoadTimeout))
  145. throw new ConcurrentLoadTimeoutException("Too much counters loading concurrently, timed out waiting for them to load.");
  146. hasAcquired = true;
  147. counter = ResourcesStoresCache.GetOrAdd(tenantId, __ => Task.Factory.StartNew(() =>
  148. {
  149. var transportState = ResourseTransportStates.GetOrAdd(tenantId, s => new TransportState());
  150. var cs = new CounterStorage(systemDatabase.ServerUrl, tenantId, config, transportState);
  151. AssertLicenseParameters(config);
  152. // if we have a very long init process, make sure that we reset the last idle time for this db.
  153. LastRecentlyUsed.AddOrUpdate(tenantId, SystemTime.UtcNow, (_, time) => SystemTime.UtcNow);
  154. return cs;
  155. }).ContinueWith(task =>
  156. {
  157. if (task.Status == TaskStatus.Faulted) // this observes the task exception
  158. {
  159. Logger.WarnException("Failed to create counters " + tenantId, task.Exception);
  160. }
  161. return task;
  162. }).Unwrap());
  163. return true;
  164. }
  165. finally
  166. {
  167. if (hasAcquired)
  168. ResourceSemaphore.Release();
  169. }
  170. }
  171. public
  172. void Unprotect(CounterStorageDocument configDocument)
  173. {
  174. if (configDocument.SecuredSettings == null)
  175. {
  176. configDocument.SecuredSettings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  177. return;
  178. }
  179. foreach (var prop in configDocument.SecuredSettings.ToList())
  180. {
  181. if (prop.Value == null)
  182. continue;
  183. var bytes = Convert.FromBase64String(prop.Value);
  184. var entrophy = Encoding.UTF8.GetBytes(prop.Key);
  185. try
  186. {
  187. var unprotectedValue = ProtectedData.Unprotect(bytes, entrophy, DataProtectionScope.CurrentUser);
  188. configDocument.SecuredSettings[prop.Key] = Encoding.UTF8.GetString(unprotectedValue);
  189. }
  190. catch (Exception e)
  191. {
  192. Logger.WarnException("Could not unprotect secured db data " + prop.Key + " setting the value to '<data could not be decrypted>'", e);
  193. configDocument.SecuredSettings[prop.Key] = Constants.DataCouldNotBeDecrypted;
  194. }
  195. }
  196. }
  197. public void Protect(CounterStorageDocument configDocument)
  198. {
  199. if (configDocument.SecuredSettings == null)
  200. {
  201. configDocument.SecuredSettings = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
  202. return;
  203. }
  204. foreach (var prop in configDocument.SecuredSettings.ToList())
  205. {
  206. if (prop.Value == null)
  207. continue;
  208. var bytes = Encoding.UTF8.GetBytes(prop.Value);
  209. var entrophy = Encoding.UTF8.GetBytes(prop.Key);
  210. var protectedValue = ProtectedData.Protect(bytes, entrophy, DataProtectionScope.CurrentUser);
  211. configDocument.SecuredSettings[prop.Key] = Convert.ToBase64String(protectedValue);
  212. }
  213. }
  214. private void AssertLicenseParameters(RavenConfiguration config)
  215. {
  216. string maxCounters;
  217. if (ValidateLicense.CurrentLicense.Attributes.TryGetValue("numberOfCounters", out maxCounters))
  218. {
  219. if (string.Equals(maxCounters, "unlimited", StringComparison.OrdinalIgnoreCase) == false)
  220. {
  221. var numberOfAllowedCounters = int.Parse(maxCounters);
  222. int nextPageStart = 0;
  223. var counters =
  224. systemDatabase.Documents.GetDocumentsWithIdStartingWith(ResourcePrefix, null, null, 0,
  225. numberOfAllowedCounters, CancellationToken.None, ref nextPageStart).ToList();
  226. if (counters.Count >= numberOfAllowedCounters)
  227. throw new InvalidOperationException(
  228. "You have reached the maximum number of counters that you can have according to your license: " +
  229. numberOfAllowedCounters + Environment.NewLine +
  230. "But we detect: " + counters.Count + " counter storages" + Environment.NewLine +
  231. "You can either upgrade your RavenDB license or delete a counter from the server");
  232. }
  233. }
  234. if (Authentication.IsLicensedForCounters == false)
  235. {
  236. throw new InvalidOperationException("Your license does not allow the use of the Counters");
  237. }
  238. Authentication.AssertLicensedBundles(config.Core.ActiveBundles);
  239. }
  240. public void ForAllCountersInCacheOnly(Action<CounterStorage> action)
  241. {
  242. foreach (var value in ResourcesStoresCache
  243. .Select(cs => cs.Value)
  244. .Where(value => value.Status == TaskStatus.RanToCompletion))
  245. {
  246. action(value.Result);
  247. }
  248. }
  249. public void ForAllCounters(Action<CounterStorage> action)
  250. {
  251. using (systemDatabase.DisableAllTriggersForCurrentThread())
  252. {
  253. int nextPageStart = 0;
  254. var counterDocs = systemDatabase.Documents.GetDocumentsWithIdStartingWith(ResourcePrefix, null, null,
  255. 0,int.MaxValue, CancellationToken.None, ref nextPageStart).ToList();
  256. foreach (var doc in counterDocs)
  257. {
  258. var id = GetCounterIdFromDocumentKey(doc);
  259. Debug.Assert(String.IsNullOrWhiteSpace(id) == false,"key of counter should not be empty");
  260. Task<CounterStorage> counterFetchTask;
  261. if (!TryGetOrCreateResourceStore(id, out counterFetchTask))
  262. throw new InvalidOperationException(string.Format("Could not get counter specified by counter storage document. The id that wasn't found is {0}", id));
  263. var counter = AsyncHelpers.RunSync(() => counterFetchTask);
  264. action(counter);
  265. }
  266. }
  267. }
  268. private static string GetCounterIdFromDocumentKey(RavenJToken doc)
  269. {
  270. var metadata = doc.Value<RavenJObject>("@metadata");
  271. var docKey = metadata.Value<string>("@id");
  272. var startIndex = docKey.LastIndexOf('/') + 1;
  273. if (startIndex >= docKey.Length)
  274. throw new InvalidOperationException(string.Format("Counter document key is invalid. (got {0})", docKey));
  275. var id = docKey.Substring(startIndex);
  276. return id;
  277. }
  278. protected override DateTime LastWork(CounterStorage resource)
  279. {
  280. return resource.LastWrite;
  281. }
  282. }
  283. }