Exemple #1
0
        public void ClusterMemberSettings_ComputeIntervals()
        {
            ClusterMemberSettings settings = new ClusterMemberSettings((MsgEP)"logical://foo");

            settings.MasterBroadcastInterval = TimeSpan.FromSeconds(2);
            settings.MissingMasterCount      = 3;
            settings.SlaveUpdateInterval     = TimeSpan.FromSeconds(4);
            settings.MissingSlaveCount       = 5;

            Assert.AreEqual(TimeSpan.FromSeconds(2 * 3), settings.MissingMasterInterval);
            Assert.AreEqual(TimeSpan.FromSeconds(4 * 5), settings.MissingSlaveInterval);
        }
Exemple #2
0
        public void ClusterMemberSettings_Equals()
        {
            ClusterMemberSettings s1, s2;

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            Assert.AreEqual(s1, s2);

            s1.ClusterBaseEP = "logical://foo";
            Assert.AreNotEqual(s1, s2);

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.MasterBroadcastInterval = TimeSpan.FromMinutes(99);
            Assert.AreNotEqual(s1, s2);

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.SlaveUpdateInterval = TimeSpan.FromMinutes(99);
            Assert.AreNotEqual(s1, s2);

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.ElectionInterval = TimeSpan.FromMinutes(99);
            Assert.AreNotEqual(s1, s2);

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.MissingMasterCount = 99;
            Assert.AreNotEqual(s1, s2);

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.MissingSlaveCount = 99;
            Assert.AreNotEqual(s1, s2);

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.MasterBkInterval = TimeSpan.FromMinutes(99);
            Assert.AreNotEqual(s1, s2);

            s1 = new ClusterMemberSettings((MsgEP)"logical://test");
            s2 = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.SlaveBkInterval = TimeSpan.FromMinutes(99);
            Assert.AreNotEqual(s1, s2);

            s1            = new ClusterMemberSettings((MsgEP)"logical://test");
            s2            = new ClusterMemberSettings((MsgEP)"logical://test");
            s1.BkInterval = TimeSpan.FromMinutes(99);
            Assert.AreNotEqual(s1, s2);
        }
Exemple #3
0
        public void ClusterMemberSettings_Default()
        {
            ClusterMemberSettings settings = new ClusterMemberSettings((MsgEP)"logical://test");

            Assert.AreEqual((MsgEP)"logical://test", settings.ClusterBaseEP);
            Assert.AreEqual(ClusterMemberMode.Normal, settings.Mode);
            Assert.AreEqual(TimeSpan.FromSeconds(30), settings.MasterBroadcastInterval);
            Assert.AreEqual(TimeSpan.FromSeconds(30), settings.SlaveUpdateInterval);
            Assert.AreEqual(TimeSpan.FromSeconds(10), settings.ElectionInterval);
            Assert.AreEqual(2, settings.MissingMasterCount);
            Assert.AreEqual(2, settings.MissingSlaveCount);
            Assert.AreEqual(TimeSpan.FromSeconds(1), settings.MasterBkInterval);
            Assert.AreEqual(TimeSpan.FromSeconds(1), settings.SlaveBkInterval);
            Assert.AreEqual(TimeSpan.FromSeconds(1), settings.BkInterval);
        }
