Пример #1
0
        private static BlittableJsonReaderObject GetReduceResult(ulong reduceKeyHash, Index index, TransactionOperationContext context)
        {
            using (var reader = index.IndexPersistence.OpenIndexReader(context.Transaction.InnerTransaction))
                using (index.DocumentDatabase.DocumentsStorage.ContextPool.AllocateOperationContext(out DocumentsOperationContext ctx))
                    using (ctx.OpenReadTransaction())
                    {
                        var queryParameters = context.ReadObject(new DynamicJsonValue
                        {
                            ["p0"] = reduceKeyHash.ToString()
                        }, "query/parameters");
                        var query = new IndexQueryServerSide($"FROM INDEX '{index.Name}' WHERE '{Constants.Documents.Indexing.Fields.ReduceKeyHashFieldName}' = $p0", queryParameters);

                        var fieldsToFetch = new FieldsToFetch(query, index.Definition);

                        var retriever = new MapReduceQueryResultRetriever(null, null, null, null, context, fieldsToFetch, null);
                        var result    = reader
                                        .Query(query, null, fieldsToFetch, new Reference <int>(), new Reference <int>(), retriever, ctx, null, CancellationToken.None)
                                        .ToList();

                        if (result.Count == 0)
                        {
                            return(context.ReadObject(new DynamicJsonValue(), "debug-reduce-result"));
                        }

                        if (result.Count > 1)
                        {
                            throw new InvalidOperationException("Cannot have multiple reduce results for a single reduce key");
                        }

                        return(result[0].Result.Data);
                    }
        }
Пример #2
0
        public override unsafe void Execute(TransactionOperationContext context, Table items, long index, DatabaseRecord record, bool isPassive, out object result)
        {
            result = null;
            var subscriptionId = SubscriptionId ?? index;

            SubscriptionName = string.IsNullOrEmpty(SubscriptionName) ? subscriptionId.ToString() : SubscriptionName;
            var receivedSubscriptionState = context.ReadObject(new SubscriptionState
            {
                Query = Query,
                ChangeVectorForNextBatchStartingPoint = InitialChangeVector,
                SubscriptionId   = subscriptionId,
                SubscriptionName = SubscriptionName,
                LastTimeServerMadeProgressWithDocuments = DateTime.UtcNow,
                Disabled = Disabled,
                LastClientConnectionTime = DateTime.Now
            }.ToJson(), SubscriptionName);
            BlittableJsonReaderObject modifiedSubscriptionState = null;

            try
            {
                string subscriptionItemName = SubscriptionState.GenerateSubscriptionItemKeyName(DatabaseName, SubscriptionName);

                using (Slice.From(context.Allocator, subscriptionItemName, out Slice valueName))
                    using (Slice.From(context.Allocator, subscriptionItemName.ToLowerInvariant(), out Slice valueNameLowered))
                    {
                        if (items.ReadByKey(valueNameLowered, out TableValueReader tvr))
                        {
                            var ptr = tvr.Read(2, out int size);
                            var doc = new BlittableJsonReaderObject(ptr, size, context);

                            var existingSubscriptionState = JsonDeserializationClient.SubscriptionState(doc);

                            if (SubscriptionId != existingSubscriptionState.SubscriptionId)
                            {
                                throw new InvalidOperationException("A subscription could not be modified because the name '" + subscriptionItemName +
                                                                    "' is already in use in a subscription with different Id.");
                            }

                            if (Enum.TryParse(InitialChangeVector, out Constants.Documents.SubscriptionChangeVectorSpecialStates changeVectorState) && changeVectorState == Constants.Documents.SubscriptionChangeVectorSpecialStates.DoNotChange)
                            {
                                if (receivedSubscriptionState.Modifications == null)
                                {
                                    receivedSubscriptionState.Modifications = new DynamicJsonValue();
                                }

                                receivedSubscriptionState.Modifications[nameof(SubscriptionState.ChangeVectorForNextBatchStartingPoint)] = existingSubscriptionState.ChangeVectorForNextBatchStartingPoint;
                                modifiedSubscriptionState = context.ReadObject(receivedSubscriptionState, SubscriptionName);
                            }
                        }

                        ClusterStateMachine.UpdateValue(subscriptionId, items, valueNameLowered, valueName, modifiedSubscriptionState ?? receivedSubscriptionState);
                    }
            }
            finally
            {
                receivedSubscriptionState.Dispose();
                modifiedSubscriptionState?.Dispose();
            }
        }
