예제 #1
0
        protected override void Create(SingleNodeOptions options)
        {
            var dbPath = Path.GetFullPath(ResolveDbPath(options.DbPath, options.HttpPort));

            _dbLock = new ExclusiveDbLock(dbPath);
            if (!_dbLock.Acquire())
            {
                throw new Exception(string.Format("Couldn't acquire exclusive lock on DB at '{0}'.", dbPath));
            }

            var db             = new TFChunkDb(CreateDbConfig(dbPath, options.CachedChunks, options.ChunksCacheSize));
            var vnodeSettings  = GetVNodeSettings(options);
            var dbVerifyHashes = !options.SkipDbVerify;
            var runProjections = options.RunProjections;

            Log.Info("\n{0,-25} {1}\n{2,-25} {3} (0x{3:X})\n{4,-25} {5} (0x{5:X})\n{6,-25} {7} (0x{7:X})\n{8,-25} {9} (0x{9:X})\n",
                     "DATABASE:", db.Config.Path,
                     "WRITER CHECKPOINT:", db.Config.WriterCheckpoint.Read(),
                     "CHASER CHECKPOINT:", db.Config.ChaserCheckpoint.Read(),
                     "EPOCH CHECKPOINT:", db.Config.EpochCheckpoint.Read(),
                     "TRUNCATE CHECKPOINT:", db.Config.TruncateCheckpoint.Read());

            var enabledNodeSubsystems = runProjections >= RunProjections.System
                ? new[] { NodeSubsystems.Projections }
                : new NodeSubsystems[0];

            _projections = new Projections.Core.ProjectionsSubsystem(options.ProjectionThreads, runProjections);
            _node        = new SingleVNode(db, vnodeSettings, dbVerifyHashes, ESConsts.MemTableEntryCount, _projections);
            RegisterWebControllers(enabledNodeSubsystems);
            RegisterUIProjections();
        }
예제 #2
0
        public async Task can_release_when_running_in_task_pool()
        {
            using var sut = new ExclusiveDbLock(GetDbPath());
            Assert.True(sut.Acquire());
            Assert.True(sut.IsAcquired);
            await Task.Delay(1);

            sut.Release();
        }
예제 #3
0
        protected override void Create(ClusterNodeOptions opts)
        {
            var dbPath = opts.Db;

            if (!opts.MemDb)
            {
                var absolutePath = Path.GetFullPath(dbPath);
                if (Runtime.IsWindows)
                {
                    absolutePath = absolutePath.ToLower();
                }

                _dbLock = new ExclusiveDbLock(absolutePath);
                if (!_dbLock.Acquire())
                {
                    throw new Exception(string.Format("Couldn't acquire exclusive lock on DB at '{0}'.", dbPath));
                }
            }

            _clusterNodeMutex = new ClusterNodeMutex();
            if (!_clusterNodeMutex.Acquire())
            {
                throw new Exception(string.Format("Couldn't acquire exclusive Cluster Node mutex '{0}'.",
                                                  _clusterNodeMutex.MutexName));
            }

            if (!opts.DiscoverViaDns && opts.GossipSeed.Length == 0)
            {
                if (opts.ClusterSize == 1)
                {
                    Log.Info("DNS discovery is disabled, but no gossip seed endpoints have been specified. Since "
                             + "the cluster size is set to 1, this may be intentional. Gossip seeds can be specified "
                             + "using the `GossipSeed` option.");
                }
            }

            var runProjections        = opts.RunProjections;
            var enabledNodeSubsystems = runProjections >= ProjectionType.System
                                ? new[] { NodeSubsystems.Projections }
                                : new NodeSubsystems[0];

            _node = BuildNode(opts);
            RegisterWebControllers(enabledNodeSubsystems, opts);
        }