Exemple #4
0
        public void ClusterStatus_Serialize_ClusterStatus()
        {
            Dictionary <string, string> clusterProps = new Dictionary <string, string>(StringComparer.OrdinalIgnoreCase);
            EnhancedMemoryStream        ms           = new EnhancedMemoryStream();
            ClusterMemberSettings       settings     = new ClusterMemberSettings((MsgEP)"logical://test");
            ClusterMemberStatus         memberStatus;
            ClusterStatus status;

            status = new ClusterStatus((MsgEP)"logical://test/master");
            status.LoadProperties(clusterProps);
            status.MasterProtocolCaps = unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF);

            memberStatus             = new ClusterMemberStatus("logical://test/master", ClusterMemberState.Master, settings);
            memberStatus["setting1"] = "value1";
            memberStatus["setting2"] = "value2";
            status.Members.Add(memberStatus);

            memberStatus             = new ClusterMemberStatus("logical://test/bar", ClusterMemberState.Slave, settings);
            memberStatus["setting3"] = "value3";
            memberStatus["setting4"] = "value4";
            status.Members.Add(memberStatus);

            status.Write(ms);
            ms.Position = 0;
            status      = new ClusterStatus(ms);

            Assert.AreEqual((MsgEP)"logical://test/master", status.MasterEP);
            Assert.AreEqual(Helper.MachineName, status.MasterMachine);
            Assert.AreEqual(unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF), status.MasterProtocolCaps);
            Assert.AreEqual(2, status.Members.Count);

            memberStatus = status.Members[0];
            Assert.AreEqual((MsgEP)"logical://test/master", memberStatus.InstanceEP);
            Assert.AreEqual(Helper.MachineName, memberStatus.MachineName);
            Assert.AreEqual(ClusterMemberState.Master, memberStatus.State);
            Assert.AreEqual(settings, memberStatus.Settings);
            Assert.AreEqual("value1", memberStatus["setting1"]);
            Assert.AreEqual("value2", memberStatus["setting2"]);

            memberStatus = status.Members[1];
            Assert.AreEqual((MsgEP)"logical://test/bar", memberStatus.InstanceEP);
            Assert.AreEqual(Helper.MachineName, memberStatus.MachineName);
            Assert.AreEqual(ClusterMemberState.Slave, memberStatus.State);
            Assert.AreEqual(settings, memberStatus.Settings);
            Assert.AreEqual("value3", memberStatus["setting3"]);
            Assert.AreEqual("value4", memberStatus["setting4"]);
        }
Exemple #5
0
        public void ClusterMemberSettings_LoadFromClusterMemberStatus()
        {
            ClusterMemberStatus   status;
            ClusterMemberSettings settings;
            string cfg;

            try
            {
                cfg = @"
&section Settings

    ClusterBaseEP           = logical://test
    Mode                    = PreferSlave
    MasterBroadcastInterval = 10m
    SlaveUpdateInterval     = 11m
    MissingMasterCount      = 13
    MissingSlaveCount       = 20
    MasterBkInterval        = 14m
    SlaveBkInterval         = 15m
    BkInterval              = 16m
    ElectionInterval        = 17m

&endsection
";
                Config.SetConfig(cfg.Replace('&', '#'));

                settings = ClusterMemberSettings.LoadConfig("Settings");
                status   = new ClusterMemberStatus("logical://test/foo", ClusterMemberState.Slave, settings);
                settings = new ClusterMemberSettings(status);

                Assert.AreEqual((MsgEP)"logical://test", settings.ClusterBaseEP);
                Assert.AreEqual(ClusterMemberMode.PreferSlave, settings.Mode);
                Assert.AreEqual(TimeSpan.FromMinutes(10), settings.MasterBroadcastInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(11), settings.SlaveUpdateInterval);
                Assert.AreEqual(13, settings.MissingMasterCount);
                Assert.AreEqual(20, settings.MissingSlaveCount);
                Assert.AreEqual(TimeSpan.FromMinutes(14), settings.MasterBkInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(15), settings.SlaveBkInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(16), settings.BkInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(17), settings.ElectionInterval);
            }
            finally
            {
                Config.SetConfig(null);
            }
        }