Пример #3
0
        public override unsafe void Execute(TransactionOperationContext context, Table items, long index, DatabaseRecord record, RachisState state, out object result)
        {
            result = null;

            var itemKey = SubscriptionState.GenerateSubscriptionItemKeyName(DatabaseName, SubscriptionName);

            using (Slice.From(context.Allocator, itemKey.ToLowerInvariant(), out Slice valueNameLowered))
                using (Slice.From(context.Allocator, itemKey, out Slice valueName))
                {
                    if (items.ReadByKey(valueNameLowered, out var tvr) == false)
                    {
                        throw new RachisApplyException($"Cannot find subscription {index}");
                    }

                    var ptr = tvr.Read(2, out int size);
                    var doc = new BlittableJsonReaderObject(ptr, size, context);

                    var subscriptionState = JsonDeserializationClient.SubscriptionState(doc);
                    subscriptionState.Disabled = Disable;
                    using (var obj = context.ReadObject(subscriptionState.ToJson(), "subscription"))
                    {
                        ClusterStateMachine.UpdateValue(index, items, valueNameLowered, valueName, obj);
                    }
                }
        }
Пример #4
0
        private unsafe void UpdateInternal(TransactionOperationContext context, string guid, string type, long index, long term, HistoryStatus status, object result, Exception exception)
        {
            var table = context.Transaction.InnerTransaction.OpenTable(LogHistoryTable, LogHistorySlice);

            TableValueReader reader;

            using (Slice.From(context.Allocator, guid, out var guidSlice))
            {
                if (table.ReadByKey(guidSlice, out reader) == false)
                {
                    return;
                }
            }

            if (TypeConverter.IsSupportedType(result) == false)
            {
                throw new RachisApplyException("We don't support type " + result.GetType().FullName + ".");
            }

            using (Slice.From(context.Allocator, guid, out var guidSlice))
                using (Slice.From(context.Allocator, type, out var typeSlice))
                    using (table.Allocate(out TableValueBuilder tvb))
                    {
                        tvb.Add(guidSlice);
                        tvb.Add(Bits.SwapBytes(index));
                        tvb.Add(Bits.SwapBytes(GetUniqueTicks(context.Transaction.InnerTransaction)));
                        tvb.Add(*(long *)reader.Read((int)(LogHistoryColumn.Term), out _));
                        tvb.Add(term);
                        tvb.Add(typeSlice);
                        tvb.Add((byte)status);
                        if (result == null)
                        {
                            tvb.Add(Slices.Empty);
                        }
                        else
                        {
                            var blittableResult = context.ReadObject(new DynamicJsonValue {
                                ["Result"] = result
                            }, "set-history-result");
                            tvb.Add(blittableResult.BasePointer, blittableResult.Size);
                        }

                        if (exception == null)
                        {
                            tvb.Add(Slices.Empty);
                            tvb.Add(Slices.Empty);
                        }
                        else
                        {
                            var exceptionType = context.GetLazyString(exception.GetType().AssemblyQualifiedName);
                            var exceptionMsg  = context.GetLazyString(exception.ToString());
                            tvb.Add(exceptionType.Buffer, exceptionType.Size);
                            tvb.Add(exceptionMsg.Buffer, exceptionMsg.Size);
                        }

                        table.Set(tvb);
                    }
        }
Пример #5
0
        public override unsafe void Execute(TransactionOperationContext context, Table items, long index, DatabaseRecord record, RachisState state, out object result)
        {
            result = null;
            var subscriptionId = SubscriptionId ?? index;

            SubscriptionName = string.IsNullOrEmpty(SubscriptionName) ? subscriptionId.ToString() : SubscriptionName;

            var subscriptionItemName = SubscriptionState.GenerateSubscriptionItemKeyName(DatabaseName, SubscriptionName);

            using (Slice.From(context.Allocator, subscriptionItemName, out Slice valueName))
                using (Slice.From(context.Allocator, subscriptionItemName.ToLowerInvariant(), out Slice valueNameLowered))
                {
                    if (items.ReadByKey(valueNameLowered, out TableValueReader tvr))
                    {
                        var ptr = tvr.Read(2, out int size);
                        var doc = new BlittableJsonReaderObject(ptr, size, context);

                        var existingSubscriptionState = JsonDeserializationClient.SubscriptionState(doc);

                        if (SubscriptionId != existingSubscriptionState.SubscriptionId)
                        {
                            throw new RachisApplyException("A subscription could not be modified because the name '" + subscriptionItemName +
                                                           "' is already in use in a subscription with different Id.");
                        }

                        if (string.IsNullOrEmpty(InitialChangeVector) == false && InitialChangeVector == nameof(Constants.Documents.SubscriptionChangeVectorSpecialStates.DoNotChange))
                        {
                            InitialChangeVector = existingSubscriptionState.ChangeVectorForNextBatchStartingPoint;
                        }
                        else
                        {
                            AssertValidChangeVector();
                        }
                    }
                    else
                    {
                        AssertValidChangeVector();
                    }

                    using (var receivedSubscriptionState = context.ReadObject(new SubscriptionState
                    {
                        Query = Query,
                        ChangeVectorForNextBatchStartingPoint = InitialChangeVector,
                        SubscriptionId = subscriptionId,
                        SubscriptionName = SubscriptionName,
                        LastBatchAckTime = null,
                        Disabled = Disabled,
                        MentorNode = MentorNode,
                        LastClientConnectionTime = null
                    }.ToJson(), SubscriptionName))
                    {
                        ClusterStateMachine.UpdateValue(subscriptionId, items, valueNameLowered, valueName, receivedSubscriptionState);
                    }
                }
        }
