Esempio n. 1
0
        public async Task ReAddWatcherNode()
        {
            var cluster = await CreateRaftCluster(2, watcherCluster : true);

            var leader  = cluster.Leader;
            var watcher = cluster.Nodes.Single(x => x != leader);

            await leader.ServerStore.RemoveFromClusterAsync(watcher.ServerStore.NodeTag);

            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leader.WebUrl, null))
                using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
                {
                    await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(watcher.WebUrl, watcher.ServerStore.NodeTag), ctx);
                }
        }
Esempio n. 2
0
        public static async Task <TcpConnectionInfo> GetTcpInfoAsync(string url, string databaseName, string tag, X509Certificate2 certificate)
        {
            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(url, certificate))
                using (requestExecutor.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                {
                    var getTcpInfoCommand = new GetTcpInfoCommand(tag, databaseName);
                    await requestExecutor.ExecuteAsync(getTcpInfoCommand, context);

                    var tcpConnectionInfo = getTcpInfoCommand.Result;
                    if (url.StartsWith("https", StringComparison.OrdinalIgnoreCase) && tcpConnectionInfo.Certificate == null)
                    {
                        throw new InvalidOperationException("Getting TCP info over HTTPS but the server didn't return the expected certificate to use over TCP, invalid response, aborting");
                    }
                    return(tcpConnectionInfo);
                }
        }
Esempio n. 3
0
        public async Task CanSnapshotCompareExchangeTombstones()
        {
            var leader = await CreateRaftClusterAndGetLeader(1);


            using (var store = GetDocumentStore(options: new Options
            {
                Server = leader
            }))
            {
                using (var session = store.OpenAsyncSession(new SessionOptions
                {
                    TransactionMode = TransactionMode.ClusterWide
                }))
                {
                    session.Advanced.ClusterTransaction.CreateCompareExchangeValue("foo", "bar");
                    await session.SaveChangesAsync();

                    var result = await session.Advanced.ClusterTransaction.GetCompareExchangeValueAsync <string>("foo");

                    session.Advanced.ClusterTransaction.DeleteCompareExchangeValue(result);
                    await session.SaveChangesAsync();
                }

                var server2    = GetNewServer();
                var server2Url = server2.ServerStore.GetNodeHttpServerUrl();
                Servers.Add(server2);

                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leader.WebUrl, null))
                    using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
                    {
                        await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(server2Url, watcher : true), ctx);

                        var addDatabaseNode = new AddDatabaseNodeOperation(store.Database);
                        await store.Maintenance.Server.SendAsync(addDatabaseNode);
                    }

                using (server2.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                    using (ctx.OpenReadTransaction())
                    {
                        Assert.True(server2.ServerStore.Cluster.HasCompareExchangeTombstones(ctx, store.Database));
                    }
            }
        }
Esempio n. 4
0
        public async Task RemoveEntryFromLog()
        {
            var index    = GetLongQueryString("index");
            var first    = GetBoolValueQueryString("first", false) ?? true;
            var nodeList = new List <string>();

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext context))
                using (context.OpenReadTransaction())
                {
                    var removed = ServerStore.Engine.RemoveEntryFromRaftLog(index);
                    if (removed)
                    {
                        nodeList.Add(ServerStore.NodeTag);
                    }

                    if (first)
                    {
                        foreach (var node in ServerStore.GetClusterTopology(context).AllNodes)
                        {
                            if (node.Value == Server.WebUrl)
                            {
                                continue;
                            }

                            var cmd = new RemoveEntryFromRaftLogCommand(index);
                            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(node.Value, Server.Certificate.Certificate))
                            {
                                await requestExecutor.ExecuteAsync(cmd, context);

                                nodeList.AddRange(cmd.Result);
                            }
                        }
                    }

                    await using (var writer = new AsyncBlittableJsonTextWriter(context, ResponseBodyStream()))
                    {
                        writer.WriteStartObject();
                        writer.WriteArray("Nodes", nodeList);
                        writer.WriteEndObject();
                    }
                }
        }
Esempio n. 5
0
        public async Task FailOnAddingNodeThatHasPortZero()
        {
            var leader = await CreateRaftClusterAndGetLeader(1);
            leader.ServerStore.Configuration.Core.ServerUrls = new[] { leader.WebUrl };
            leader.ServerStore.ValidateFixedPort = true;

            var server2 = GetNewServer();
            var server2Url = server2.ServerStore.GetNodeHttpServerUrl();
            Servers.Add(server2);

            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leader.WebUrl, null))
            using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
            {
                var ex = await Assert.ThrowsAsync<RavenException>(async () =>
                    await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(server2Url), ctx));

                Assert.Contains($"Node '{server2Url}' has port '0' in 'Configuration.Core.ServerUrls' setting. " +
                                "Adding a node with non fixed port is forbidden. Define a fixed port for the node to enable cluster creation.", ex.Message);
            }
        }
        public async Task CanSnapshotManyCompareExchangeWithExpirationToManyNodes()
        {
            var count      = 3 * 1024;
            var nodesCount = 7;

            using var leader = GetNewServer();
            using (var store = GetDocumentStore(new Options
            {
                Server = leader
            }))
            {
                var now              = DateTime.UtcNow;
                var expiry           = now.AddMinutes(2);
                var compareExchanges = new Dictionary <string, User>();
                await AddCompareExchangesWithExpire(count, compareExchanges, store, expiry);

                for (int i = 0; i < nodesCount; i++)
                {
                    var follower = GetNewServer();
                    ServersForDisposal.Add(follower);

                    using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leader.WebUrl, null))
                        using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
                        {
                            await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(follower.WebUrl, watcher : true), ctx);
                        }

                    await follower.ServerStore.Engine.WaitForTopology(Leader.TopologyModification.NonVoter);
                }

                leader.ServerStore.Observer.Time.UtcDateTime = () => now.AddMinutes(3);

                var val = await WaitForValueAsync(async() =>
                {
                    var stats = await store.Maintenance.SendAsync(new GetDetailedStatisticsOperation());
                    return(stats.CountOfCompareExchange);
                }, 0);

                Assert.Equal(0, val);
            }
        }
Esempio n. 7
0
        private static async Task <string[]> GetClusterNodeUrlsAsync(string leadersUrl, IDocumentStore store)
        {
            string[] urls;
            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leadersUrl, store.Certificate))
            {
                try
                {
                    await requestExecutor.UpdateTopologyAsync(new ServerNode
                                                              { Url = leadersUrl }, 15000, true);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e);
                    throw;
                }

                urls = requestExecutor.Topology.Nodes.Select(x => x.Url).ToArray();
            }

            return(urls);
        }