Exemple #6
0
        public void ClusterStatus_Clone_ClusterStatus()
        {
            ClusterMemberSettings settings = new ClusterMemberSettings((MsgEP)"logical://test");
            ClusterMemberStatus   memberStatus;
            ClusterStatus         input, output;

            input                    = new ClusterStatus((MsgEP)"logical://test/master");
            input.ClusterTime        = DateTime.UtcNow;
            input.MasterProtocolCaps = unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF);

            memberStatus             = new ClusterMemberStatus("logical://test/master", ClusterMemberState.Master, settings);
            memberStatus["setting1"] = "value1";
            memberStatus["setting2"] = "value2";
            input.Members.Add(memberStatus);

            memberStatus             = new ClusterMemberStatus("logical://test/bar", ClusterMemberState.Slave, settings);
            memberStatus["setting3"] = "value3";
            memberStatus["setting4"] = "value4";
            input.Members.Add(memberStatus);

            output = input.Clone();
            Assert.AreNotSame(input, output);

            Assert.AreEqual((MsgEP)"logical://test/master", output.MasterEP);
            Assert.AreEqual(Helper.MachineName, output.MasterMachine);
            Assert.AreEqual(input.ClusterTime, output.ClusterTime);
            Assert.AreEqual(input.ReceiveTime, output.ReceiveTime);
            Assert.AreEqual(unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF), output.MasterProtocolCaps);
            Assert.AreEqual(2, output.Members.Count);

            memberStatus = output.Members[0];
            Assert.AreEqual((MsgEP)"logical://test/master", memberStatus.InstanceEP);
            Assert.AreEqual(Helper.MachineName, memberStatus.MachineName);
            Assert.AreEqual(ClusterMemberState.Master, memberStatus.State);
            Assert.AreEqual(settings, memberStatus.Settings);
            Assert.AreEqual("value1", memberStatus["setting1"]);
            Assert.AreEqual("value2", memberStatus["setting2"]);

            memberStatus = output.Members[1];
            Assert.AreEqual((MsgEP)"logical://test/bar", memberStatus.InstanceEP);
            Assert.AreEqual(Helper.MachineName, memberStatus.MachineName);
            Assert.AreEqual(ClusterMemberState.Slave, memberStatus.State);
            Assert.AreEqual(settings, memberStatus.Settings);
            Assert.AreEqual("value3", memberStatus["setting3"]);
            Assert.AreEqual("value4", memberStatus["setting4"]);
        }
Exemple #7
0
        public void ClusterStatus_Clone_ClusterMemberStatus()
        {
            ClusterMemberSettings settings = new ClusterMemberSettings((MsgEP)"logical://test");
            ClusterMemberStatus   input, output;

            input              = new ClusterMemberStatus("logical://test/foo", ClusterMemberState.Master, settings);
            input["setting1"]  = "value1";
            input["setting2"]  = "value2";
            input.ProtocolCaps = unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF);

            output = input.Clone();
            Assert.AreNotSame(input, output);

            Assert.AreEqual((MsgEP)"logical://test/foo", output.InstanceEP);
            Assert.AreEqual(Helper.MachineName, output.MachineName);
            Assert.AreEqual(unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF), output.ProtocolCaps);
            Assert.AreEqual(ClusterMemberState.Master, output.State);
            Assert.AreEqual(settings, output.Settings);
            Assert.AreEqual("value1", output["setting1"]);
            Assert.AreEqual("value2", output["setting2"]);
        }
Exemple #8
0
        public void ClusterStatus_Serialize_ClusterMemberStatus()
        {
            EnhancedMemoryStream  ms       = new EnhancedMemoryStream();
            ClusterMemberSettings settings = new ClusterMemberSettings((MsgEP)"logical://test");
            ClusterMemberStatus   status;

            status              = new ClusterMemberStatus("logical://test/foo", ClusterMemberState.Master, settings);
            status["setting1"]  = "value1";
            status["setting2"]  = "value2";
            status.ProtocolCaps = unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF);

            status.Write(ms);
            ms.Position = 0;
            status      = new ClusterMemberStatus(ms);

            Assert.AreEqual((MsgEP)"logical://test/foo", status.InstanceEP);
            Assert.AreEqual(Helper.MachineName, status.MachineName);
            Assert.AreEqual(unchecked ((ClusterMemberProtocolCaps)0xFFFFFFFF), status.ProtocolCaps);
            Assert.AreEqual(ClusterMemberState.Master, status.State);
            Assert.AreEqual(settings, status.Settings);
            Assert.AreEqual("value1", status["setting1"]);
            Assert.AreEqual("value2", status["setting2"]);
        }