Пример #6
0
        public string GetHistoryLogsAsString(TransactionOperationContext context)
        {
            var sb = new StringBuilder();

            foreach (var entry in GetHistoryLogs(context))
            {
                sb.AppendLine(context.ReadObject(entry, "raft-command-history").ToString());
            }

            return(sb.ToString());
        }
Пример #7
0
        private static unsafe BlittableJsonReaderObject GetAggregationResult(long pageNumber, Table table, TransactionOperationContext context)
        {
            var tmp = Bits.SwapBytes(pageNumber);

            using (Slice.External(context.Allocator, (byte *)&tmp, sizeof(long), out Slice pageNumberSlice))
            {
                table.ReadByKey(pageNumberSlice, out TableValueReader tvr);

                var numberOfResults = *(int *)tvr.Read(ReduceMapResultsOfStaticIndex.NumberOfResultsPosition, out int _);

                if (numberOfResults == 0)
                {
                    return(context.ReadObject(new DynamicJsonValue(), "debug-reduce-result"));
                }

                return(new BlittableJsonReaderObject(tvr.Read(3, out int size), size, context));
            }
        }
Пример #8
0
        public unsafe void AddAlert(Alert alert, TransactionOperationContext context, RavenTransaction tx)
        {
            _store?.TrackChangeAfterTransactionCommit(context, "AlertRaised", alert.Key);

            var table = tx.InnerTransaction.OpenTable(_alertsSchema, AlertsSchema.AlertsTree);

            var alertId = alert.Id;

            var alertAsJson = alert.ToJson();

            // if previous alert has dismissed until value pass this value to newly saved alert
            Slice slice;

            using (Slice.From(tx.InnerTransaction.Allocator, alertId, out slice))
            {
                var existingTvr = table.ReadByKey(slice);
                if (existingTvr != null)
                {
                    var existingAlert = Read(context, existingTvr);

                    object dismissedUntilValue;
                    existingAlert.TryGetMember(nameof(alert.DismissedUntil), out dismissedUntilValue);
                    if (dismissedUntilValue != null)
                    {
                        var dismissedUntil = (LazyStringValue)dismissedUntilValue;
                        alertAsJson[nameof(alert.DismissedUntil)] = dismissedUntil;
                    }
                }
            }


            using (var id = context.GetLazyString(alertId))
                using (var json = context.ReadObject(alertAsJson, "Alert", BlittableJsonDocumentBuilder.UsageMode.ToDisk))
                {
                    var tvb = new TableValueBuilder
                    {
                        { id.Buffer, id.Size },
                        { json.BasePointer, json.Size }
                    };

                    table.Set(tvb);
                }
        }
        public BlittableJsonReaderObject GetDatabaseRecordForDebugPackage(TransactionOperationContext context, string databaseName)
        {
            var databaseRecord = Server.ServerStore.Cluster.ReadRawDatabaseRecord(context, databaseName);

            if (databaseRecord == null)
            {
                throw new RavenException($"Couldn't fetch {nameof(DatabaseRecord)} from server for database '{databaseName}'");
            }

            var djv = new DynamicJsonValue();

            foreach (string fld in FieldsThatShouldBeExposedForDebug)
            {
                if (databaseRecord.Raw.TryGetMember(fld, out var obj))
                {
                    djv[fld] = obj;
                }
            }

            return(context.ReadObject(djv, "databaserecord"));
        }
Пример #10
0
        private static string GetTreeName(BlittableJsonReaderObject reduceEntry, IndexDefinitionBase indexDefinition, TransactionOperationContext context)
        {
            HashSet <string> groupByFields;

            if (indexDefinition is MapReduceIndexDefinition)
            {
                groupByFields = ((MapReduceIndexDefinition)indexDefinition).GroupByFields;
            }
            else if (indexDefinition is AutoMapReduceIndexDefinition)
            {
                groupByFields = ((AutoMapReduceIndexDefinition)indexDefinition).GroupByFields.Keys.ToHashSet();
            }
            else
            {
                throw new InvalidOperationException("Invalid map reduce index definition: " + indexDefinition.GetType());
            }

            foreach (var prop in reduceEntry.GetPropertyNames())
            {
                if (groupByFields.Contains(prop))
                {
                    continue;
                }

                if (reduceEntry.Modifications == null)
                {
                    reduceEntry.Modifications = new DynamicJsonValue(reduceEntry);
                }

                reduceEntry.Modifications.Remove(prop);
            }

            var reduceKey = context.ReadObject(reduceEntry, "debug: creating reduce tree name");

            return(reduceKey.ToString());
        }
