Example #1
0
        public void Base36Uuid_Crypto()
        {
            // Generate 20K base-36 cyoptographically secure random UUIDs and verify that
            // the're all 13-characters, consist of lowercase letters and digits and also
            // that we never generate duplicates.

            var existing = new HashSet <string>();

            for (int i = 0; i < 20000; i++)
            {
                var v = NeonHelper.CreateBase36Uuid(secure: true);

                Assert.NotNull(v);
                Assert.Equal(13, v.Length);

                foreach (var ch in v)
                {
                    if (!char.IsLetterOrDigit(ch))
                    {
                        Assert.False(false, $"Invalid character [{ch}] found in [{v}].");
                    }
                    else if (char.IsUpper(ch))
                    {
                        Assert.False(false, $"Uppercase character [{ch}] found in [{v}].");
                    }
                    else if (ch > (char)127)
                    {
                        Assert.False(false, $"Non-ASCII character [{ch}] found in [{v}].");
                    }
                }

                Assert.DoesNotContain(v, existing);
                existing.Add(v);
            }
        }
Example #2
0
        public async Task Single_WithoutActionsOrCounters()
        {
            // Verify that the elector works without callback actions.

            var  leaseName = $"test-{NeonHelper.CreateBase36Uuid()}";
            var  config    = new LeaderElectionConfig(fixture.K8s, @namespace: KubeNamespace.Default, leaseName: leaseName, identity: "instance-0");
            var  elector   = new LeaderElector(config);
            Task electorTask;

            try
            {
                using (elector)
                {
                    electorTask = elector.RunAsync();

                    NeonHelper.WaitFor(() => elector.IsLeader, timeout: MaxWaitTime);

                    Assert.True(elector.IsLeader);
                    Assert.Equal("instance-0", elector.Leader);
                }

                // Ensure that the elector task completes.

                await electorTask.WaitAsync(timeout : MaxWaitTime);
            }
            finally
            {
                await config.K8s.DeleteNamespacedLeaseWithHttpMessagesAsync(leaseName, config.Namespace);
            }
        }
Example #3
0
        /// <summary>
        /// <para>
        /// Transforms responses before sending them back to the client. In this case it intercepts the initial Bla
        /// </para>
        /// </summary>
        /// <param name="httpContext">The HTTP Context.</param>
        /// <param name="proxyResponse">The Proxied Response.</param>
        /// <returns></returns>
        public override async ValueTask <bool> TransformResponseAsync(HttpContext httpContext,
                                                                      HttpResponseMessage proxyResponse)
        {
            await SyncContext.Clear;

            await base.TransformResponseAsync(httpContext, proxyResponse);

            var session = new Session()
            {
                Id           = NeonHelper.CreateBase36Uuid(),
                UpstreamHost = proxyResponse.RequestMessage.RequestUri.Authority
            };

            var headers   = proxyResponse.Content.Headers;
            var mediaType = headers.ContentType?.MediaType ?? "";

            if (!httpContext.Request.Cookies.ContainsKey(Service.SessionCookieName) ||
                (mediaType == "text/html" && httpContext.Response.StatusCode == 200))
            {
                httpContext.Response.Cookies.Append(Service.SessionCookieName, cipher.EncryptToBase64($"{session.Id}"));
            }

            await cache.SetAsync(session.Id, NeonHelper.JsonSerializeToBytes(session));

            return(true);
        }
Example #4
0
        public async Task Single_WithCounters()
        {
            // Verify that the elector can increment performance counters.

            var leaseName        = $"test-{NeonHelper.CreateBase36Uuid()}";
            var promotionCounter = Metrics.CreateCounter("test_promotions", string.Empty);
            var demotionCounter  = Metrics.CreateCounter("test_demotions", string.Empty);
            var newLeaderCounter = Metrics.CreateCounter("test_newleaders", string.Empty);

            var config = new LeaderElectionConfig(
                k8s:              fixture.K8s,
                @namespace:       KubeNamespace.Default,
                leaseName:        leaseName,
                identity:         "instance-0",
                promotionCounter: promotionCounter,
                demotionCounter:  demotionCounter,
                newLeaderCounter: newLeaderCounter);

            var  elector = new LeaderElector(config);
            Task electorTask;

            try
            {
                using (elector)
                {
                    electorTask = elector.RunAsync();

                    NeonHelper.WaitFor(() => elector.IsLeader, timeout: MaxWaitTime);

                    Assert.True(elector.IsLeader);
                    Assert.Equal("instance-0", elector.Leader);
                }

                // Ensure that the elector task completes.

                await electorTask.WaitAsync(timeout : MaxWaitTime);

                // Ensure that the counters are correct.

                Assert.Equal(1, promotionCounter.Value);
                Assert.Equal(0, demotionCounter.Value);     // We don't see demotions when the elector is disposed
                Assert.Equal(2, newLeaderCounter.Value);    // We do see a leadership change when the elector is disposed
            }
            finally
            {
                await config.K8s.DeleteNamespacedLeaseWithHttpMessagesAsync(leaseName, config.Namespace);
            }
        }