Exemple #9
0
        public void ClusterMemberSettings_LoadConfig()
        {
            ClusterMemberSettings settings;
            string cfg;

            try
            {
                // Verify that we actually load existing settings

                cfg = @"
&section Settings

    ClusterBaseEP           = logical://test
    Mode                    = PreferMaster
    MasterBroadcastInterval = 10m
    SlaveUpdateInterval     = 11m
    MissingMasterCount      = 13
    MissingSlaveCount       = 20
    MasterBkInterval        = 14m
    SlaveBkInterval         = 15m
    BkInterval              = 16m
    ElectionInterval        = 17m

&endsection
";
                Config.SetConfig(cfg.Replace('&', '#'));
                settings = ClusterMemberSettings.LoadConfig("Settings");

                Assert.AreEqual((MsgEP)"logical://test", settings.ClusterBaseEP);
                Assert.AreEqual(ClusterMemberMode.PreferMaster, settings.Mode);
                Assert.AreEqual(TimeSpan.FromMinutes(10), settings.MasterBroadcastInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(11), settings.SlaveUpdateInterval);
                Assert.AreEqual(13, settings.MissingMasterCount);
                Assert.AreEqual(20, settings.MissingSlaveCount);
                Assert.AreEqual(TimeSpan.FromMinutes(14), settings.MasterBkInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(15), settings.SlaveBkInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(16), settings.BkInterval);
                Assert.AreEqual(TimeSpan.FromMinutes(17), settings.ElectionInterval);

                // Verify that settings not in the config are initialized
                // with the proper defaults

                cfg = @"
&section Settings

    ClusterBaseEP = logical://test

&endsection
";
                Config.SetConfig(cfg.Replace('&', '#'));
                settings = ClusterMemberSettings.LoadConfig("Settings");

                Assert.AreEqual((MsgEP)"logical://test", settings.ClusterBaseEP);
                Assert.AreEqual(ClusterMemberMode.Normal, settings.Mode);
                Assert.AreEqual(TimeSpan.FromSeconds(30), settings.MasterBroadcastInterval);
                Assert.AreEqual(TimeSpan.FromSeconds(30), settings.SlaveUpdateInterval);
                Assert.AreEqual(2, settings.MissingMasterCount);
                Assert.AreEqual(2, settings.MissingSlaveCount);
                Assert.AreEqual(TimeSpan.FromSeconds(1), settings.MasterBkInterval);
                Assert.AreEqual(TimeSpan.FromSeconds(1), settings.SlaveBkInterval);
                Assert.AreEqual(TimeSpan.FromSeconds(1), settings.BkInterval);
                Assert.AreEqual(TimeSpan.FromSeconds(10), settings.ElectionInterval);

                // Make sure we see an exception if the ClusterBaseEP setting is
                // not present.

                cfg = @"
&section Settings

&endsection
";
                Config.SetConfig(cfg.Replace('&', '#'));

                try
                {
                    settings = ClusterMemberSettings.LoadConfig("Settings");
                    Assert.Fail("Expected an ArgumentException");
                }
                catch (Exception e)
                {
                    Assert.IsInstanceOfType(e, typeof(ArgumentException));
                }
            }
            finally
            {
                Config.SetConfig(null);
            }
        }