Пример #11
0
        public static async Task PutCertificateCollectionInCluster(CertificateDefinition certDef, byte[] certBytes, string password, ServerStore serverStore, TransactionOperationContext ctx)
        {
            var collection = new X509Certificate2Collection();

            if (string.IsNullOrEmpty(password))
            {
                collection.Import(certBytes);
            }
            else
            {
                collection.Import(certBytes, password, X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable);
            }

            var first = true;
            var collectionPrimaryKey = string.Empty;

            foreach (var x509Certificate in collection)
            {
                if (serverStore.Server.Certificate.Certificate?.Thumbprint != null && serverStore.Server.Certificate.Certificate.Thumbprint.Equals(x509Certificate.Thumbprint))
                {
                    throw new InvalidOperationException($"You are trying to import the same server certificate ({x509Certificate.Thumbprint}) as the one which is already loaded. This is not supported.");
                }
            }

            foreach (var x509Certificate in collection)
            {
                var currentCertDef = new CertificateDefinition
                {
                    Name              = certDef.Name,
                    Permissions       = certDef.Permissions,
                    SecurityClearance = certDef.SecurityClearance,
                    Password          = certDef.Password,
                };

                if (x509Certificate.HasPrivateKey)
                {
                    // avoid storing the private key
                    currentCertDef.Certificate = Convert.ToBase64String(x509Certificate.Export(X509ContentType.Cert));
                }

                // In case of a collection, we group all the certificates together and treat them as one unit.
                // They all have the same name and permissions but a different thumbprint.
                // The first certificate in the collection will be the primary certificate and its thumbprint will be the one shown in a GET request
                // The other certificates are secondary certificates and will contain a link to the primary certificate.
                currentCertDef.Thumbprint  = x509Certificate.Thumbprint;
                currentCertDef.NotAfter    = x509Certificate.NotAfter;
                currentCertDef.Certificate = Convert.ToBase64String(x509Certificate.Export(X509ContentType.Cert));

                if (first)
                {
                    var firstKey = Constants.Certificates.Prefix + x509Certificate.Thumbprint;
                    collectionPrimaryKey = firstKey;

                    foreach (var cert in collection)
                    {
                        if (Constants.Certificates.Prefix + cert.Thumbprint != firstKey)
                        {
                            currentCertDef.CollectionSecondaryKeys.Add(Constants.Certificates.Prefix + cert.Thumbprint);
                        }
                    }
                }
                else
                {
                    currentCertDef.CollectionPrimaryKey = collectionPrimaryKey;
                }

                var certKey = Constants.Certificates.Prefix + currentCertDef.Thumbprint;
                if (serverStore.CurrentRachisState == RachisState.Passive)
                {
                    using (var certificate = ctx.ReadObject(currentCertDef.ToJson(), "Client/Certificate/Definition"))
                        using (var tx = ctx.OpenWriteTransaction())
                        {
                            serverStore.Cluster.PutLocalState(ctx, certKey, certificate);
                            tx.Commit();
                        }
                }
                else
                {
                    var putResult = await serverStore.PutValueInClusterAsync(new PutCertificateCommand(certKey, currentCertDef));

                    await serverStore.Cluster.WaitForIndexNotification(putResult.Index);
                }

                first = false;
            }
        }