예제 #4
0
        protected override void Create(ClusterNodeOptions opts)
        {
            var dbPath = opts.Db;

            if (opts.Insecure)
            {
                Log.Warning(
                    "\n==============================================================================================================\n" +
                    "INSECURE MODE IS ON. THIS MODE IS *NOT* RECOMMENDED FOR PRODUCTION USE.\n" +
                    "INSECURE MODE WILL DISABLE ALL AUTHENTICATION, AUTHORIZATION AND TRANSPORT SECURITY FOR ALL CLIENTS AND NODES.\n" +
                    "==============================================================================================================\n");
            }

            var deprecationMessages = string.Empty;

            if (opts.EnableAtomPubOverHTTP)
            {
                deprecationMessages +=
                    "- AtomPub over HTTP Interface has been deprecated as of version 20.6.0. It is recommended to use gRPC instead.\n";
            }

            if (opts.DisableInternalTcpTls)
            {
                deprecationMessages +=
                    $"- The '{nameof(Options.DisableInternalTcpTls)}' option has been deprecated as of version 20.6.1 and currently has no effect. "
                    + $"Please use the '{nameof(Options.Insecure)}' option instead.\n";
            }

            if (opts.EnableExternalTCP)
            {
                deprecationMessages +=
                    "- The Legacy TCP Client Interface has been deprecated as of version 20.6.0. "
                    + $"The External TCP Interface can be re-enabled with the '{nameof(Options.EnableExternalTCP)}' option. "
                    + "It is recommended to use gRPC instead.\n";
            }

            if (opts.DisableExternalTcpTls)
            {
                deprecationMessages +=
                    $"- The '{nameof(Options.DisableExternalTcpTls)}' option has been deprecated as of version 20.6.1.\n";
            }

            if (!opts.GossipOnSingleNode)
            {
                deprecationMessages +=
                    $"- The '{nameof(Options.GossipOnSingleNode)}' option has been deprecated as of version 21.2\n";
            }

            if (deprecationMessages.Any())
            {
                Log.Warning($"DEPRECATED\n{deprecationMessages}");
            }

            if (!opts.MemDb)
            {
                var absolutePath = Path.GetFullPath(dbPath);
                if (Runtime.IsWindows)
                {
                    absolutePath = absolutePath.ToLower();
                }

                _dbLock = new ExclusiveDbLock(absolutePath);
                if (!_dbLock.Acquire())
                {
                    throw new InvalidConfigurationException($"Couldn't acquire exclusive lock on DB at '{dbPath}'.");
                }
            }

            _clusterNodeMutex = new ClusterNodeMutex();
            if (!_clusterNodeMutex.Acquire())
            {
                throw new InvalidConfigurationException($"Couldn't acquire exclusive Cluster Node mutex '{_clusterNodeMutex.MutexName}'.");
            }

            if (!opts.DiscoverViaDns && opts.GossipSeed.Length == 0)
            {
                if (opts.ClusterSize == 1)
                {
                    Log.Information(
                        "DNS discovery is disabled, but no gossip seed endpoints have been specified. Since "
                        + "the cluster size is set to 1, this may be intentional. Gossip seeds can be specified "
                        + "using the `GossipSeed` option.");
                }
            }

            var runProjections        = opts.RunProjections;
            var enabledNodeSubsystems = runProjections >= ProjectionType.System
                                ? new[] { NodeSubsystems.Projections }
                                : new NodeSubsystems[0];

            Node = BuildNode(opts, LoadConfig);

            RegisterWebControllers(enabledNodeSubsystems, opts);
        }
예제 #5
0
        protected override void Create(ClusterNodeOptions opts)
        {
            var dbPath = opts.Db;

            if (opts.Dev)
            {
                Log.Warn(
                    "\n========================================================================================================\n" +
                    "DEVELOPMENT MODE IS ON. THIS MODE IS *NOT* INTENDED FOR PRODUCTION USE.\n" +
                    "WHEN IN DEVELOPMENT MODE EVENT STORE WILL\n" +
                    " - NOT WRITE ANY DATA TO DISK.\n" +
                    " - USE A SELF SIGNED CERTIFICATE.\n" +
                    "========================================================================================================\n");
            }

            if (!opts.MemDb)
            {
                var absolutePath = Path.GetFullPath(dbPath);
                if (Runtime.IsWindows)
                {
                    absolutePath = absolutePath.ToLower();
                }

                _dbLock = new ExclusiveDbLock(absolutePath);
                if (!_dbLock.Acquire())
                {
                    throw new Exception(string.Format("Couldn't acquire exclusive lock on DB at '{0}'.", dbPath));
                }
            }

            _clusterNodeMutex = new ClusterNodeMutex();
            if (!_clusterNodeMutex.Acquire())
            {
                throw new Exception(string.Format("Couldn't acquire exclusive Cluster Node mutex '{0}'.",
                                                  _clusterNodeMutex.MutexName));
            }

            if (!opts.DiscoverViaDns && opts.GossipSeed.Length == 0)
            {
                if (opts.ClusterSize == 1)
                {
                    Log.Info("DNS discovery is disabled, but no gossip seed endpoints have been specified. Since "
                             + "the cluster size is set to 1, this may be intentional. Gossip seeds can be specified "
                             + "using the `GossipSeed` option.");
                }
            }

            var runProjections        = opts.RunProjections;
            var enabledNodeSubsystems = runProjections >= ProjectionType.System
                                ? new[] { NodeSubsystems.Projections }
                                : new NodeSubsystems[0];

            _node = BuildNode(opts);

            RegisterWebControllers(enabledNodeSubsystems, opts);

            _host = new WebHostBuilder()
                    .UseKestrel(o => {
                o.Listen(opts.IntIp, opts.IntHttpPort);
                o.Listen(opts.ExtIp, opts.ExtHttpPort, listenOptions => listenOptions.UseHttps(_node.Certificate));
            })
                    .UseStartup(new ClusterVNodeStartup(_node))
                    .ConfigureLogging(logging => {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Warning);
            })
                    .UseNLog()
                    .Build();
        }