Esempio n. 8
0
        public async Task MoveOnToNextTcpAddressOnGuidFailInCluster()
        {
            //Leader asks node for tcpInfo and node answers with Leader's url. Leader will try to connect to Node and end up connecting to itself,
            //guid check will fail, and failover will happen.

            var cluster = await CreateRaftClusterWithSsl(1, watcherCluster : true);

            var serverB = CreateSecuredServer(cluster.Leader.ServerStore.GetNodeTcpServerUrl(), uniqueCerts: false);

            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(cluster.Leader.WebUrl, cluster.Leader.Certificate.Certificate))
                using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
                {
                    string database = GetDatabaseName();

                    await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(serverB.WebUrl, serverB.ServerStore.NodeTag), ctx);

                    using (var leaderStore =
                               new DocumentStore {
                        Urls = new[] { cluster.Leader.WebUrl }, Certificate = cluster.Leader.Certificate.Certificate, Database = database
                    }.Initialize())
                    {
                        var res = leaderStore.Maintenance.Server.Send(new CreateDatabaseOperation(new DatabaseRecord(database)));
                    }

                    using (var storeB = new DocumentStore {
                        Urls = new[] { serverB.WebUrl }, Certificate = serverB.Certificate.Certificate, Database = database
                    }.Initialize())
                    {
                        var name = await WaitForValueAsync(() =>
                        {
                            var record = storeB.Maintenance.Server.Send(new GetDatabaseRecordOperation(database));
                            return(record.DatabaseName);
                        }, database);

                        Assert.Equal(database, name);
                    }
                }
        }
        private async Task WriteDebugInfoPackageForNodeAsync(
            JsonOperationContext context,
            ZipArchive archive,
            string tag,
            string url,
            IEnumerable <string> databaseNames,
            X509Certificate2 certificate,
            bool stacktraces)
        {
            //note : theoretically GetDebugInfoFromNodeAsync() can throw, error handling is done at the level of WriteDebugInfoPackageForNodeAsync() calls
            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(url, certificate))
            {
                var timeout = TimeSpan.FromMinutes(1);
                if (ServerStore.Configuration.Cluster.OperationTimeout.AsTimeSpan > timeout)
                {
                    timeout = ServerStore.Configuration.Cluster.OperationTimeout.AsTimeSpan;
                }

                requestExecutor.DefaultTimeout = timeout;

                using (var responseStream = await GetDebugInfoFromNodeAsync(
                           context,
                           requestExecutor,
                           databaseNames ?? EmptyStringArray,
                           stacktraces))
                {
                    var entry = archive.CreateEntry($"Node - [{tag}].zip");
                    entry.ExternalAttributes = ((int)(FilePermissions.S_IRUSR | FilePermissions.S_IWUSR)) << 16;

                    using (var entryStream = entry.Open())
                    {
                        await responseStream.CopyToAsync(entryStream);

                        await entryStream.FlushAsync();
                    }
                }
            }
        }
Esempio n. 10
0
        public async Task AddNodeToClusterWithoutError()
        {
            var(_, leader) = await CreateRaftCluster(1);

            var server2    = GetNewServer();
            var server2Url = server2.ServerStore.GetNodeHttpServerUrl();

            Servers.Add(server2);

            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leader.WebUrl, null))
                using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
                {
                    await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(server2Url, watcher : true), ctx);

                    await server2.ServerStore.Engine.WaitForTopology(Leader.TopologyModification.NonVoter);
                }

            using (server2.ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
            {
                var logs = ctx.ReadObject(server2.ServerStore.Engine.InMemoryDebug.ToJson(), "watcher-logs").ToString();
                Assert.False(logs.Contains("Exception"), logs);
            }
        }
Esempio n. 11
0
        protected async Task <List <ProcessNode> > CreateCluster(string[] peers, IDictionary <string, string> customSettings = null, X509Certificate2 certificate = null)
        {
            var processes = new List <ProcessNode>();

            foreach (var peer in peers)
            {
                processes.Add(await GetServerAsync(peer));
            }

            var chosenOne = processes[0];

            using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(chosenOne.Url, certificate))
                using (requestExecutor.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                {
                    foreach (var processNode in processes)
                    {
                        if (processNode == chosenOne)
                        {
                            continue;
                        }

                        var addCommand = new AddClusterNodeCommand(processNode.Url);
                        await requestExecutor.ExecuteAsync(addCommand, context);
                    }

                    var clusterCreated = await WaitForValueAsync(async() =>
                    {
                        var clusterTopology = new GetClusterTopologyCommand();
                        await requestExecutor.ExecuteAsync(clusterTopology, context);
                        return(clusterTopology.Result.Topology.Members.Count);
                    }, peers.Length);

                    Assert.True(clusterCreated == peers.Length, "Failed to create initial cluster");
                }

            return(processes);
        }
Esempio n. 12
0
        public ServerOperationExecutor ForNode(string nodeTag)
        {
            if (string.IsNullOrWhiteSpace(nodeTag))
            {
                throw new ArgumentException("Value cannot be null or whitespace.", nameof(nodeTag));
            }

            if (string.Equals(_nodeTag, nodeTag, StringComparison.OrdinalIgnoreCase))
            {
                return(this);
            }

            if (_store.Conventions.DisableTopologyUpdates)
            {
                throw new InvalidOperationException($"Cannot switch server operation executor, because {nameof(Conventions)}.{nameof(_store.Conventions.DisableTopologyUpdates)} is set to 'true'.");
            }

            return(_cache.GetOrAdd(nodeTag, tag =>
            {
                var requestExecutor = _initialRequestExecutor ?? _requestExecutor;

                var topology = GetTopology(requestExecutor);

                var node = topology
                           .Nodes
                           .Find(x => string.Equals(x.ClusterTag, tag, StringComparison.OrdinalIgnoreCase));

                if (node == null)
                {
                    throw new InvalidOperationException($"Could not find node '{tag}' in the topology. Available nodes: [{string.Join(", ", topology.Nodes.Select(x => x.ClusterTag))}]");
                }

                var clusterExecutor = ClusterRequestExecutor.CreateForSingleNode(node.Url, _store.Certificate, _store.Conventions);
                return new ServerOperationExecutor(_store, clusterExecutor, requestExecutor, _cache, node.ClusterTag);
            }));
        }