Пример #12
0
        protected override AggregationResult AggregateOn(List <BlittableJsonReaderObject> aggregationBatch, TransactionOperationContext indexContext, CancellationToken token)
        {
            var aggregatedResultsByReduceKey = new Dictionary <BlittableJsonReaderObject, Dictionary <string, PropertyResult> >(ReduceKeyComparer.Instance);

            foreach (var obj in aggregationBatch)
            {
                token.ThrowIfCancellationRequested();

                using (obj)
                {
                    var aggregatedResult = new Dictionary <string, PropertyResult>();

                    foreach (var propertyName in obj.GetPropertyNames())
                    {
                        if (_indexDefinition.TryGetField(propertyName, out var indexField))
                        {
                            switch (indexField.Aggregation)
                            {
                            case AggregationOperation.None:
                                if (obj.TryGet(propertyName, out object groupByValue) == false)
                                {
                                    throw new InvalidOperationException($"Could not read group by value of '{propertyName}' property");
                                }

                                aggregatedResult[propertyName] = new PropertyResult
                                {
                                    ResultValue = groupByValue
                                };
                                break;

                            case AggregationOperation.Count:
                            case AggregationOperation.Sum:

                                object value;
                                if (obj.TryGetMember(propertyName, out value) == false)
                                {
                                    throw new InvalidOperationException($"Could not read numeric value of '{propertyName}' property");
                                }

                                double doubleValue;
                                long   longValue;

                                var numberType = BlittableNumber.Parse(value, out doubleValue, out longValue);
                                var aggregate  = new PropertyResult(numberType);

                                switch (numberType)
                                {
                                case NumberParseResult.Double:
                                    aggregate.ResultValue = aggregate.DoubleValue = doubleValue;
                                    break;

                                case NumberParseResult.Long:
                                    aggregate.ResultValue = aggregate.LongValue = longValue;
                                    break;

                                default:
                                    throw new ArgumentOutOfRangeException($"Unknown number type: {numberType}");
                                }

                                aggregatedResult[propertyName] = aggregate;
                                break;

                            //case FieldMapReduceOperation.None:
                            default:
                                throw new ArgumentOutOfRangeException($"Unhandled field type '{indexField.Aggregation}' to aggregate on");
                            }
                        }

                        if (_indexDefinition.GroupByFields.ContainsKey(propertyName) == false)
                        {
                            // we want to reuse existing entry to get a reduce key

                            if (obj.Modifications == null)
                            {
                                obj.Modifications = new DynamicJsonValue(obj);
                            }

                            obj.Modifications.Remove(propertyName);
                        }
                    }

                    var reduceKey = indexContext.ReadObject(obj, "reduce key");

                    if (aggregatedResultsByReduceKey.TryGetValue(reduceKey, out Dictionary <string, PropertyResult> existingAggregate) == false)
                    {
                        aggregatedResultsByReduceKey.Add(reduceKey, aggregatedResult);
                    }
                    else
                    {
                        reduceKey.Dispose();

                        foreach (var propertyResult in existingAggregate)
                        {
                            propertyResult.Value.Aggregate(aggregatedResult[propertyResult.Key]);
                        }
                    }
                }
            }

            var resultObjects = new List <Document>(aggregatedResultsByReduceKey.Count);

            foreach (var aggregationResult in aggregatedResultsByReduceKey)
            {
                aggregationResult.Key.Dispose();

                var djv = new DynamicJsonValue();

                foreach (var aggregate in aggregationResult.Value)
                {
                    djv[aggregate.Key] = aggregate.Value.ResultValue;
                }

                resultObjects.Add(new Document {
                    Data = indexContext.ReadObject(djv, "map/reduce")
                });
            }

            return(new AggregatedDocuments(resultObjects));
        }