Example #5
0
        public async Task Single_WithoutCounters()
        {
            // Verify that we can create a single [LeaderElector] instance and that:
            //
            //      1. The [OnNewLeader] action is called
            //      2. The [OnStartedLeader] action is called
            //      3. The new leader matches the current instance
            //      4. IsLeader and GetLeader() work

            var    leaseName = $"test-{NeonHelper.CreateBase36Uuid()}";
            bool   isLeading = false;
            string leader    = null;
            var    config    = new LeaderElectionConfig(fixture.K8s, @namespace: KubeNamespace.Default, leaseName: leaseName, identity: "instance-0");
            Task   electorTask;

            try
            {
                var elector = new LeaderElector(
                    config:           config,
                    onStartedLeading: () => isLeading    = true,
                    onStoppedLeading: () => isLeading    = false,
                    onNewLeader:      identity => leader = identity);

                using (elector)
                {
                    electorTask = elector.RunAsync();

                    NeonHelper.WaitFor(() => isLeading, timeout: MaxWaitTime);

                    Assert.True(isLeading);
                    Assert.True(elector.IsLeader);
                    Assert.Equal("instance-0", leader);
                    Assert.Equal("instance-0", elector.Leader);
                }

                // Ensure that the elector task completes.

                await electorTask.WaitAsync(timeout : MaxWaitTime);
            }
            finally
            {
                await config.K8s.DeleteNamespacedLeaseWithHttpMessagesAsync(leaseName, config.Namespace);
            }
        }
Example #6
0
 /// <summary>
 /// Returns a 5-digit base-36 UUID string.
 /// </summary>
 /// <returns>The UUID.</returns>
 private string CreateUuidString()
 {
     return(NeonHelper.CreateBase36Uuid().Substring(0, 5));
 }
Example #7
0
        /// <summary>
        /// Starts the controller.
        /// </summary>
        /// <param name="k8s">The <see cref="IKubernetes"/> client to use.</param>
        /// <returns>The tracking <see cref="Task"/>.</returns>
        public static async Task StartAsync(IKubernetes k8s)
        {
            Covenant.Requires <ArgumentNullException>(k8s != null, nameof(k8s));

            if (NeonHelper.IsLinux)
            {
                // Ensure that the [/var/run/neonkube/node-tasks] folder exists on the node.

                var scriptPath = Path.Combine(Node.HostMount, $"tmp/node-agent-folder-{NeonHelper.CreateBase36Uuid()}.sh");
                var script     =
                    $@"#!/bin/bash

set -euo pipefail

# Ensure that the nodetask runtime folders exist and have the correct permissions.

if [ ! -d {hostNeonRunFolder} ]; then

mkdir -p {hostNeonRunFolder}
chmod 700 {hostNeonRunFolder}
fi

if [ ! -d {hostNeonTasksFolder} ]; then

mkdir -p {hostNeonTasksFolder}
chmod 700 {hostNeonTasksFolder}
fi

# Remove this script.

rm $0
";
                File.WriteAllText(scriptPath, NeonHelper.ToLinuxLineEndings(script));
                try
                {
                    (await Node.BashExecuteCaptureAsync(scriptPath)).EnsureSuccess();
                }
                finally
                {
                    NeonHelper.DeleteFile(scriptPath);
                }
            }

            // Load the configuration settings.

            var leaderConfig =
                new LeaderElectionConfig(
                    k8s,
                    @namespace: KubeNamespace.NeonSystem,
                    leaseName:        $"{Program.Service.Name}.nodetask-{Node.Name}",
                    identity:         Pod.Name,
                    promotionCounter: Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_promoted", "Leader promotions"),
                    demotionCounter:  Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_demoted", "Leader demotions"),
                    newLeaderCounter: Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_newLeader", "Leadership changes"));

            var options = new ResourceManagerOptions()
            {
                IdleInterval             = Program.Service.Environment.Get("NODETASK_IDLE_INTERVAL", TimeSpan.FromSeconds(60)),
                ErrorMinRequeueInterval  = Program.Service.Environment.Get("NODETASK_ERROR_MIN_REQUEUE_INTERVAL", TimeSpan.FromSeconds(15)),
                ErrorMaxRetryInterval    = Program.Service.Environment.Get("NODETASK_ERROR_MAX_REQUEUE_INTERVAL", TimeSpan.FromSeconds(60)),
                IdleCounter              = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_idle", "IDLE events processed."),
                ReconcileCounter         = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_reconcile", "RECONCILE events processed."),
                DeleteCounter            = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_delete", "DELETED events processed."),
                IdleErrorCounter         = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_idle_error", "Failed NodeTask IDLE event processing."),
                ReconcileErrorCounter    = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_reconcile_error", "Failed NodeTask RECONCILE event processing."),
                DeleteErrorCounter       = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_delete_error", "Failed NodeTask DELETE event processing."),
                StatusModifyErrorCounter = Metrics.CreateCounter($"{Program.Service.MetricsPrefix}nodetask_statusmodify_error", "Failed NodeTask STATUS-MODIFY events processing.")
            };

            resourceManager = new ResourceManager <V1NeonNodeTask, NodeTaskController>(
                k8s,
                options:      options,
                filter:       NodeTaskFilter,
                leaderConfig: leaderConfig);

            await resourceManager.StartAsync();
        }