Esempio n. 13
0
        private ServerOperationExecutor(DocumentStoreBase store, ClusterRequestExecutor requestExecutor, ClusterRequestExecutor initialRequestExecutor, ConcurrentDictionary <string, ServerOperationExecutor> cache, string nodeTag)
        {
            if (store == null)
            {
                throw new ArgumentNullException(nameof(store));
            }
            if (requestExecutor == null)
            {
                throw new ArgumentNullException(nameof(requestExecutor));
            }

            _store                  = store;
            _requestExecutor        = requestExecutor;
            _initialRequestExecutor = initialRequestExecutor;
            _nodeTag                = nodeTag;
            _cache                  = cache;

            store.RegisterEvents(_requestExecutor);

            if (_nodeTag == null)
            {
                store.AfterDispose += (sender, args) => Dispose();
            }
        }
Esempio n. 14
0
        public async Task AddNode()
        {
            SetupCORSHeaders();

            var nodeUrl       = GetQueryStringValueAndAssertIfSingleAndNotEmpty("url");
            var watcher       = GetBoolValueQueryString("watcher", false);
            var assignedCores = GetIntValueQueryString("assignedCores", false);

            if (assignedCores <= 0)
            {
                throw new ArgumentException("Assigned cores must be greater than 0!");
            }

            nodeUrl = UrlHelper.TryGetLeftPart(nodeUrl);
            var remoteIsHttps = nodeUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase);

            if (HttpContext.Request.IsHttps != remoteIsHttps)
            {
                throw new InvalidOperationException($"Cannot add node '{nodeUrl}' to cluster because it will create invalid mix of HTTPS & HTTP endpoints. A cluster must be only HTTPS or only HTTP.");
            }

            NodeInfo nodeInfo;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(nodeUrl, Server.Certificate.Certificate))
                {
                    requestExecutor.DefaultTimeout = ServerStore.Engine.OperationTimeout;

                    var infoCmd = new GetNodeInfoCommand();
                    try
                    {
                        await requestExecutor.ExecuteAsync(infoCmd, ctx);
                    }
                    catch (AllTopologyNodesDownException e)
                    {
                        throw new InvalidOperationException($"Couldn't contact node at {nodeUrl}", e);
                    }

                    nodeInfo = infoCmd.Result;

                    if (ServerStore.IsPassive() && nodeInfo.TopologyId != null)
                    {
                        throw new TopologyMismatchException("You can't add new node to an already existing cluster");
                    }
                }

            if (assignedCores != null && assignedCores > nodeInfo.NumberOfCores)
            {
                throw new ArgumentException("Cannot add node because the assigned cores is larger " +
                                            $"than the available cores on that machine: {nodeInfo.NumberOfCores}");
            }

            ServerStore.EnsureNotPassive();

            if (assignedCores == null)
            {
                assignedCores = ServerStore.LicenseManager.GetCoresToAssign(nodeInfo.NumberOfCores);
            }

            Debug.Assert(assignedCores <= nodeInfo.NumberOfCores);

            ServerStore.LicenseManager.AssertCanAddNode(nodeUrl, assignedCores.Value);

            if (ServerStore.IsLeader())
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    string          topologyId;
                    ClusterTopology clusterTopology;
                    using (ctx.OpenReadTransaction())
                    {
                        clusterTopology = ServerStore.GetClusterTopology(ctx);
                        topologyId      = clusterTopology.TopologyId;
                    }

                    var possibleNode = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                    if (possibleNode.HasUrl)
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because this url is already used by node {possibleNode.NodeTag}");
                    }

                    if (nodeInfo.ServerId == ServerStore.GetServerId())
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because it's a synonym of the current node URL:{ServerStore.GetNodeHttpServerUrl()}");
                    }

                    if (nodeInfo.TopologyId != null)
                    {
                        if (topologyId != nodeInfo.TopologyId)
                        {
                            throw new TopologyMismatchException(
                                      $"Adding a new node to cluster failed. The new node is already in another cluster. " +
                                      $"Expected topology id: {topologyId}, but we get {nodeInfo.TopologyId}");
                        }

                        if (nodeInfo.CurrentState != RachisState.Passive)
                        {
                            throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster " +
                                                                $"because it's already in the cluster under tag :{nodeInfo.NodeTag} " +
                                                                $"and URL: {clusterTopology.GetUrlFromTag(nodeInfo.NodeTag)}");
                        }
                    }

                    var nodeTag = nodeInfo.NodeTag == RachisConsensus.InitialTag ? null : nodeInfo.NodeTag;
                    CertificateDefinition oldServerCert = null;
                    X509Certificate2      certificate   = null;

                    if (remoteIsHttps)
                    {
                        if (nodeInfo.Certificate == null)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because it has no certificate while trying to use HTTPS");
                        }

                        certificate = new X509Certificate2(Convert.FromBase64String(nodeInfo.Certificate), (string)null, X509KeyStorageFlags.MachineKeySet);

                        var now = DateTime.UtcNow;
                        if (certificate.NotBefore.ToUniversalTime() > now)
                        {
                            // Because of time zone and time drift issues, we can't assume that the certificate generation will be
                            // proper. Because of that, we allow tolerance of the NotBefore to be a bit earlier / later than the
                            // current time. Clients may still fail to work with our certificate because of timing issues,
                            // but the admin needs to setup time sync properly and there isn't much we can do at that point
                            if ((certificate.NotBefore.ToUniversalTime() - now).TotalDays > 1)
                            {
                                throw new InvalidOperationException(
                                          $"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' is not yet valid. It starts on {certificate.NotBefore}");
                            }
                        }

                        if (certificate.NotAfter.ToUniversalTime() < now)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' expired on {certificate.NotAfter}");
                        }

                        var expected = GetStringQueryString("expectedThumbprint", required: false);
                        if (expected != null)
                        {
                            if (certificate.Thumbprint != expected)
                            {
                                throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate thumbprint '{certificate.Thumbprint}' doesn't match the expected thumbprint '{expected}'.");
                            }
                        }

                        using (ctx.OpenReadTransaction())
                        {
                            var key      = Constants.Certificates.Prefix + certificate.Thumbprint;
                            var readCert = ServerStore.Cluster.Read(ctx, key);
                            if (readCert != null)
                            {
                                oldServerCert = JsonDeserializationServer.CertificateDefinition(readCert);
                            }
                        }

                        if (oldServerCert == null)
                        {
                            var certificateDefinition = new CertificateDefinition
                            {
                                Certificate       = nodeInfo.Certificate,
                                Thumbprint        = certificate.Thumbprint,
                                NotAfter          = certificate.NotAfter,
                                Name              = "Server Certificate for " + nodeUrl,
                                SecurityClearance = SecurityClearance.ClusterNode
                            };

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + certificate.Thumbprint, certificateDefinition));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }
                    }

                    await ServerStore.AddNodeToClusterAsync(nodeUrl, nodeTag, validateNotInTopology : false, asWatcher : watcher ?? false);

                    using (ctx.OpenReadTransaction())
                    {
                        clusterTopology = ServerStore.GetClusterTopology(ctx);
                        possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        nodeTag         = possibleNode.HasUrl ? possibleNode.NodeTag : null;

                        if (certificate != null)
                        {
                            var key = Constants.Certificates.Prefix + certificate.Thumbprint;

                            var modifiedServerCert = JsonDeserializationServer.CertificateDefinition(ServerStore.Cluster.Read(ctx, key));

                            if (modifiedServerCert == null)
                            {
                                throw new ConcurrencyException("After adding the certificate, it was removed, shouldn't happen unless another admin removed it midway through.");
                            }

                            if (oldServerCert == null)
                            {
                                modifiedServerCert.Name = "Server certificate for Node " + nodeTag;
                            }
                            else
                            {
                                var value = "Node " + nodeTag;
                                if (modifiedServerCert.Name.Contains(value) == false)
                                {
                                    modifiedServerCert.Name += ", " + value;
                                }
                            }

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(key, modifiedServerCert));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }

                        var nodeDetails = new NodeDetails
                        {
                            NodeTag             = nodeTag,
                            AssignedCores       = assignedCores.Value,
                            NumberOfCores       = nodeInfo.NumberOfCores,
                            InstalledMemoryInGb = nodeInfo.InstalledMemoryInGb,
                            UsableMemoryInGb    = nodeInfo.UsableMemoryInGb,
                            BuildInfo           = nodeInfo.BuildInfo
                        };
                        await ServerStore.LicenseManager.CalculateLicenseLimits(nodeDetails, forceFetchingNodeInfo : true, waitToUpdate : true);
                    }

                    NoContentStatus();
                    return;
                }
            }
            RedirectToLeader();
        }
