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