Exemple #10
0
        /// <summary>
        /// Associates the service handler with a message router by registering
        /// the necessary application message handlers.
        /// </summary>
        /// <param name="router">The message router.</param>
        /// <param name="keyPrefix">The configuration key prefix or (null to use <b>LillTek.Datacenter.AppStore</b>).</param>
        /// <param name="perfCounters">The application's performance counter set (or <c>null</c>).</param>
        /// <param name="perfPrefix">The string to prefix any performance counter names (or <c>null</c>).</param>
        /// <remarks>
        /// <para>
        /// Applications that expose performance counters will pass a non-<c>null</c> <b>perfCounters</b>
        /// instance.  The service handler should add any counters it implements to this set.
        /// If <paramref name="perfPrefix" /> is not <c>null</c> then any counters added should prefix their
        /// names with this parameter.
        /// </para>
        /// </remarks>
        public void Start(MsgRouter router, string keyPrefix, PerfCounterSet perfCounters, string perfPrefix)
        {
            var config = new Config(keyPrefix != null ? keyPrefix : ConfigPrefix);

            // Make sure the syncLock is set early.

            this.syncLock = router.SyncRoot;

            // Make sure that the LillTek.Datacenter message types have been
            // registered with the LillTek.Messaging subsystem.

            LillTek.Datacenter.Global.RegisterMsgTypes();

            // Verify the router parameter

            if (router == null)
            {
                throw new ArgumentNullException("router", "Router cannot be null.");
            }

            if (this.router != null)
            {
                throw new InvalidOperationException("This handler has already been started.");
            }

            // General initialization

            mode                = config.Get <AppStoreMode>("Mode", AppStoreMode.Primary);
            primaryBroadcast    = config.Get("PrimaryBroadcast", true);
            packageScanInterval = config.Get("PackageScanInterval", TimeSpan.FromMinutes(5));
            primaryPollInterval = config.Get("PrimaryPollInterval", TimeSpan.FromMinutes(15));
            primaryPollTime     = SysTime.Now;
            onTransfer          = new AsyncCallback(OnTransfer);
            downloads           = new Dictionary <AppRef, PendingDownload>();
            forceSync           = false;
            cDownloads          = 0;
            netFail             = false;

            // Initialize the package folder

            packageFolder              = new AppPackageFolder(syncLock, config.Get("PackageFolder", "Packages"));
            packageFolder.ChangeEvent += new MethodArg1Invoker(OnPackageFolderChange);
            packageScanTime            = SysTime.Now;

            // Initialize the performance counters

            startTime = DateTime.UtcNow;
            perf      = new Perf(perfCounters, perfPrefix);

            // Crank up the background task timer.

            bkTimer = new GatedTimer(new TimerCallback(OnBkTimer), null, config.Get("BkTaskInterval", TimeSpan.FromSeconds(1)));

            try
            {
                // Initialize the router

                this.router = router;

                // Join the cluster, initializing this instance's state.

                cluster         = new ClusterMember(router, ClusterMemberSettings.LoadConfig(config.KeyPrefix + "Cluster"));
                cluster["Mode"] = this.mode.ToString();

                cluster.ClusterStatusUpdate += new ClusterMemberEventHandler(OnClusterStatusUpdate);
                cluster.Start();

                // Rather than calling cluster.JoinWait() which could take a really long
                // time, I'm going to sleep for two seconds.  There are three scenarios:
                //
                //      1. This is the first Application Store instance.
                //
                //      2. Other instances are running but they haven't
                //         organized into a cluster.
                //
                //      3. A cluster is already running.
                //
                // If #1 is the current situation, then it will take a very long time
                // for JoinWait() to return because we have to go through the entire
                // missed master broadcast and election periods.  Since we're the only
                // instance, we could have started serving content well before this.
                //
                // #2 won't be very common but if it is the case, the worst thing
                // that will happen is that it will take a while to discover the
                // primary store.
                //
                // If #3 is the case, then two seconds should be long enough for the
                // master to send the instance a cluster update.

                Thread.Sleep(2000);

                // Register the message handlers via the cluster member
                // so that the endpoint used will be the member's instanceEP.

                cluster.AddTarget(this, AppStoreHandler.DynamicScope);
            }
            catch
            {
                if (packageFolder != null)
                {
                    packageFolder.Dispose();
                    packageFolder = null;
                }

                if (bkTimer != null)
                {
                    bkTimer.Dispose();
                    bkTimer = null;
                }

                router.Dispatcher.RemoveTarget(this);

                if (cluster != null)
                {
                    cluster.Stop();
                    cluster = null;
                }

                throw;
            }
        }