Пример #13
0
        private void NegotiateWithLeader(TransactionOperationContext context, LogLengthNegotiation negotiation)
        {
            _debugRecorder.Start();
            // only the leader can send append entries, so if we accepted it, it's the leader
            if (_engine.Log.IsInfoEnabled)
            {
                _engine.Log.Info($"{ToString()}: Got a negotiation request for term {negotiation.Term} where our term is {_term}");
            }
            if (negotiation.Term != _term)
            {
                //  Our leader is no longer a valid one
                var msg = $"The term was changed after leader validation from {_term} to {negotiation.Term}, so you are no longer a valid leader";
                _connection.Send(context, new LogLengthNegotiationResponse
                {
                    Status       = LogLengthNegotiationResponse.ResponseStatus.Rejected,
                    Message      = msg,
                    CurrentTerm  = _term,
                    LastLogIndex = negotiation.PrevLogIndex
                });
                throw new InvalidOperationException($"We close this follower because: {msg}");
            }
            long prevTerm;

            using (context.OpenReadTransaction())
            {
                prevTerm = _engine.GetTermFor(context, negotiation.PrevLogIndex) ?? 0;
            }
            if (prevTerm != negotiation.PrevLogTerm)
            {
                if (_engine.Log.IsInfoEnabled)
                {
                    _engine.Log.Info($"{ToString()}: Got a negotiation request with PrevLogTerm={negotiation.PrevLogTerm} while our PrevLogTerm={prevTerm}" +
                                     " will negotiate to find next matched index");
                }
                // we now have a mismatch with the log position, and need to negotiate it with
                // the leader
                NegotiateMatchEntryWithLeaderAndApplyEntries(context, _connection, negotiation);
            }
            else
            {
                if (_engine.Log.IsInfoEnabled)
                {
                    _engine.Log.Info($"{ToString()}: Got a negotiation request with identical PrevLogTerm will continue to steady state");
                }
                // this (or the negotiation above) completes the negotiation process
                _connection.Send(context, new LogLengthNegotiationResponse
                {
                    Status       = LogLengthNegotiationResponse.ResponseStatus.Acceptable,
                    Message      = $"Found a log index / term match at {negotiation.PrevLogIndex} with term {prevTerm}",
                    CurrentTerm  = _term,
                    LastLogIndex = negotiation.PrevLogIndex
                });
            }
            _debugRecorder.Record("Matching Negotiation is over, waiting for snapshot");
            _engine.Timeout.Defer(_connection.Source);

            // at this point, the leader will send us a snapshot message
            // in most cases, it is an empty snapshot, then start regular append entries
            // the reason we send this is to simplify the # of states in the protocol

            var snapshot = _connection.ReadInstallSnapshot(context);

            _debugRecorder.Record("Snapshot received");
            using (context.OpenWriteTransaction())
            {
                var lastTerm        = _engine.GetTermFor(context, snapshot.LastIncludedIndex);
                var lastCommitIndex = _engine.GetLastEntryIndex(context);
                if (snapshot.LastIncludedTerm == lastTerm && snapshot.LastIncludedIndex < lastCommitIndex)
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info(
                            $"{ToString()}: Got installed snapshot with last index={snapshot.LastIncludedIndex} while our lastCommitIndex={lastCommitIndex}, will just ignore it");
                    }

                    //This is okay to ignore because we will just get the committed entries again and skip them
                    ReadInstallSnapshotAndIgnoreContent(context);
                }
                else if (InstallSnapshot(context))
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info(
                            $"{ToString()}: Installed snapshot with last index={snapshot.LastIncludedIndex} with LastIncludedTerm={snapshot.LastIncludedTerm} ");
                    }

                    _engine.SetLastCommitIndex(context, snapshot.LastIncludedIndex, snapshot.LastIncludedTerm);
                    _engine.ClearLogEntriesAndSetLastTruncate(context, snapshot.LastIncludedIndex, snapshot.LastIncludedTerm);
                }
                else
                {
                    var lastEntryIndex = _engine.GetLastEntryIndex(context);
                    if (lastEntryIndex < snapshot.LastIncludedIndex)
                    {
                        var message =
                            $"The snapshot installation had failed because the last included index {snapshot.LastIncludedIndex} in term {snapshot.LastIncludedTerm} doesn't match the last entry {lastEntryIndex}";
                        if (_engine.Log.IsInfoEnabled)
                        {
                            _engine.Log.Info($"{ToString()}: {message}");
                        }
                        throw new InvalidOperationException(message);
                    }
                }

                // snapshot always has the latest topology
                if (snapshot.Topology == null)
                {
                    const string message = "Expected to get topology on snapshot";
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info($"{ToString()}: {message}");
                    }
                    throw new InvalidOperationException(message);
                }
                using (var topologyJson = context.ReadObject(snapshot.Topology, "topology"))
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info($"{ToString()}: topology on install snapshot: {topologyJson}");
                    }

                    var topology = JsonDeserializationRachis <ClusterTopology> .Deserialize(topologyJson);

                    RachisConsensus.SetTopology(_engine, context, topology);
                }

                context.Transaction.Commit();
            }

            _engine.Timeout.Defer(_connection.Source);
            _debugRecorder.Record("Invoking StateMachine.SnapshotInstalled");

            // notify the state machine, we do this in an async manner, and start
            // the operator in a separate thread to avoid timeouts while this is
            // going on

            var task = Task.Run(() => _engine.SnapshotInstalledAsync(snapshot.LastIncludedIndex));

            var sp = Stopwatch.StartNew();

            var timeToWait = (int)(_engine.ElectionTimeout.TotalMilliseconds / 4);

            while (task.Wait(timeToWait) == false)
            {
                // this may take a while, so we let the other side know that
                // we are still processing, and we reset our own timer while
                // this is happening
                MaybeNotifyLeaderThatWeAreStillAlive(context, sp);
            }

            _debugRecorder.Record("Done with StateMachine.SnapshotInstalled");

            // we might have moved from passive node, so we need to start the timeout clock
            _engine.Timeout.Start(_engine.SwitchToCandidateStateOnTimeout);

            _debugRecorder.Record("Snapshot installed");
            //Here we send the LastIncludedIndex as our matched index even for the case where our lastCommitIndex is greater
            //So we could validate that the entries sent by the leader are indeed the same as the ones we have.
            _connection.Send(context, new InstallSnapshotResponse
            {
                Done         = true,
                CurrentTerm  = _term,
                LastLogIndex = snapshot.LastIncludedIndex
            });

            _engine.Timeout.Defer(_connection.Source);
        }