Esempio n. 15
0
 public ServerOperationExecutor(DocumentStoreBase store)
 {
     _requestExecutor = store.Conventions.DisableTopologyUpdates
         ? ClusterRequestExecutor.CreateForSingleNode(store.Urls[0], store.Certificate)
         : ClusterRequestExecutor.Create(store.Urls, store.Certificate);
 }
Esempio n. 16
0
        public async Task AddNode()
        {
            SetupCORSHeaders();

            var nodeUrl       = GetStringQueryString("url").TrimEnd('/');
            var watcher       = GetBoolValueQueryString("watcher", false);
            var assignedCores = GetIntValueQueryString("assignedCores", false);

            if (assignedCores <= 0)
            {
                throw new ArgumentException("Assigned cores must be greater than 0!");
            }

            var remoteIsHttps = nodeUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase);

            if (HttpContext.Request.IsHttps != remoteIsHttps)
            {
                throw new InvalidOperationException($"Cannot add node '{nodeUrl}' to cluster because it will create invalid mix of HTTPS & HTTP endpoints. A cluster must be only HTTPS or only HTTP.");
            }

            NodeInfo nodeInfo;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(nodeUrl, Server.ClusterCertificateHolder.Certificate))
                {
                    requestExecutor.DefaultTimeout = ServerStore.Engine.OperationTimeout;

                    var infoCmd = new GetNodeInfoCommand();
                    try
                    {
                        await requestExecutor.ExecuteAsync(infoCmd, ctx);
                    }
                    catch (AllTopologyNodesDownException e)
                    {
                        throw new InvalidOperationException($"Couldn't contact node at {nodeUrl}", e);
                    }

                    nodeInfo = infoCmd.Result;

                    if (ServerStore.IsPassive() && nodeInfo.TopologyId != null)
                    {
                        throw new TopologyMismatchException("You can't add new node to an already existing cluster");
                    }
                }

            if (assignedCores == null)
            {
                assignedCores = ServerStore.LicenseManager.GetCoresToAssign(nodeInfo.NumberOfCores);
            }

            if (assignedCores != null && assignedCores > nodeInfo.NumberOfCores)
            {
                throw new ArgumentException("Cannot add node because the assigned cores is larger " +
                                            $"than the available cores on that machine: {nodeInfo.NumberOfCores}");
            }

            ServerStore.EnsureNotPassive();

            if (ServerStore.LicenseManager.CanAddNode(nodeUrl, assignedCores, out var licenseLimit) == false)
            {
                SetLicenseLimitResponse(licenseLimit);
                return;
            }

            if (ServerStore.IsLeader())
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    string topologyId;
                    using (ctx.OpenReadTransaction())
                    {
                        var clusterTopology = ServerStore.GetClusterTopology(ctx);
                        var possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        if (possibleNode.HasUrl)
                        {
                            throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because this url is already used by node {possibleNode.NodeTag}");
                        }
                        topologyId = clusterTopology.TopologyId;
                    }

                    if (nodeInfo.TopologyId != null && topologyId != nodeInfo.TopologyId)
                    {
                        throw new TopologyMismatchException(
                                  $"Adding a new node to cluster failed. The new node is already in another cluster. Expected topology id: {topologyId}, but we get {nodeInfo.TopologyId}");
                    }

                    var nodeTag = nodeInfo.NodeTag == RachisConsensus.InitialTag ? null : nodeInfo.NodeTag;
                    CertificateDefinition oldServerCert = null;
                    X509Certificate2      certificate   = null;

                    if (remoteIsHttps)
                    {
                        if (nodeInfo.Certificate == null)
                        {
                            throw  new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because it has no certificate while trying to use HTTPS");
                        }

                        certificate = new X509Certificate2(Convert.FromBase64String(nodeInfo.Certificate));

                        if (certificate.NotBefore > DateTime.UtcNow)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' is not yet valid. It starts on {certificate.NotBefore}");
                        }

                        if (certificate.NotAfter < DateTime.UtcNow)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' expired on {certificate.NotAfter}");
                        }

                        var expected = GetStringQueryString("expectedThumbrpint", required: false);
                        if (expected != null)
                        {
                            if (certificate.Thumbprint != expected)
                            {
                                throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate thumbprint '{certificate.Thumbprint}' doesn't match the expected thumbprint '{expected}'.");
                            }
                        }

                        using (ctx.OpenReadTransaction())
                        {
                            var key      = Constants.Certificates.Prefix + certificate.Thumbprint;
                            var readCert = ServerStore.Cluster.Read(ctx, key);
                            if (readCert != null)
                            {
                                oldServerCert = JsonDeserializationServer.CertificateDefinition(readCert);
                            }
                        }

                        if (oldServerCert == null)
                        {
                            var certificateDefinition = new CertificateDefinition
                            {
                                Certificate       = nodeInfo.Certificate,
                                Thumbprint        = certificate.Thumbprint,
                                Name              = "Server Certificate for " + nodeUrl,
                                SecurityClearance = SecurityClearance.ClusterNode
                            };

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + certificate.Thumbprint, certificateDefinition));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }
                    }

                    await ServerStore.AddNodeToClusterAsync(nodeUrl, nodeTag, validateNotInTopology : false, asWatcher : watcher ?? false);

                    using (ctx.OpenReadTransaction())
                    {
                        var clusterTopology = ServerStore.GetClusterTopology(ctx);
                        var possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        nodeTag = possibleNode.HasUrl ? possibleNode.NodeTag : null;

                        if (certificate != null)
                        {
                            var key = Constants.Certificates.Prefix + certificate.Thumbprint;

                            var modifiedServerCert = JsonDeserializationServer.CertificateDefinition(ServerStore.Cluster.Read(ctx, key));

                            if (modifiedServerCert == null)
                            {
                                throw new ConcurrencyException("After adding the certificate, it was removed, shouldn't happen unless another admin removed it midway through.");
                            }

                            if (oldServerCert == null)
                            {
                                modifiedServerCert.Name = "Server certificate for Node " + nodeTag;
                            }
                            else
                            {
                                var value = "Node " + nodeTag;
                                if (modifiedServerCert.Name.Contains(value) == false)
                                {
                                    modifiedServerCert.Name += ", " + value;
                                }
                            }

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(key, modifiedServerCert));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }

                        ServerStore.LicenseManager.CalculateLicenseLimits(nodeTag, assignedCores, nodeInfo, forceFetchingNodeInfo: true);
                    }

                    NoContentStatus();
                    return;
                }
            }
            RedirectToLeader();
        }
        public async Task CanSnapshotManyCompareExchangeWithExpiration()
        {
            DebuggerAttachedTimeout.DisableLongTimespan = true;
            var count = 1024;

            using var leader   = GetNewServer();
            using var follower = GetNewServer();
            using (var store = GetDocumentStore(new Options
            {
                Server = leader
            }))
            {
                var expiry           = DateTime.Now.AddMinutes(2);
                var compareExchanges = new Dictionary <string, User>();
                await AddCompareExchangesWithExpire(count, compareExchanges, store, expiry);
                await AssertCompareExchanges(compareExchanges, store, expiry);

                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leader.WebUrl, null))
                    using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
                    {
                        await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(follower.WebUrl, watcher : true), ctx);

                        var cmd = new AddDatabaseNodeOperation(store.Database).GetCommand(store.Conventions, ctx);
                        await requestExecutor.ExecuteAsync(cmd, ctx);

                        await follower.ServerStore.Cluster.WaitForIndexNotification(cmd.Result.RaftCommandIndex);
                    }


                using (var fStore = GetDocumentStore(new Options
                {
                    Server = follower,
                    CreateDatabase = false,
                    ModifyDatabaseName = _ => store.Database,
                    ModifyDocumentStore = s => s.Conventions = new DocumentConventions {
                        DisableTopologyUpdates = true
                    }
                }))
                {
                    await AssertCompareExchanges(compareExchanges, store, expiry);

                    leader.ServerStore.Observer.Time.UtcDateTime = () => DateTime.UtcNow.AddMinutes(3);

                    var val = await WaitForValueAsync(async() =>
                    {
                        var stats = await store.Maintenance.SendAsync(new GetDetailedStatisticsOperation());
                        return(stats.CountOfCompareExchange);
                    }, 0);

                    Assert.Equal(0, val);

                    val = await WaitForValueAsync(async() =>
                    {
                        var stats = await fStore.Maintenance.SendAsync(new GetDetailedStatisticsOperation());
                        return(stats.CountOfCompareExchange);
                    }, 0);

                    Assert.Equal(0, val);
                }
            }
        }