Exemple #11
0
        /// <summary>
        /// Loads DNS client settings from a configuration section.
        /// </summary>
        /// <param name="keyPrefix">The configuration section key prefix (or <c>null</c>).</param>
        /// <remarks>
        /// <para>
        /// The settings will loaded are:
        /// </para>
        /// <div class="tablediv">
        /// <table class="dtTABLE" cellspacing="0" ID="Table1">
        /// <tr valign="top">
        /// <th width="1">Setting</th>
        /// <th width="1">Default</th>
        /// <th width="90%">Description</th>
        /// </tr>
        /// <tr valign="top">
        ///     <td>Enabled</td>
        ///     <td>true</td>
        ///     <td>
        ///     Indicates whether the dynamic DNS client should be enabled.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>NetworkBinding</td>
        ///     <td>ANY</td>
        ///     <td>
        ///     Specifies the <see cref="NetworkBinding" /> the DNS client
        ///     should use for sending UDP host registration messages to the
        ///     DNS servers.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>Mode</td>
        ///     <td>Cluster</td>
        ///     <td>
        ///     Controls how the client registers hosts with the DNS server.  The possible values
        ///     are <see cref="DynDnsMode.Udp" /> or <see cref="DynDnsMode.Cluster"/>.
        ///     <see cref="DynDnsMode.Both" /> is not allowed for DNS clients.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>SharedKey</td>
        ///     <td>(see note)</td>
        ///     <td>
        ///     Shared symmetric encryption key used to decrypt UDP registration messages
        ///     sent by DNS clients while in <see cref="DynDnsMode.Udp" /> mode.  This
        ///     key must match the shared key configured for the client.  This defaults
        ///     to a reasonable value.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>Domain</td>
        ///     <td>(see note)</td>
        ///     <td>
        ///     <para>
        ///     Specifies the name server domain for the name server.  If this is
        ///     specified, the DNS client will periodically query DNS for the NS
        ///     records for the domain and then use the IP addresses to send UDP
        ///     host registration messages to the servers.
        ///     </para>
        ///     <para>
        ///     This setting must be formatted as a network binding with a host name
        ///     and port, such as <b>LILLTEK.NET:DYNAMIC-DNS</b>.
        ///     </para>
        ///     <note>
        ///     One of <b>Domain</b> or <b>NameServer</b> must be specified when when <b>Mode=UDP</b>.
        ///     If both settings are present, then <b>Domain</b> will be used.
        ///     </note>
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>NameServer[#]</td>
        ///     <td>(see note)</td>
        ///     <td>
        ///     <para>
        ///     Specifies the network bindings for the DNS servers for the delivery
        ///     of UDP host registration messages.  These entries may include
        ///     IP addresses or host names, but note that host name lookups are
        ///     performed only once by the server, when it starts.
        ///     </para>
        ///     <note>
        ///     One of <b>Domain</b> or <b>NameServer</b> must be specified when when <b>Mode=UDP</b>.
        ///     If both settings are present, then <b>Domain</b> will be used.
        ///     </note>
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>BkInterval</td>
        ///     <td>1s</td>
        ///     <td>
        ///     Minimum interval for which background activities will be scheduled.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>DomainRefreshInterval</td>
        ///     <td>15m</td>
        ///     <td>
        ///     Interval at which DNS NS queries will be performed to refresh the list of
        ///     name servers for the specified <b>Domain</b>.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>UdpRegisterInterval</td>
        ///     <td>60s</td>
        ///     <td>
        ///     Interval at which UDP host registration messages will be sent to the
        ///     DNS servers when operating in <b>Mode=UDP</b>.
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>Host[#]</td>
        ///     <td>(optional)</td>
        ///     <td>
        ///     <para>
        ///     The set of static host entries to be returned by the DNS server.  These
        ///     entries are formatted as:
        ///     </para>
        ///     <code lang="none">
        ///     &lt;host name&gt; "," &lt;ip or cname&gt; [ "," &lt;TTL&gt; [ "," &lt;host-mode&gt; [ ";" "NAT" ] ] ]
        ///     </code>
        ///     <para>
        ///     where <b>host name</b> is the DNS name being registered, <b>ip</b> or <b>cname</b>
        ///     specifies the IP address or CNAME reference to the host, <b>TTL</b> is the optional
        ///     time-to-live (TTL) to use for the entry in seconds, and <b>host-mode</b> is the optional
        ///     host entry mode, one of <b>ADDRESS</b>, <b>ADDRESSLIST</b>, or <b>CNAME</b>.
        ///     </para>
        ///     <para>
        ///     The <b>TTL</b> value defaults to -1 seconds (indicating that the server's default TTL
        ///     will be used) and the <b>host-mode</b> defaults to <b>ADDRESS</b> for IP addresses or
        ///     <b>CNAME</b> for CNAME references. You can also set <b>TTL=-1</b> to use the DNS server
        ///     default for this.
        ///     </para>
        ///     <note>
        ///     A host mode of <b>ADDRESS</b> or <b>ADDRESSLIST</b> can only be specified for IP
        ///     addresses and <b>CNAME</b> can only be specified for CNAME entries.
        ///     </note>
        ///     </td>
        /// </tr>
        /// <tr valign="top">
        ///     <td>Cluster</td>
        ///     <td>(see note)</td>
        ///     <td>
        ///     <b>Cluster</b> is a subsection in the configuration that
        ///     that specifies the settings required to establish a cooperative
        ///     cluster with the Dynamic DNS instances on the network while operating
        ///     in CLUSTER mode.  The client uses the <see cref="ClusterMember" /> class
        ///     to perform the work necessary to join the cluster.  The <b>ClusterBaseEP</b>
        ///     setting is required.
        ///     </td>
        /// </tr>
        /// </table>
        /// </div>
        /// </remarks>
        public DynDnsClientSettings(string keyPrefix)
        {
            Config config = new Config(keyPrefix);

            string[] list;
            List <DynDnsHostEntry> registrations;
            List <NetworkBinding>  bindings;

            list          = config.GetArray("Host");
            registrations = new List <DynDnsHostEntry>(list.Length);

            for (int i = 0; i < list.Length; i++)
            {
                try
                {
                    registrations.Add(DynDnsHostEntry.Parse(list[i]));
                }
                catch
                {
                    SysLog.LogWarning("DynamicDnsClient: Error parsing host registration [{0}Host[{1}]={2}].", config.KeyPrefix, i, list[i]);
                }
            }

            this.Hosts = registrations.ToArray();

            list     = config.GetArray("NameServer");
            bindings = new List <NetworkBinding>(list.Length);

            for (int i = 0; i < list.Length; i++)
            {
                try
                {
                    bindings.Add(new NetworkBinding((list[i])));
                }
                catch
                {
                    SysLog.LogWarning("DynamicDnsClient: Error parsing name server binding [{0}NameServer[{1}]={2}].", config.KeyPrefix, i, list[i]);
                }
            }

            this.NameServers = bindings.ToArray();

            this.Enabled = config.Get("Enabled", this.Enabled);

            if (this.Enabled)
            {
                this.NetworkBinding        = config.Get("NetworkBinding", this.NetworkBinding);
                this.Mode                  = config.Get <DynDnsMode>("Mode", this.Mode);
                this.SharedKey             = new SymmetricKey(config.Get("SharedKey", "aes:BcskocQ2W4aIGEemkPsy5dhAxuWllweKLVToK1NoYzg=:5UUVxRPml8L4WH82unR74A=="));
                this.Domain                = config.Get("Domain", this.Domain);
                this.BkInterval            = config.Get("BkInterval", this.BkInterval);
                this.DomainRefreshInterval = config.Get("DomainRefreshInterval", this.DomainRefreshInterval);
                this.UdpRegisterInterval   = config.Get("UdpRegisterInterval", this.UdpRegisterInterval);

                if (this.Mode == DynDnsMode.Cluster)
                {
                    this.Cluster = ClusterMemberSettings.LoadConfig(config.KeyPrefix + "Cluster");
                }

                if (this.Mode == DynDnsMode.Both)
                {
                    throw new FormatException("DynamicDnsClient: [Mode=BOTH] is not supported.");
                }

                if (this.Mode == DynDnsMode.Udp && this.Domain.IsAny && this.NameServers.Length == 0)
                {
                    throw new FormatException("DynDnsClient: One of DOMAIN or NAMESERVER[#] must be specified when [Mode=UDP].");
                }
            }
        }