Пример #14
0
        private void ReadAndCommitSnapshot(TransactionOperationContext context, InstallSnapshot snapshot, CancellationToken token)
        {
            using (context.OpenWriteTransaction())
            {
                var lastTerm        = _engine.GetTermFor(context, snapshot.LastIncludedIndex);
                var lastCommitIndex = _engine.GetLastEntryIndex(context);

                if (_engine.GetSnapshotRequest(context) == false &&
                    snapshot.LastIncludedTerm == lastTerm && snapshot.LastIncludedIndex < lastCommitIndex)
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info(
                            $"{ToString()}: Got installed snapshot with last index={snapshot.LastIncludedIndex} while our lastCommitIndex={lastCommitIndex}, will just ignore it");
                    }

                    //This is okay to ignore because we will just get the committed entries again and skip them
                    ReadInstallSnapshotAndIgnoreContent(token);
                }
                else if (InstallSnapshot(context, token))
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info(
                            $"{ToString()}: Installed snapshot with last index={snapshot.LastIncludedIndex} with LastIncludedTerm={snapshot.LastIncludedTerm} ");
                    }

                    _engine.SetLastCommitIndex(context, snapshot.LastIncludedIndex, snapshot.LastIncludedTerm);
                    _engine.ClearLogEntriesAndSetLastTruncate(context, snapshot.LastIncludedIndex, snapshot.LastIncludedTerm);
                }
                else
                {
                    var lastEntryIndex = _engine.GetLastEntryIndex(context);
                    if (lastEntryIndex < snapshot.LastIncludedIndex)
                    {
                        var message =
                            $"The snapshot installation had failed because the last included index {snapshot.LastIncludedIndex} in term {snapshot.LastIncludedTerm} doesn't match the last entry {lastEntryIndex}";
                        if (_engine.Log.IsInfoEnabled)
                        {
                            _engine.Log.Info($"{ToString()}: {message}");
                        }

                        throw new InvalidOperationException(message);
                    }
                }

                // snapshot always has the latest topology
                if (snapshot.Topology == null)
                {
                    const string message = "Expected to get topology on snapshot";
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info($"{ToString()}: {message}");
                    }

                    throw new InvalidOperationException(message);
                }

                using (var topologyJson = context.ReadObject(snapshot.Topology, "topology"))
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info($"{ToString()}: topology on install snapshot: {topologyJson}");
                    }

                    var topology = JsonDeserializationRachis <ClusterTopology> .Deserialize(topologyJson);

                    RachisConsensus.SetTopology(_engine, context, topology);
                }

                _engine.SetSnapshotRequest(context, false);

                context.Transaction.InnerTransaction.LowLevelTransaction.OnDispose += t =>
                {
                    if (t is LowLevelTransaction llt && llt.Committed)
                    {
                        // we might have moved from passive node, so we need to start the timeout clock
                        _engine.Timeout.Start(_engine.SwitchToCandidateStateOnTimeout);
                    }
                };

                context.Transaction.Commit();
            }
        }
Пример #15
0
        private void NegotiateWithLeader(TransactionOperationContext context, LogLengthNegotiation negotiation)
        {
            // only the leader can send append entries, so if we accepted it, it's the leader
            if (_engine.Log.IsInfoEnabled)
            {
                _engine.Log.Info($"Follower {_engine.Tag}: Got a negotiation request for term {negotiation.Term} where our term is {_engine.CurrentTerm}");
            }
            if (negotiation.Term > _engine.CurrentTerm)
            {
                _engine.FoundAboutHigherTerm(negotiation.Term);
            }

            long prevTerm;

            using (context.OpenReadTransaction())
            {
                prevTerm = _engine.GetTermFor(context, negotiation.PrevLogIndex) ?? 0;
            }
            if (prevTerm != negotiation.PrevLogTerm)
            {
                if (_engine.Log.IsInfoEnabled)
                {
                    _engine.Log.Info($"Follower {_engine.Tag}: Got a negotiation request with PrevLogTerm={negotiation.PrevLogTerm} while our PrevLogTerm={prevTerm}" +
                                     " will negotiate to find next matched index");
                }
                // we now have a mismatch with the log position, and need to negotiate it with
                // the leader
                NegotiateMatchEntryWithLeaderAndApplyEntries(context, _connection, negotiation);
            }
            else
            {
                if (_engine.Log.IsInfoEnabled)
                {
                    _engine.Log.Info($"Follower {_engine.Tag}: Got a negotiation request with identical PrevLogTerm will continue to steady state");
                }
                // this (or the negotiation above) completes the negotiation process
                _connection.Send(context, new LogLengthNegotiationResponse
                {
                    Status       = LogLengthNegotiationResponse.ResponseStatus.Acceptable,
                    Message      = $"Found a log index / term match at {negotiation.PrevLogIndex} with term {prevTerm}",
                    CurrentTerm  = _engine.CurrentTerm,
                    LastLogIndex = negotiation.PrevLogIndex
                });
            }

            _engine.Timeout.Defer(_connection.Source);

            // at this point, the leader will send us a snapshot message
            // in most cases, it is an empty snapshot, then start regular append entries
            // the reason we send this is to simplify the # of states in the protocol

            var snapshot = _connection.ReadInstallSnapshot(context);

            using (context.OpenWriteTransaction())
            {
                var lastCommitIndex = _engine.GetLastCommitIndex(context);
                if (snapshot.LastIncludedIndex < lastCommitIndex)
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info(
                            $"Follower {_engine.Tag}: Got installed snapshot with last index={snapshot.LastIncludedIndex} while our lastCommitIndex={lastCommitIndex}, will just ignore it");
                    }
                    //This is okay to ignore because we will just get the commited entries again and skip them
                    ReadInstallSnapshotAndIgnoreContent(context);
                }
                else if (InstallSnapshot(context))
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info(
                            $"Follower {_engine.Tag}: Installed snapshot with last index={snapshot.LastIncludedIndex} with LastIncludedTerm={snapshot.LastIncludedTerm} ");
                    }
                    _engine.SetLastCommitIndex(context, snapshot.LastIncludedIndex, snapshot.LastIncludedTerm);
                    _engine.ClearLogEntriesAndSetLastTruncate(context, snapshot.LastIncludedIndex, snapshot.LastIncludedTerm);
                }
                else
                {
                    var lastEntryIndex = _engine.GetLastEntryIndex(context);
                    if (lastEntryIndex < snapshot.LastIncludedIndex)
                    {
                        var message =
                            $"The snapshot installation had failed because the last included index {snapshot.LastIncludedIndex} in term {snapshot.LastIncludedTerm} doesn't match the last entry {lastEntryIndex}";
                        if (_engine.Log.IsInfoEnabled)
                        {
                            _engine.Log.Info($"Follower {_engine.Tag}: {message}");
                        }
                        throw new InvalidOperationException(message);
                    }
                }

                // snapshot always has the latest topology
                if (snapshot.Topology == null)
                {
                    const string message = "Expected to get topology on snapshot";
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info($"Follower {_engine.Tag}: {message}");
                    }
                    throw new InvalidOperationException(message);
                }
                using (var topologyJson = context.ReadObject(snapshot.Topology, "topology"))
                {
                    if (_engine.Log.IsInfoEnabled)
                    {
                        _engine.Log.Info($"Follower {_engine.Tag}: topology on install snapshot: {topologyJson}");
                    }

                    var topology = JsonDeserializationRachis <ClusterTopology> .Deserialize(topologyJson);

                    RachisConsensus.SetTopology(_engine, context, topology);
                }

                context.Transaction.Commit();
            }
            //Here we send the LastIncludedIndex as our matched index even for the case where our lastCommitIndex is greater
            //So we could validate that the entries sent by the leader are indeed the same as the ones we have.
            _connection.Send(context, new InstallSnapshotResponse
            {
                Done         = true,
                CurrentTerm  = _engine.CurrentTerm,
                LastLogIndex = snapshot.LastIncludedIndex
            });

            _engine.Timeout.Defer(_connection.Source);

            // notify the state machine
            _engine.SnapshotInstalled(context, snapshot.LastIncludedIndex);

            _engine.Timeout.Defer(_connection.Source);
        }