Esempio n. 18
0
        public async Task AddNode()
        {
            var nodeUrl       = GetQueryStringValueAndAssertIfSingleAndNotEmpty("url");
            var tag           = GetStringQueryString("tag", false);
            var watcher       = GetBoolValueQueryString("watcher", false);
            var raftRequestId = GetRaftRequestIdFromQuery();

            var maxUtilizedCores = GetIntValueQueryString("maxUtilizedCores", false);

            if (maxUtilizedCores != null && maxUtilizedCores <= 0)
            {
                throw new ArgumentException("Max utilized cores cores must be greater than 0");
            }

            nodeUrl = nodeUrl.Trim();
            if (Uri.IsWellFormedUriString(nodeUrl, UriKind.Absolute) == false)
            {
                throw new InvalidOperationException($"Given node URL '{nodeUrl}' is not in a correct format.");
            }

            nodeUrl = UrlHelper.TryGetLeftPart(nodeUrl);
            var remoteIsHttps = nodeUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase);

            if (HttpContext.Request.IsHttps != remoteIsHttps)
            {
                throw new InvalidOperationException($"Cannot add node '{nodeUrl}' to cluster because it will create invalid mix of HTTPS & HTTP endpoints. A cluster must be only HTTPS or only HTTP.");
            }

            tag = tag?.Trim();

            NodeInfo nodeInfo;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(nodeUrl, Server.Certificate.Certificate))
                {
                    requestExecutor.DefaultTimeout = ServerStore.Engine.OperationTimeout;

                    // test connection to remote.
                    var result = await ServerStore.TestConnectionToRemote(nodeUrl, database : null);

                    if (result.Success == false)
                    {
                        throw new InvalidOperationException(result.Error);
                    }

                    // test connection from remote to destination
                    result = await ServerStore.TestConnectionFromRemote(requestExecutor, ctx, nodeUrl);

                    if (result.Success == false)
                    {
                        throw new InvalidOperationException(result.Error);
                    }

                    var infoCmd = new GetNodeInfoCommand();
                    try
                    {
                        await requestExecutor.ExecuteAsync(infoCmd, ctx);
                    }
                    catch (AllTopologyNodesDownException e)
                    {
                        throw new InvalidOperationException($"Couldn't contact node at {nodeUrl}", e);
                    }

                    nodeInfo = infoCmd.Result;

                    if (SchemaUpgrader.CurrentVersion.ServerVersion != nodeInfo.ServerSchemaVersion)
                    {
                        var nodesVersion = nodeInfo.ServerSchemaVersion == 0 ? "Pre 4.2 version" : nodeInfo.ServerSchemaVersion.ToString();
                        throw new InvalidOperationException($"Can't add node with mismatched storage schema version.{Environment.NewLine}" +
                                                            $"My version is {SchemaUpgrader.CurrentVersion.ServerVersion}, while node's version is {nodesVersion}");
                    }

                    if (ServerStore.IsPassive() && nodeInfo.TopologyId != null)
                    {
                        throw new TopologyMismatchException("You can't add new node to an already existing cluster");
                    }
                }

            if (ServerStore.ValidateFixedPort && nodeInfo.HasFixedPort == false)
            {
                throw new InvalidOperationException($"Failed to add node '{nodeUrl}' to cluster. " +
                                                    $"Node '{nodeUrl}' has port '0' in 'Configuration.Core.ServerUrls' setting. " +
                                                    "Adding a node with non fixed port is forbidden. Define a fixed port for the node to enable cluster creation.");
            }

            await ServerStore.EnsureNotPassiveAsync();

            ServerStore.LicenseManager.AssertCanAddNode();

            if (ServerStore.IsLeader())
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    var clusterTopology = ServerStore.GetClusterTopology();

                    var possibleNode = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                    if (possibleNode.HasUrl)
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because this url is already used by node {possibleNode.NodeTag}");
                    }

                    if (nodeInfo.ServerId == ServerStore.GetServerId())
                    {
                        throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because it's a synonym of the current node URL:{ServerStore.GetNodeHttpServerUrl()}");
                    }

                    if (nodeInfo.TopologyId != null)
                    {
                        AssertCanAddNodeWithTopologyId(clusterTopology, nodeInfo, nodeUrl);
                    }

                    var nodeTag = nodeInfo.NodeTag == RachisConsensus.InitialTag ? tag : nodeInfo.NodeTag;
                    CertificateDefinition oldServerCert = null;
                    X509Certificate2      certificate   = null;

                    if (remoteIsHttps)
                    {
                        if (nodeInfo.Certificate == null)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because it has no certificate while trying to use HTTPS");
                        }

                        certificate = new X509Certificate2(Convert.FromBase64String(nodeInfo.Certificate), (string)null, X509KeyStorageFlags.MachineKeySet);

                        var now = DateTime.UtcNow;
                        if (certificate.NotBefore.ToUniversalTime() > now)
                        {
                            // Because of time zone and time drift issues, we can't assume that the certificate generation will be
                            // proper. Because of that, we allow tolerance of the NotBefore to be a bit earlier / later than the
                            // current time. Clients may still fail to work with our certificate because of timing issues,
                            // but the admin needs to setup time sync properly and there isn't much we can do at that point
                            if ((certificate.NotBefore.ToUniversalTime() - now).TotalDays > 1)
                            {
                                throw new InvalidOperationException(
                                          $"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' is not yet valid. It starts on {certificate.NotBefore}");
                            }
                        }

                        if (certificate.NotAfter.ToUniversalTime() < now)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate '{certificate.FriendlyName}' expired on {certificate.NotAfter}");
                        }

                        var expected = GetStringQueryString("expectedThumbprint", required: false);
                        if (expected != null)
                        {
                            if (certificate.Thumbprint != expected)
                            {
                                throw new InvalidOperationException($"Cannot add node {nodeTag} with url {nodeUrl} to cluster because its certificate thumbprint '{certificate.Thumbprint}' doesn't match the expected thumbprint '{expected}'.");
                            }
                        }

                        // if it's the same server certificate as our own, we don't want to add it to the cluster
                        if (certificate.Thumbprint != Server.Certificate.Certificate.Thumbprint)
                        {
                            using (ctx.OpenReadTransaction())
                            {
                                var readCert = ServerStore.Cluster.GetCertificateByThumbprint(ctx, certificate.Thumbprint);
                                if (readCert != null)
                                {
                                    oldServerCert = JsonDeserializationServer.CertificateDefinition(readCert);
                                }
                            }

                            if (oldServerCert == null)
                            {
                                var certificateDefinition = new CertificateDefinition
                                {
                                    Certificate          = nodeInfo.Certificate,
                                    Thumbprint           = certificate.Thumbprint,
                                    PublicKeyPinningHash = certificate.GetPublicKeyPinningHash(),
                                    NotAfter             = certificate.NotAfter,
                                    Name = "Server Certificate for " + nodeUrl,
                                    SecurityClearance = SecurityClearance.ClusterNode
                                };

                                var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(certificate.Thumbprint, certificateDefinition,
                                                                                                             $"{raftRequestId}/put-new-certificate"));

                                await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                            }
                        }
                    }

                    await ServerStore.AddNodeToClusterAsync(nodeUrl, nodeTag, validateNotInTopology : true, asWatcher : watcher ?? false);

                    using (ctx.OpenReadTransaction())
                    {
                        clusterTopology = ServerStore.GetClusterTopology(ctx);
                        possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        nodeTag         = possibleNode.HasUrl ? possibleNode.NodeTag : null;

                        if (certificate != null && certificate.Thumbprint != Server.Certificate.Certificate.Thumbprint)
                        {
                            var modifiedServerCert = JsonDeserializationServer.CertificateDefinition(ServerStore.Cluster.GetCertificateByThumbprint(ctx, certificate.Thumbprint));

                            if (modifiedServerCert == null)
                            {
                                throw new ConcurrencyException("After adding the certificate, it was removed, shouldn't happen unless another admin removed it midway through.");
                            }

                            if (oldServerCert == null)
                            {
                                modifiedServerCert.Name = "Server certificate for Node " + nodeTag;
                            }
                            else
                            {
                                var value = "Node " + nodeTag;
                                if (modifiedServerCert.Name.Contains(value) == false)
                                {
                                    modifiedServerCert.Name += ", " + value;
                                }
                            }

                            var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(certificate.Thumbprint, modifiedServerCert, $"{raftRequestId}/put-modified-certificate"));

                            await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                        }

                        var detailsPerNode = new DetailsPerNode
                        {
                            MaxUtilizedCores    = maxUtilizedCores,
                            NumberOfCores       = nodeInfo.NumberOfCores,
                            InstalledMemoryInGb = nodeInfo.InstalledMemoryInGb,
                            UsableMemoryInGb    = nodeInfo.UsableMemoryInGb,
                            BuildInfo           = nodeInfo.BuildInfo,
                            OsInfo = nodeInfo.OsInfo
                        };

                        var maxCores = ServerStore.LicenseManager.LicenseStatus.MaxCores;

                        try
                        {
                            await ServerStore.PutNodeLicenseLimitsAsync(nodeTag, detailsPerNode, maxCores, $"{raftRequestId}/put-license-limits");
                        }
                        catch
                        {
                            // we'll retry this again later
                        }
                    }

                    NoContentStatus();
                    return;
                }
            }
            RedirectToLeader();
        }