예제 #6
0
        protected override void Create(ClusterNodeOptions opts)
        {
            var dbPath = Path.GetFullPath(ResolveDbPath(opts.DbPath, opts.ExternalHttpPort));

            if (!opts.InMemDb)
            {
                _dbLock = new ExclusiveDbLock(dbPath);
                if (!_dbLock.Acquire())
                {
                    throw new Exception(string.Format("Couldn't acquire exclusive lock on DB at '{0}'.", dbPath));
                }
            }
            _clusterNodeMutex = new ClusterNodeMutex();
            if (!_clusterNodeMutex.Acquire())
            {
                throw new Exception(string.Format("Couldn't acquire exclusive Cluster Node mutex '{0}'.", _clusterNodeMutex.MutexName));
            }

            var dbConfig = CreateDbConfig(dbPath, opts.CachedChunks, opts.ChunksCacheSize, opts.InMemDb);

            FileStreamExtensions.ConfigureFlush(disableFlushToDisk: opts.UnsafeDisableFlushToDisk);
            var db            = new TFChunkDb(dbConfig);
            var vNodeSettings = GetClusterVNodeSettings(opts);

            IGossipSeedSource gossipSeedSource;

            if (opts.DiscoverViaDns)
            {
                gossipSeedSource = new DnsGossipSeedSource(opts.ClusterDns, opts.ClusterGossipPort);
            }
            else
            {
                if (opts.GossipSeeds.Length == 0)
                {
                    if (opts.ClusterSize > 1)
                    {
                        Log.Error(string.Format("DNS discovery is disabled, but no gossip seed endpoints have been specified. " +
                                                "Specify gossip seeds using the --{0} command line option.", Opts.GossipSeedCmd));
                    }
                    else
                    {
                        Log.Info(string.Format("DNS discovery is disabled, but no gossip seed endpoints have been specified. Since" +
                                               "the cluster size is set to 1, this may be intentional. Gossip seeds can be specified" +
                                               "seeds using the --{0} command line option.", Opts.GossipSeedCmd));
                    }
                }

                gossipSeedSource = new KnownEndpointGossipSeedSource(opts.GossipSeeds);
            }

            var dbVerifyHashes = !opts.SkipDbVerify;
            var runProjections = opts.RunProjections;

            Log.Info("\n{0,-25} {1}\n"
                     + "{2,-25} {3}\n"
                     + "{4,-25} {5} (0x{5:X})\n"
                     + "{6,-25} {7} (0x{7:X})\n"
                     + "{8,-25} {9} (0x{9:X})\n"
                     + "{10,-25} {11} (0x{11:X})\n",
                     "INSTANCE ID:", vNodeSettings.NodeInfo.InstanceId,
                     "DATABASE:", db.Config.Path,
                     "WRITER CHECKPOINT:", db.Config.WriterCheckpoint.Read(),
                     "CHASER CHECKPOINT:", db.Config.ChaserCheckpoint.Read(),
                     "EPOCH CHECKPOINT:", db.Config.EpochCheckpoint.Read(),
                     "TRUNCATE CHECKPOINT:", db.Config.TruncateCheckpoint.Read());

            var enabledNodeSubsystems = runProjections >= RunProjections.System
                ? new[] { NodeSubsystems.Projections }
                : new NodeSubsystems[0];

            _projections = new Projections.Core.ProjectionsSubsystem(opts.ProjectionThreads, opts.RunProjections);
            _node        = new ClusterVNode(db, vNodeSettings, gossipSeedSource, dbVerifyHashes, opts.MaxMemTableSize, _projections);
            RegisterWebControllers(enabledNodeSubsystems, vNodeSettings);
            RegisterUiProjections();
        }
