/// <summary> /// <para> /// <b>INTERNAL USE ONLY:</b> Returns a <see cref="HiveBus"/> connected to the hive /// <b>neon</b> virtual host. This property is intended for use only by neonHIVE /// tools, services and containers. /// </para> /// <note> /// <b>WARNING:</b> The <see cref="HiveBus"/> instance returned should <b>NEVER BE DISPOSED</b>. /// </note> /// </summary> /// <param name="useBootstrap"> /// Optionally specifies that the settings returned should directly /// reference to the HiveMQ cluster nodes rather than routing traffic /// through the <b>private</b> traffic manager. This is used internally /// to resolve chicken-and-the-egg dilemmas for the traffic manager and /// proxy implementations that rely on HiveMQ messaging. /// </param> public HiveBus NeonHiveBus(bool useBootstrap = false) { var bus = neonHiveBus; if (bus != null) { return(bus); } lock (syncLock) { if (neonHiveBus != null) { return(neonHiveBus); } if (!useBootstrap) { return(neonHiveBus = parent.GetNeonSettings(useBootstrap: false).ConnectHiveBus()); } // Remember the bootstrap settings and then start a task that // periodically polls Consul for settings changes to raise the // [HiveMQBootstrapChanged] event when changes happen. bootstrapSettings = parent.GetNeonSettings(useBootstrap); neonHiveBus = bootstrapSettings.ConnectHiveBus(); bootstrapChangeDetector = Task.Run(() => BootstrapChangeDetector()); return(neonHiveBus); } }
/// <summary> /// Returns a RabbitMQ cluster connection using specified settings and credentials /// loaded from a Docker secret. This works only for Docker services where the /// Docker secret was mounted into the service containers. /// </summary> /// <param name="settings">The Couchbase settings.</param> /// <param name="secretName">The local name of the Docker secret holding the credentials.</param> /// <param name="dispatchConsumersAsync">Optionally enables <c>async</c> message consumers. This defaults to <c>false</c>.</param> /// <returns>The RabbitMQ <see cref="IConnection"/>.</returns> /// <remarks> /// The credentials must be formatted as JSON as serialized by the <see cref="Credentials"/> /// class. /// </remarks> public static IConnection ConnectUsingSecret(this HiveMQSettings settings, string secretName, bool dispatchConsumersAsync = false) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(settings.VirtualHost)); Covenant.Requires <ArgumentNullException>(settings.AmqpHosts != null && settings.AmqpHosts.Count > 0); Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(secretName)); var credentials = NeonHelper.JsonDeserialize <Credentials>(HiveHelper.GetSecret(secretName), dispatchConsumersAsync); return(new RabbitMQConnection(settings.ConnectRabbitMQ(credentials))); }
/// <summary> /// Polls Consul for changes to the <b>neon</b> virtual host HiveMQ /// bootstrap settings and raises the <see cref="HiveMQBootstrapChanged"/> /// when this happens. /// </summary> /// <returns>The tracking <see cref="Task"/>.</returns> private async Task BootstrapChangeDetector() { var pollInterval = TimeSpan.FromSeconds(120); // Delay for a random period of time between [0..pollInterval]. This // will help prevent Consul traffic spikes when services are started // at the same time. await Task.Delay(NeonHelper.RandTimespan(pollInterval)); // This will spin forever once started when a NeonHiveBus using bootstrap // settings is created above. This polls Consul for changes to the [neon] // HiveMQ virtual host settings stored in Consul. We'll be performing two // Consul lookups for each poll (one to get the [neon] vhost settings and // the other to obtain the bootstrap settings. // // When a settings change is detected, we'll first ensure that we've // establisted a new [neonHiveBus] connection using the new settings and // then we'll raise the change event. while (true) { try { var latestBootstrapSettngs = parent.GetNeonSettings(useBootstrap: true); if (!NeonHelper.JsonEquals(bootstrapSettings, latestBootstrapSettngs)) { // The latest bootstrap settings don't match what we used to // connect the current [bus]. lock (syncLock) { bootstrapSettings = latestBootstrapSettngs; neonHiveBus = bootstrapSettings.ConnectHiveBus(); } var handler = bootstrapChangedEvent; handler?.Invoke(this, latestBootstrapSettngs); } } catch (Exception e) { log.LogError(e); } await Task.Delay(pollInterval); } }
/// <summary> /// <para> /// <b>Internal use by neonHIVE services only:</b> Generates a <see cref="HiveMQSettings"/> instance /// that directly references the HiveMQ nodes and then persists this to Consul as /// <see cref="HiveGlobals.HiveMQSettingsBootstrap"/>. /// </para> /// <note> /// The persisted settings do not include any credentials. /// </note> /// </summary> public void SaveBootstrapSettings() { var settings = new HiveMQSettings() { AmqpPort = HiveHostPorts.HiveMQAMQP, AdminPort = HiveHostPorts.HiveMQManagement, TlsEnabled = false, Username = null, Password = null, VirtualHost = HiveConst.HiveMQNeonVHost }; foreach (var node in hive.Definition.SortedNodes.Where(n => n.Labels.HiveMQ)) { settings.AmqpHosts.Add($"{node.Name}.{hive.Definition.Hostnames.HiveMQ}"); } foreach (var node in hive.Definition.SortedNodes.Where(n => n.Labels.HiveMQManager)) { settings.AdminHosts.Add($"{node.Name}.{hive.Definition.Hostnames.HiveMQ}"); } hive.Globals.Set(HiveGlobals.HiveMQSettingsBootstrap, settings); }
/// <summary> /// Actually starts RabbitMQ within the initialization <see cref="Action"/>. You'll /// generally want to use <see cref="Start(string, string, List{string}, string, string, bool)"/> /// but this method is used internally or for special situations. /// </summary> /// <param name="image">Optionally specifies the RabbitMQ container image (defaults to <b>nhive/rabbitmq-test:latest</b>).</param> /// <param name="name">Optionally specifies the RabbitMQ container name (defaults to <c>rmq-test</c>).</param> /// <param name="env">Optional environment variables to be passed to the RabbitMQ container, formatted as <b>NAME=VALUE</b> or just <b>NAME</b>.</param> /// <param name="username">Optional RabbitMQ username (defaults to <b>Administrator</b>).</param> /// <param name="password">Optional RabbitMQ password (defaults to <b>password</b>).</param> /// <param name="precompile"> /// Optionally configure RabbitMQ precompiling. This may improve RabbitMQ performance by /// 20-50% at the cost of an additional 30-45 seconds of startup time. This can be /// enabled for performance oriented unit tests. This defaults to <c>false</c>. /// </param> /// <returns> /// <c>true</c> if the fixture wasn't previously initialized and /// this method call initialized it or <c>false</c> if the fixture /// was already initialized. /// </returns> public void StartInAction( string image = "nhive/rabbitmq-test:latest", string name = "rmq-test", List <string> env = null, string username = "******", string password = "******", bool precompile = false) { Covenant.Requires <ArgumentNullException>(!string.IsNullOrEmpty(image)); base.CheckWithinAction(); if (IsInitialized) { return; } if (precompile) { if (env == null) { env = new List <string>(); } env.Add("RABBITMQ_HIPE_COMPILE=1"); } RunContainer( name, image, new string[] { "--detach", "--mount", "type=volume,target=/var/lib/rabbitmq", "--publish", $"{NetworkPorts.AMQP}:{NetworkPorts.AMQP}", "--publish", $"{NetworkPorts.RabbitMQAdmin}:{NetworkPorts.RabbitMQAdmin}", "--env", "DEBUG=false" }, env: env); var hosts = new string[] { "127.0.0.1" }; Settings = new HiveMQSettings() { AdminHosts = hosts.ToList(), AmqpHosts = hosts.ToList(), Username = username, Password = password, NeonLog = false }; Covenant.Assert(Settings.IsValid); // Wait for container to warm up by ensuring that we can connect admin and AMQP clients. NeonHelper.WaitFor( () => { try { using (Settings.ConnectRabbitMQ(Username, Password, dispatchConsumersAsync: false)) { return(true); } } catch { return(false); } }, timeout: TimeSpan.FromSeconds(precompile ? 120 : 30), // We need to wait longer then precompiling (it takes an additional 45-60 seconds to compile). pollTime: TimeSpan.FromSeconds(0.5)); NeonHelper.WaitFor( () => { try { using (var manager = Settings.ConnectManager(Username, Password)) { // Ensure that the manager can actually process requests. return(!manager.GetVHostsAsync().Result.IsEmpty()); } } catch { return(false); } }, timeout: TimeSpan.FromSeconds(30), pollTime: TimeSpan.FromSeconds(0.5)); }