Esempio n. 19
0
        protected async Task <(RavenServer Leader, List <ProcessNode> Peers, List <RavenServer> LocalPeers)> CreateMixedCluster(
            string[] peers, int localPeers = 0, IDictionary <string, string> customSettings = null, X509Certificate2 certificate = null)
        {
            var leaderServer = GetNewServer(new ServerCreationOptions {
                CustomSettings = customSettings
            });
            await leaderServer.ServerStore.EnsureNotPassiveAsync(leaderServer.WebUrl);

            var nodeAdded       = new ManualResetEvent(false);
            var expectedMembers = 2;

            leaderServer.ServerStore.Engine.TopologyChanged += (sender, clusterTopology) =>
            {
                var count = expectedMembers;
                if (clusterTopology.Promotables.Count == 0 && clusterTopology.Members.Count == count)
                {
                    var result = Interlocked.CompareExchange(ref expectedMembers, count + 1, count);
                    if (result == count)
                    {
                        nodeAdded.Set();
                    }
                }
            };

            using (leaderServer.ServerStore.ContextPool.AllocateOperationContext(out JsonOperationContext context))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leaderServer.WebUrl, certificate))
                {
                    var local = new List <RavenServer>();

                    for (int i = 0; i < localPeers; i++)
                    {
                        var peer = GetNewServer(new ServerCreationOptions {
                            CustomSettings = customSettings
                        });
                        var addCommand = new AddClusterNodeCommand(peer.WebUrl);
                        await requestExecutor.ExecuteAsync(addCommand, context);

                        Assert.True(nodeAdded.WaitOne(TimeSpan.FromSeconds(30)));
                        nodeAdded.Reset();
                        local.Add(peer);
                    }

                    var processes = new List <ProcessNode>();
                    foreach (var peer in peers)
                    {
                        processes.Add(await GetServerAsync(peer));
                    }

                    foreach (var processNode in processes)
                    {
                        var addCommand = new AddClusterNodeCommand(processNode.Url);
                        await requestExecutor.ExecuteAsync(addCommand, context);

                        Assert.True(nodeAdded.WaitOne(TimeSpan.FromSeconds(30)));
                        nodeAdded.Reset();
                    }

                    Assert.Equal(peers.Length + localPeers + 1, leaderServer.ServerStore.GetClusterTopology().Members.Count);
                    return(leaderServer, processes, local);
                }
        }