예제 #7
0
        public ClusterVNodeHostedService(ClusterVNodeOptions options)
        {
            if (options == null)
            {
                throw new ArgumentNullException(nameof(options));
            }
            _options = options.Projections.RunProjections >= ProjectionType.System
                                ? options.WithSubsystem(new ProjectionsSubsystem(
                                                            new ProjectionSubsystemOptions(
                                                                options.Projections.ProjectionThreads,
                                                                options.Projections.RunProjections,
                                                                options.Application.StartStandardProjections,
                                                                TimeSpan.FromMinutes(options.Projections.ProjectionsQueryExpiry),
                                                                options.Projections.FaultOutOfOrderProjections,
                                                                options.Projections.ProjectionCompilationTimeout,
                                                                options.Projections.ProjectionExecutionTimeout)))
                                : options;

            if (!_options.Database.MemDb)
            {
                var absolutePath = Path.GetFullPath(_options.Database.Db);
                if (Runtime.IsWindows)
                {
                    absolutePath = absolutePath.ToLower();
                }

                _dbLock = new ExclusiveDbLock(absolutePath);
                if (!_dbLock.Acquire())
                {
                    throw new InvalidConfigurationException($"Couldn't acquire exclusive lock on DB at '{_options.Database.Db}'.");
                }
            }

            _clusterNodeMutex = new ClusterNodeMutex();
            if (!_clusterNodeMutex.Acquire())
            {
                throw new InvalidConfigurationException($"Couldn't acquire exclusive Cluster Node mutex '{_clusterNodeMutex.MutexName}'.");
            }

            var authorizationConfig = string.IsNullOrEmpty(_options.Auth.AuthorizationConfig)
                                ? _options.Application.Config
                                : _options.Auth.AuthorizationConfig;

            var authenticationConfig = string.IsNullOrEmpty(_options.Auth.AuthenticationConfig)
                                ? _options.Application.Config
                                : _options.Auth.AuthenticationConfig;

            var pluginLoader = new PluginLoader(new DirectoryInfo(Locations.PluginsDirectory));

            var plugInContainer = FindPlugins();

            if (_options.Database.DbLogFormat == DbLogFormat.V2)
            {
                var logFormatFactory = new LogV2FormatAbstractorFactory();
                Node = ClusterVNode.Create(_options, logFormatFactory, GetAuthenticationProviderFactory(),
                                           GetAuthorizationProviderFactory(), GetPersistentSubscriptionConsumerStrategyFactories());
            }
            else if (_options.Database.DbLogFormat == DbLogFormat.ExperimentalV3)
            {
                var logFormatFactory = new LogV3FormatAbstractorFactory();
                Node = ClusterVNode.Create(_options, logFormatFactory, GetAuthenticationProviderFactory(),
                                           GetAuthorizationProviderFactory(), GetPersistentSubscriptionConsumerStrategyFactories());
            }
            else
            {
                throw new ArgumentOutOfRangeException("Unexpected log format specified.");
            }

            var runProjections        = _options.Projections.RunProjections;
            var enabledNodeSubsystems = runProjections >= ProjectionType.System
                                ? new[] { NodeSubsystems.Projections }
                                : Array.Empty <NodeSubsystems>();

            RegisterWebControllers(enabledNodeSubsystems);

            AuthorizationProviderFactory GetAuthorizationProviderFactory()
            {
                if (_options.Application.Insecure)
                {
                    return(new AuthorizationProviderFactory(_ => new PassthroughAuthorizationProviderFactory()));
                }
                var authorizationTypeToPlugin = new Dictionary <string, AuthorizationProviderFactory> {
                    {
                        "internal", new AuthorizationProviderFactory(components =>
                                                                     new LegacyAuthorizationProviderFactory(components.MainQueue))
                    }
                };

                foreach (var potentialPlugin in pluginLoader.Load <IAuthorizationPlugin>())
                {
                    try {
                        var commandLine = potentialPlugin.CommandLineName.ToLowerInvariant();
                        Log.Information(
                            "Loaded authorization plugin: {plugin} version {version} (Command Line: {commandLine})",
                            potentialPlugin.Name, potentialPlugin.Version, commandLine);
                        authorizationTypeToPlugin.Add(commandLine,
                                                      new AuthorizationProviderFactory(_ =>
                                                                                       potentialPlugin.GetAuthorizationProviderFactory(authorizationConfig)));
                    } catch (CompositionException ex) {
                        Log.Error(ex, "Error loading authentication plugin.");
                    }
                }

                if (!authorizationTypeToPlugin.TryGetValue(_options.Auth.AuthorizationType.ToLowerInvariant(),
                                                           out var factory))
                {
                    throw new ApplicationInitializationException(
                              $"The authorization type {_options.Auth.AuthorizationType} is not recognised. If this is supposed " +
                              $"to be provided by an authorization plugin, confirm the plugin DLL is located in {Locations.PluginsDirectory}." +
                              Environment.NewLine +
                              $"Valid options for authorization are: {string.Join(", ", authorizationTypeToPlugin.Keys)}.");
                }

                return(factory);
            }
예제 #8
0
        protected override void Create(ClusterNodeOptions opts)
        {
            var dbPath = opts.Db;

            if (opts.Dev)
            {
                Log.Warning(
                    "\n========================================================================================================\n" +
                    "DEVELOPMENT MODE IS ON. THIS MODE IS *NOT* INTENDED FOR PRODUCTION USE.\n" +
                    "WHEN IN DEVELOPMENT MODE EVENT STORE WILL\n" +
                    " - NOT WRITE ANY DATA TO DISK.\n" +
                    " - USE A SELF SIGNED CERTIFICATE.\n" +
                    "========================================================================================================\n");
            }

            Log.Information(
                "\nINTERFACES\n" +
                "External TCP (Protobuf)\n" +
                $"\tEnabled\t: {opts.EnableExternalTCP}\n" +
                $"\tPort\t: {(opts.ExtTcpPort)}\n" +
                "External HTTP (AtomPub)\n" +
                $"\tEnabled\t: {opts.EnableAtomPubOverHTTP}\n" +
                $"\tPort\t: {opts.ExtHttpPort}\n");

            if (opts.EnableAtomPubOverHTTP)
            {
                Log.Warning("\n DEPRECATION WARNING: AtomPub over HTTP Interface has been deprecated as of version 20.02. It is recommended to use gRPC instead.\n");
            }

            if (opts.EnableExternalTCP)
            {
                Log.Warning(
                    "\n DEPRECATION WARNING: The Legacy TCP Client Interface has been deprecated as of version 20.02. "
                    + $"The External TCP Interface can be re-enabled with the '{nameof(Options.EnableExternalTCP)}' option. "
                    + "It is recommended to use gRPC instead.\n");
            }

            if (!opts.MemDb)
            {
                var absolutePath = Path.GetFullPath(dbPath);
                if (Runtime.IsWindows)
                {
                    absolutePath = absolutePath.ToLower();
                }

                _dbLock = new ExclusiveDbLock(absolutePath);
                if (!_dbLock.Acquire())
                {
                    throw new Exception($"Couldn't acquire exclusive lock on DB at '{dbPath}'.");
                }
            }

            _clusterNodeMutex = new ClusterNodeMutex();
            if (!_clusterNodeMutex.Acquire())
            {
                throw new Exception($"Couldn't acquire exclusive Cluster Node mutex '{_clusterNodeMutex.MutexName}'.");
            }

            if (!opts.DiscoverViaDns && opts.GossipSeed.Length == 0)
            {
                if (opts.ClusterSize == 1)
                {
                    Log.Information(
                        "DNS discovery is disabled, but no gossip seed endpoints have been specified. Since "
                        + "the cluster size is set to 1, this may be intentional. Gossip seeds can be specified "
                        + "using the `GossipSeed` option.");
                }
            }

            var runProjections        = opts.RunProjections;
            var enabledNodeSubsystems = runProjections >= ProjectionType.System
                                ? new[] { NodeSubsystems.Projections }
                                : new NodeSubsystems[0];

            Node = BuildNode(opts);

            RegisterWebControllers(enabledNodeSubsystems, opts);
        }
예제 #9
0
 public void releasing_before_acquiring_throws()
 {
     using var sut = new ExclusiveDbLock(GetDbPath());
     Assert.Throws <InvalidOperationException>(() => sut.Release());
 }
예제 #10
0
 public void acquiring_twice_throws()
 {
     using var sut = new ExclusiveDbLock(GetDbPath());
     sut.Acquire();
     Assert.Throws <InvalidOperationException>(() => sut.Acquire());
 }
예제 #11
0
        protected override void Create(ClusterNodeOptions opts)
        {
            var dbPath = opts.Db;

            if (opts.Dev)
            {
                Log.Warning(
                    "\n========================================================================================================\n" +
                    "DEVELOPMENT MODE IS ON. THIS MODE IS *NOT* INTENDED FOR PRODUCTION USE.\n" +
                    "WHEN IN DEVELOPMENT MODE EVENTSTOREDB WILL\n" +
                    " - DISABLE TLS ON ALL TCP INTERFACES.\n" +
                    " - ENABLE THE ATOMPUB PROTOCOL OVER HTTP.\n" +
                    "========================================================================================================\n");
            }
            else if (opts.Insecure)
            {
                Log.Warning(
                    "\n========================================================================================================\n" +
                    "INSECURE MODE IS ON. THIS MODE IS *NOT* RECOMMENDED FOR PRODUCTION USE.\n" +
                    "WHEN IN INSECURE MODE EVENTSTOREDB WILL\n" +
                    " - DISABLE ALL AUTHENTICATION AND AUTHORIZATION.\n" +
                    " - DISABLE TLS ON ALL TCP AND HTTP INTERFACES.\n" +
                    "========================================================================================================\n");
            }

            Log.Information(
                "\nINTERFACES\n" +
                "External TCP (Protobuf)\n" +
                $"\tEnabled\t: {opts.EnableExternalTCP}\n" +
                $"\tPort\t: {(opts.ExtTcpPort)}\n" +
                "HTTP (AtomPub)\n" +
                $"\tEnabled\t: {opts.EnableAtomPubOverHTTP}\n" +
                $"\tPort\t: {opts.HttpPort}\n");

            if (opts.EnableAtomPubOverHTTP)
            {
                Log.Warning(
                    "\n DEPRECATION WARNING: AtomPub over HTTP Interface has been deprecated as of version 20.6.0. It is recommended to use gRPC instead.\n");
            }

            if (opts.DisableInternalTcpTls)
            {
                Log.Warning(
                    $"\n DEPRECATION WARNING: The '{nameof(Options.DisableInternalTcpTls)}' option has been deprecated as of version 20.6.1 and currently has no effect. "
                    + $"Please use the '{nameof(Options.Insecure)}' option instead.\n");
            }

            if (opts.EnableExternalTCP)
            {
                Log.Warning(
                    "\n DEPRECATION WARNING: The Legacy TCP Client Interface has been deprecated as of version 20.6.0. "
                    + $"The External TCP Interface can be re-enabled with the '{nameof(Options.EnableExternalTCP)}' option. "
                    + "It is recommended to use gRPC instead.\n");
            }

            if (opts.DisableExternalTcpTls)
            {
                Log.Warning(
                    $"\n DEPRECATION WARNING: The '{nameof(Options.DisableExternalTcpTls)}' option has been deprecated as of version 20.6.1.\n");
            }


            if (!opts.MemDb)
            {
                var absolutePath = Path.GetFullPath(dbPath);
                if (Runtime.IsWindows)
                {
                    absolutePath = absolutePath.ToLower();
                }

                _dbLock = new ExclusiveDbLock(absolutePath);
                if (!_dbLock.Acquire())
                {
                    throw new InvalidConfigurationException($"Couldn't acquire exclusive lock on DB at '{dbPath}'.");
                }
            }

            _clusterNodeMutex = new ClusterNodeMutex();
            if (!_clusterNodeMutex.Acquire())
            {
                throw new InvalidConfigurationException($"Couldn't acquire exclusive Cluster Node mutex '{_clusterNodeMutex.MutexName}'.");
            }

            if (!opts.DiscoverViaDns && opts.GossipSeed.Length == 0)
            {
                if (opts.ClusterSize == 1)
                {
                    Log.Information(
                        "DNS discovery is disabled, but no gossip seed endpoints have been specified. Since "
                        + "the cluster size is set to 1, this may be intentional. Gossip seeds can be specified "
                        + "using the `GossipSeed` option.");
                }
            }

            var runProjections        = opts.RunProjections;
            var enabledNodeSubsystems = runProjections >= ProjectionType.System
                                ? new[] { NodeSubsystems.Projections }
                                : new NodeSubsystems[0];

            Node = BuildNode(opts, LoadConfig);

            RegisterWebControllers(enabledNodeSubsystems, opts);
        }