Пример #16
0
        public override int HandleMap(LazyStringValue key, IEnumerable mapResults, IndexWriteOperation writer, TransactionOperationContext indexContext, IndexingStatsScope stats)
        {
            EnsureValidStats(stats);

            var mappedResult = new DynamicJsonValue();

            using (_stats.BlittableJsonAggregation.Start())
            {
                var document = ((Document[])mapResults)[0];
                Debug.Assert(key == document.LoweredKey);

                foreach (var indexField in Definition.MapFields.Values)
                {
                    switch (indexField.MapReduceOperation)
                    {
                    case FieldMapReduceOperation.Count:
                        mappedResult[indexField.Name] = 1;
                        break;

                    case FieldMapReduceOperation.Sum:
                        object        fieldValue;
                        StringSegment leftPath;
                        BlittableJsonTraverser.Default.TryRead(document.Data, indexField.Name, out fieldValue, out leftPath);

                        var arrayResult = fieldValue as IEnumerable <object>;

                        if (arrayResult == null)
                        {
                            // explicitly adding this even if the value isn't there, as a null
                            mappedResult[indexField.Name] = fieldValue;
                            continue;
                        }

                        decimal total = 0;

                        foreach (var item in arrayResult)
                        {
                            if (item == null)
                            {
                                continue;
                            }

                            double doubleValue;
                            long   longValue;

                            switch (BlittableNumber.Parse(item, out doubleValue, out longValue))
                            {
                            case NumberParseResult.Double:
                                total += (decimal)doubleValue;
                                break;

                            case NumberParseResult.Long:
                                total += longValue;
                                break;
                            }
                        }

                        mappedResult[indexField.Name] = total;

                        break;

                    case FieldMapReduceOperation.None:
                        object result;
                        BlittableJsonTraverser.Default.TryRead(document.Data, indexField.Name, out result, out leftPath);

                        // explicitly adding this even if the value isn't there, as a null
                        mappedResult[indexField.Name] = result;
                        break;

                    default:
                        throw new ArgumentOutOfRangeException();
                    }
                }

                _reduceKeyProcessor.Reset();

                foreach (var groupByFieldName in Definition.GroupByFields.Keys)
                {
                    object        result;
                    StringSegment leftPath;
                    BlittableJsonTraverser.Default.TryRead(document.Data, groupByFieldName, out result, out leftPath);
                    // explicitly adding this even if the value isn't there, as a null
                    mappedResult[groupByFieldName] = result;

                    _reduceKeyProcessor.Process(indexContext.Allocator, result);
                }
            }

            BlittableJsonReaderObject mr;

            using (_stats.CreateBlittableJson.Start())
                mr = indexContext.ReadObject(mappedResult, key);

            var mapResult = _singleOutputList[0];

            mapResult.Data          = mr;
            mapResult.ReduceKeyHash = _reduceKeyProcessor.Hash;

            var resultsCount = PutMapResults(key, _singleOutputList, indexContext, stats);

            DocumentDatabase.Metrics.MapReduceMappedPerSecond.Mark(resultsCount);

            return(resultsCount);
        }