Esempio n. 20
0
        protected async Task WaitForExecutionOnRelevantNodes(JsonOperationContext context, string database, ClusterTopology clusterTopology, List <string> members, long index)
        {
            await ServerStore.Cluster.WaitForIndexNotification(index); // first let see if we commit this in the leader

            if (members.Count == 0)
            {
                throw new InvalidOperationException("Cannot wait for execution when there are no nodes to execute ON.");
            }

            var executors = new List <ClusterRequestExecutor>();

            try
            {
                using (var cts = CancellationTokenSource.CreateLinkedTokenSource(ServerStore.ServerShutdown))
                {
                    cts.CancelAfter(ServerStore.Configuration.Cluster.OperationTimeout.AsTimeSpan);

                    var waitingTasks            = new List <Task <Exception> >();
                    List <Exception> exceptions = null;

                    foreach (var member in members)
                    {
                        var url      = clusterTopology.GetUrlFromTag(member);
                        var executor = ClusterRequestExecutor.CreateForSingleNode(url, ServerStore.Server.Certificate.Certificate);
                        executors.Add(executor);
                        waitingTasks.Add(ExecuteTask(executor, member, cts.Token));
                    }

                    while (waitingTasks.Count > 0)
                    {
                        var task = await Task.WhenAny(waitingTasks);

                        waitingTasks.Remove(task);

                        if (task.Result == null)
                        {
                            continue;
                        }

                        var exception = task.Result.ExtractSingleInnerException();

                        if (exceptions == null)
                        {
                            exceptions = new List <Exception>();
                        }

                        exceptions.Add(exception);
                    }

                    if (exceptions != null)
                    {
                        var allTimeouts = true;
                        foreach (var exception in exceptions)
                        {
                            if (exception is OperationCanceledException)
                            {
                                continue;
                            }

                            allTimeouts = false;
                        }

                        var aggregateException = new AggregateException(exceptions);

                        if (allTimeouts)
                        {
                            throw new TimeoutException($"Waited too long for the raft command (number {index}) to be executed on any of the relevant nodes to this command.", aggregateException);
                        }

                        throw new InvalidDataException($"The database '{database}' was created but is not accessible, because all of the nodes on which this database was supposed to reside on, threw an exception.", aggregateException);
                    }
                }
            }
            finally
            {
                foreach (var executor in executors)
                {
                    executor.Dispose();
                }
            }

            async Task <Exception> ExecuteTask(RequestExecutor executor, string nodeTag, CancellationToken token)
            {
                try
                {
                    await executor.ExecuteAsync(new WaitForRaftIndexCommand(index), context, token : token);

                    return(null);
                }
                catch (RavenException re) when(re.InnerException is HttpRequestException)
                {
                    // we want to throw for self-checks
                    if (nodeTag == ServerStore.NodeTag)
                    {
                        return(re);
                    }

                    // ignore - we are ok when connection with a node cannot be established (test: AddDatabaseOnDisconnectedNode)
                    return(null);
                }
                catch (Exception e)
                {
                    return(e);
                }
            }
        }
Esempio n. 21
0
        public async Task AddNode()
        {
            SetupCORSHeaders();

            var nodeUrl = GetStringQueryString("url").TrimEnd('/');
            var watcher = GetBoolValueQueryString("watcher", false);

            var remoteIsHttps = nodeUrl.StartsWith("https:", StringComparison.OrdinalIgnoreCase);

            if (HttpContext.Request.IsHttps != remoteIsHttps)
            {
                throw new InvalidOperationException($"Cannot add node '{nodeUrl}' to cluster because it will create invalid mix of HTTPS & HTTP endpoints. A cluster must be only HTTPS or only HTTP.");
            }

            NodeInfo nodeInfo;

            using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(nodeUrl, Server.ClusterCertificateHolder.Certificate))
                {
                    requestExecutor.DefaultTimeout = ServerStore.Engine.OperationTimeout;

                    var infoCmd = new GetNodeInfoCommand();
                    try
                    {
                        await requestExecutor.ExecuteAsync(infoCmd, ctx);
                    }
                    catch (AllTopologyNodesDownException e)
                    {
                        throw new InvalidOperationException($"Couldn't contact node at {nodeUrl}", e);
                    }

                    nodeInfo = infoCmd.Result;

                    if (ServerStore.IsPassive() && nodeInfo.TopologyId != null)
                    {
                        throw new TopologyMismatchException("You can't add new node to an already existing cluster");
                    }
                }

            ServerStore.EnsureNotPassive();
            if (ServerStore.IsLeader())
            {
                using (ServerStore.ContextPool.AllocateOperationContext(out TransactionOperationContext ctx))
                {
                    string topologyId;
                    using (ctx.OpenReadTransaction())
                    {
                        var clusterTopology = ServerStore.GetClusterTopology(ctx);
                        var possibleNode    = clusterTopology.TryGetNodeTagByUrl(nodeUrl);
                        if (possibleNode.HasUrl)
                        {
                            throw new InvalidOperationException($"Can't add a new node on {nodeUrl} to cluster because this url is already used by node {possibleNode.NodeTag}");
                        }
                        topologyId = clusterTopology.TopologyId;
                    }

                    if (nodeInfo.TopologyId != null && topologyId != nodeInfo.TopologyId)
                    {
                        throw new TopologyMismatchException(
                                  $"Adding a new node to cluster failed. The new node is already in another cluster. Expected topology id: {topologyId}, but we get {nodeInfo.TopologyId}");
                    }

                    var nodeTag = nodeInfo.NodeTag == RachisConsensus.InitialTag
                        ? null : nodeInfo.NodeTag;

                    if (remoteIsHttps)
                    {
                        if (nodeInfo.Certificate == null)
                        {
                            throw  new InvalidOperationException($"Cannot add node {nodeTag} to cluster because it has no certificate while trying to use HTTPS");
                        }

                        var certificate = new X509Certificate2(Convert.FromBase64String(nodeInfo.Certificate));

                        if (certificate.NotBefore > DateTime.UtcNow)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} to cluster because its certificate '{certificate.FriendlyName}' is not yet valid. It starts on {certificate.NotBefore}");
                        }

                        if (certificate.NotAfter < DateTime.UtcNow)
                        {
                            throw new InvalidOperationException($"Cannot add node {nodeTag} to cluster because its certificate '{certificate.FriendlyName}' expired on {certificate.NotAfter}");
                        }

                        var expected = GetStringQueryString("expectedThumbrpint", required: false);
                        if (expected != null)
                        {
                            if (certificate.Thumbprint != expected)
                            {
                                throw new InvalidOperationException($"Cannot add node {nodeTag} to cluster because its certificate thumbprint '{certificate.Thumbprint}' doesn't match the expected thumbprint '{expected}'.");
                            }
                        }

                        var certificateDefinition = new CertificateDefinition
                        {
                            Certificate = nodeInfo.Certificate,
                            Thumbprint  = certificate.Thumbprint
                        };

                        var res = await ServerStore.PutValueInClusterAsync(new PutCertificateCommand(Constants.Certificates.Prefix + certificate.Thumbprint, certificateDefinition));

                        await ServerStore.Cluster.WaitForIndexNotification(res.Index);
                    }

                    await ServerStore.AddNodeToClusterAsync(nodeUrl, nodeTag, validateNotInTopology : false, asWatcher : watcher ?? false);

                    NoContentStatus();
                    return;
                }
            }
            RedirectToLeader();
        }
Esempio n. 22
0
 private static ClusterRequestExecutor CreateRequestExecutor(DocumentStoreBase store)
 {
     return(store.Conventions.DisableTopologyUpdates
             ? ClusterRequestExecutor.CreateForSingleNode(store.Urls[0], store.Certificate, store.Conventions)
             : ClusterRequestExecutor.Create(store.Urls, store.Certificate, store.Conventions));
 }
        public async Task CanSnapshotCompareExchangeWithExpiration()
        {
            var count = 45;

            var(_, leader) = await CreateRaftCluster(1, watcherCluster : true);

            using (var store = GetDocumentStore(options: new Options
            {
                Server = leader
            }))
            {
                var expiry           = DateTime.Now.AddMinutes(2);
                var compareExchanges = new Dictionary <string, User>();
                await CompareExchangeExpirationTest.AddCompareExchangesWithExpire(count, compareExchanges, store, expiry);

                await CompareExchangeExpirationTest.AssertCompareExchanges(compareExchanges, store, expiry);

                using (leader.ServerStore.Engine.ContextPool.AllocateOperationContext(out ClusterOperationContext context))
                    using (context.OpenReadTransaction())
                    {
                        Assert.Equal(count, CompareExchangeExpirationStorage.GetExpiredValues(context, long.MaxValue).Count());
                    }

                var server2    = GetNewServer();
                var server2Url = server2.ServerStore.GetNodeHttpServerUrl();
                Servers.Add(server2);

                using (var requestExecutor = ClusterRequestExecutor.CreateForSingleNode(leader.WebUrl, null))
                    using (requestExecutor.ContextPool.AllocateOperationContext(out var ctx))
                    {
                        await requestExecutor.ExecuteAsync(new AddClusterNodeCommand(server2Url, watcher : true), ctx);

                        var addDatabaseNode = new AddDatabaseNodeOperation(store.Database);
                        await store.Maintenance.Server.SendAsync(addDatabaseNode);
                    }

                using (server2.ServerStore.Engine.ContextPool.AllocateOperationContext(out ClusterOperationContext context))
                    using (context.OpenReadTransaction())
                    {
                        Assert.Equal(count, CompareExchangeExpirationStorage.GetExpiredValues(context, long.MaxValue).Count());
                    }

                var now = DateTime.UtcNow;
                leader.ServerStore.Observer.Time.UtcDateTime = () => now.AddMinutes(3);

                var leaderCount = WaitForValue(() =>
                {
                    using (leader.ServerStore.Engine.ContextPool.AllocateOperationContext(out ClusterOperationContext context))
                        using (context.OpenReadTransaction())
                        {
                            return(CompareExchangeExpirationStorage.GetExpiredValues(context, long.MaxValue).Count());
                        }
                }, 0, interval: 333);

                Assert.Equal(0, leaderCount);

                var server2count = WaitForValue(() =>
                {
                    using (server2.ServerStore.Engine.ContextPool.AllocateOperationContext(out ClusterOperationContext context))
                        using (context.OpenReadTransaction())
                        {
                            return(CompareExchangeExpirationStorage.GetExpiredValues(context, long.MaxValue).Count());
                        }
                }, 0, interval: 333);

                Assert.Equal(0, server2count);
            }
        }