public static Step AddCommand(this Step step, string command, bool acceptParameter = false)
        {
            var commandStep = new CommandStep(command, acceptParameter);

            step.AddChildStep(commandStep);
            return(commandStep);
        }
        public static StepCollection AddCommandWithParameter(this Step step, string command, ILocalizedString suggestParameterText)
        {
            var commandWithParameter      = new CommandStep(command, true);
            var commandWithoutOfParameter = new CommandStep(command, false);

            commandWithoutOfParameter.EchoReply(suggestParameterText, EchoOptions.EchoForceReply());
            var commandAwaitParameter = commandWithoutOfParameter.AddAnyInput();

            step.AddChildStep(commandWithParameter);
            step.AddChildStep(commandWithoutOfParameter);
            return(new StepCollection(new[] { commandWithParameter, commandAwaitParameter }));
        }
    public CommandStep AddCommandStep()
    {
        foreach (CommandStep s in commandSteps)
        {
            if (!s.activated)
            {
                s.activated = true; return(s);
            }
        }
        CommandStep nextStep = new CommandStep(commandSteps.Count);

        nextStep.activated = true;
        commandSteps.Add(nextStep);
        return(nextStep);
    }
Exemple #4
0
        /// <summary>
        /// Appends the steps required to start a neonHIVE related Docker container and upload
        /// a script to the hive managers to make it easy to restart the service manually or
        /// for hive updates.
        /// </summary>
        /// <param name="hive">The target hive.</param>
        /// <param name="steps">The target step list.</param>
        /// <param name="node">The target hive node.</param>
        /// <param name="containerName">Identifies the service.</param>
        /// <param name="image">The Docker image to be used by the container.</param>
        /// <param name="command">The <c>docker service create ...</c> command.</param>
        /// <param name="runOptions">Optional run options (defaults to <see cref="RunOptions.FaultOnError"/>).</param>
        /// <remarks>
        /// <para>
        /// This method performs the following steps:
        /// </para>
        /// <list type="number">
        ///     <item>
        ///     Passes <paramref name="image"/> to <see cref="Program.ResolveDockerImage(string)"/> to
        ///     obtain the actual image to be started.
        ///     </item>
        ///     <item>
        ///     Generates the first few lines of the script file that sets the
        ///     default image as the <c>TARGET_IMAGE</c> macro and then overrides
        ///     this with the script parameter (if there is one).  We also add
        ///     a Docker command that pulls the image.
        ///     </item>
        ///     <item>
        ///     Appends the commands to the script, replacing any text that matches
        ///     <see cref="ImagePlaceholderArg"/> with <c>${TARGET_IMAGE}</c> to make it easy
        ///     for services to be upgraded later.
        ///     </item>
        ///     <item>
        ///     Starts the service.
        ///     </item>
        ///     <item>
        ///     Uploads the generated script to each hive manager to [<see cref="HiveHostFolders.Scripts"/>/<paramref name="containerName"/>.sh].
        ///     </item>
        /// </list>
        /// </remarks>
        public static void AddContainerStartSteps(HiveProxy hive, ConfigStepList steps, SshProxy <NodeDefinition> node, string containerName, string image, IBashCommandFormatter command, RunOptions runOptions = RunOptions.FaultOnError)
        {
            Covenant.Requires <ArgumentNullException>(hive != null);
            Covenant.Requires <ArgumentNullException>(steps != null);
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(containerName));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(image));
            Covenant.Requires <ArgumentNullException>(command != null);

            // Generate the container start script.

            var script = CreateStartScript(containerName, image, true, command);

            // Add steps to upload the script to the managers and then call the script
            // to create the container on the target node.

            var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{containerName}.sh");

            steps.Add(hive.GetFileUploadSteps(node, scriptPath, script, permissions: "740"));
            steps.Add(CommandStep.CreateIdempotentDocker(node.Name, $"setup/{containerName}", scriptPath));
        }
Exemple #5
0
        /// <summary>
        /// Appends the steps required to start a neonHIVE related Docker service and upload
        /// a script to the hive managers to make it easy to restart the service manually or
        /// for hive updates.
        /// </summary>
        /// <param name="hive">The target hive.</param>
        /// <param name="steps">The target step list.</param>
        /// <param name="serviceName">Identifies the service.</param>
        /// <param name="image">The Docker image to be used by the service.</param>
        /// <param name="command">The <c>docker service create ...</c> command.</param>
        /// <param name="runOptions">Optional run options (defaults to <see cref="RunOptions.FaultOnError"/>).</param>
        /// <remarks>
        /// <para>
        /// This method performs the following steps:
        /// </para>
        /// <list type="number">
        ///     <item>
        ///     Passes <paramref name="image"/> to <see cref="Program.ResolveDockerImage(string)"/> to
        ///     obtain the actual image to be started.
        ///     </item>
        ///     <item>
        ///     Generates the first few lines of the script file that sets the
        ///     default image as the <c>TARGET_IMAGE</c> macro and then overrides
        ///     this with the script parameter (if there is one).
        ///     </item>
        ///     <item>
        ///     Appends the commands to the script, replacing any text that matches
        ///     <see cref="ImagePlaceholderArg"/> with <c>${TARGET_IMAGE}</c> to make it easy
        ///     for services to be upgraded later.
        ///     </item>
        ///     <item>
        ///     Starts the service.
        ///     </item>
        ///     <item>
        ///     Uploads the generated script to each hive manager to [<see cref="HiveHostFolders.Scripts"/>/<paramref name="serviceName"/>.sh].
        ///     </item>
        /// </list>
        /// </remarks>
        public static void AddServiceStartSteps(HiveProxy hive, ConfigStepList steps, string serviceName, string image, IBashCommandFormatter command, RunOptions runOptions = RunOptions.FaultOnError)
        {
            Covenant.Requires <ArgumentNullException>(hive != null);
            Covenant.Requires <ArgumentNullException>(steps != null);
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(serviceName));
            Covenant.Requires <ArgumentNullException>(!string.IsNullOrWhiteSpace(image));
            Covenant.Requires <ArgumentNullException>(command != null);

            // Generate the service start script.

            var script = CreateStartScript(serviceName, image, false, command);

            // Add steps to upload the script to the managers and then call the script
            // to create the service on the first manager.

            var scriptPath = LinuxPath.Combine(HiveHostFolders.Scripts, $"{serviceName}.sh");

            steps.Add(hive.GetFileUploadSteps(hive.Managers, scriptPath, script, permissions: "740"));
            steps.Add(CommandStep.CreateIdempotentDocker(hive.FirstManager.Name, $"setup/{serviceName}", scriptPath));
        }
        void CheckProcessorsFor(CommandAction action)
        {
            if (action == null)
            {
                return;
            }

            List <CommandStep> steps = action.steps;

            for (int s = 0; s < steps.Count; s++)
            {
                CommandStep step = steps[s];
                for (int i = 0; i < processors.Count; i++)
                {
                    Processor currentProc = processors[i];
                    if (currentProc.IsSupported(step.Key))
                    {
                        currentProc.Process(step);
                    }
                }
            }
        }
    private void OnGUI()
    {
        //GUI.DrawTexture(new Rect(0,0,maxSize.x,maxSize.y), Texture2D.blackTexture,ScaleMode.StretchToFill);
        if (coreData == null)
        {
            foreach (string guid in AssetDatabase.FindAssets("t: CoreData"))
            {
                coreData = AssetDatabase.LoadAssetAtPath <CoreData>(AssetDatabase.GUIDToAssetPath(guid));
            }
        }
        MoveList currentMovelist = coreData.moveLists[coreData.currentMovelistIndex];

        GUILayout.Label("");
        currentMovelist.name = GUILayout.TextField(currentMovelist.name);


        coreData.currentMovelistIndex = Mathf.Clamp(coreData.currentMovelistIndex, 0, coreData.moveLists.Count - 1);
        GUILayout.BeginHorizontal();
        coreData.currentMovelistIndex = GUILayout.Toolbar(coreData.currentMovelistIndex, coreData.GetMovelistNames());

        if (GUILayout.Button("New Move List", GUILayout.Width(175)))
        {
            coreData.moveLists.Add(new MoveList());
        }
        GUILayout.EndHorizontal();



        CommandState currentCommandStateObject = currentMovelist.commandStates[currentCommandStateIndex];

        currentCommandStateIndex = Mathf.Clamp(currentCommandStateIndex, 0, currentMovelist.commandStates.Count - 1);
        GUILayout.BeginHorizontal();
        currentCommandStateIndex = GUILayout.Toolbar(currentCommandStateIndex, coreData.GetCommandStateNames());

        if (GUILayout.Button("New Command State", GUILayout.Width(175)))
        {
            currentMovelist.commandStates.Add(new CommandState());
        }
        GUILayout.EndHorizontal();
        currentCommandStateObject.stateName = GUILayout.TextField(currentCommandStateObject.stateName, GUILayout.Width(200));
        //coreData.commandStates[currentCommandState].stateName = GUI.TextField(new Rect(0, 0, 300, 50), coreData.commandStates[currentCommandState].stateName);

        GUILayout.BeginHorizontal();
        if (GUILayout.Button("Add Command", GUILayout.Width(120)))
        {
            if (currentCommandStateObject.commandSteps == null)
            {
                currentCommandStateObject.commandSteps = new List <CommandStep>();
            }
            currentCommandStateObject.AddCommandStep();
            currentCommandStateObject.CleanUpBaseState();

            //coreData.commandStates[currentCommandState].chainSteps.Add(new ChainStep(coreData.commandStates[currentCommandState].chainSteps.Count));
        }
        GUILayout.Label("Draw Style:", GUILayout.Width(75));
        currentLineType = (LineDrawType)EditorGUILayout.EnumPopup(currentLineType, GUILayout.Width(150));
        drawBaseToggle  = GUILayout.Toggle(drawBaseToggle, "Draw Base Node", EditorStyles.miniButton, GUILayout.Width(150));
        if (drawBaseToggle)
        {
            drawBase = -1;
        }
        else
        {
            drawBase = 0;
        }
        GUILayout.EndHorizontal();
        scrollView = EditorGUILayout.BeginScrollView(scrollView);

        GUILayout.Label("", GUILayout.Height(2000), GUILayout.Width(2000));

        //draw your nodes here


        Handles.BeginGUI();

        int sCounter = 0;

        //foreach (CommandStep s in coreData.commandStates[currentCommandState].commandSteps)
        foreach (CommandStep s in currentCommandStateObject.commandSteps)
        {
            if (sCounter > drawBase)
            {
                int deleteMe = -1;
                int fCounter = 0;
                foreach (int f in s.followUps)
                {
                    CommandStep t = currentCommandStateObject.commandSteps[f];

                    if (t.activated)
                    {
                        switch (currentLineType)
                        {
                        case LineDrawType.CENTER:
                            Handles.DrawBezier(
                                s.myRect.center,
                                t.myRect.center,
                                s.myRect.center,
                                t.myRect.center,
                                Color.white, null, 3f);
                            break;

                        case LineDrawType.END_TO_END:
                            Handles.DrawBezier(
                                new Vector2(s.myRect.xMax - 2f, s.myRect.center.y),
                                new Vector2(t.myRect.xMin + 2, t.myRect.center.y),
                                new Vector2(s.myRect.xMax, s.myRect.center.y),
                                new Vector2(t.myRect.xMin, t.myRect.center.y),
                                Color.white, null, 3f);
                            break;

                        case LineDrawType.BEZIER_END_TO_END:
                            Handles.DrawBezier(
                                new Vector2(s.myRect.xMax - 2f, s.myRect.center.y),
                                new Vector2(t.myRect.xMin + 2f, t.myRect.center.y),
                                new Vector2(s.myRect.xMax + 30f, s.myRect.center.y),
                                new Vector2(t.myRect.xMin - 30f, t.myRect.center.y),
                                Color.white, null, 3f);
                            break;
                        }



                        if (GUI.Button(new Rect((t.myRect.center + s.myRect.center) * 0.5f + (xButton * -0.5f), xButton), "X"))
                        {
                            deleteMe = fCounter;
                        }
                    }
                    fCounter++;
                }
                if (deleteMe > -1)
                {
                    s.followUps.RemoveAt(deleteMe); currentCommandStateObject.CleanUpBaseState();
                }
            }
            sCounter++;
        }

        Handles.EndGUI();


        BeginWindows();
        sizerStep = 30;
        //GUI.backgroundColor = Color.black;
        int cCounter = 0;

        foreach (CommandStep c in currentCommandStateObject.commandSteps)
        {
            if (c.activated && cCounter > drawBase)
            {
                c.myRect = GUI.Window(c.idIndex, c.myRect, WindowFunction, "", EditorStyles.miniButton);
            }
            cCounter++;
        }

        EndWindows();
        EditorGUILayout.EndScrollView();
        EditorUtility.SetDirty(coreData);
    }
    void WindowFunction(int windowID)
    {
        MoveList     currentMoveList           = coreData.moveLists[coreData.currentMovelistIndex];
        CommandState currentCommandStateObject = currentMoveList.commandStates[currentCommandStateIndex];

        if (currentCommandStateIndex >= currentMoveList.commandStates.Count)
        {
            currentCommandStateIndex = 0;
        }
        if (windowID >= currentCommandStateObject.commandSteps.Count)
        {
            return;
        }
        currentCommandStateObject.commandSteps[windowID].myRect.width  = 175;
        currentCommandStateObject.commandSteps[windowID].myRect.height = 50;


        EditorGUI.LabelField(new Rect(6, 7, 35, 20), windowID.ToString());
        currentCommandStateObject.commandSteps[windowID].command.motionCommand =
            EditorGUI.IntPopup(new Rect(25, 5, 50, 20), currentCommandStateObject.commandSteps[windowID].command.motionCommand, coreData.GetMotionCommandNames(), null, EditorStyles.miniButtonLeft);

        currentCommandStateObject.commandSteps[windowID].command.input =
            EditorGUI.IntPopup(new Rect(75, 5, 65, 20), currentCommandStateObject.commandSteps[windowID].command.input, coreData.GetRawInputNames(), null, EditorStyles.miniButtonMid);
        currentCommandStateObject.commandSteps[windowID].command.state =
            EditorGUI.IntPopup(new Rect(40, 26, 70, 20), currentCommandStateObject.commandSteps[windowID].command.state, coreData.GetStateNames(), null, EditorStyles.miniButton);

        currentCommandStateObject.commandSteps[windowID].priority =
            EditorGUI.IntField(new Rect(6, 26, 20, 20), currentCommandStateObject.commandSteps[windowID].priority);


        int nextFollowup = -1;

        nextFollowup = EditorGUI.IntPopup(new Rect(150, 15, 21, 20), nextFollowup, coreData.GetFollowUpNames(currentCommandStateIndex, true), null, EditorStyles.miniButton);

        if (nextFollowup != -1)
        {
            if (currentCommandStateObject.commandSteps.Count > 0)
            {
                if (nextFollowup >= currentCommandStateObject.commandSteps.Count + 1)
                {
                    currentCommandStateObject.RemoveChainCommands(windowID);
                }
                else if (nextFollowup >= currentCommandStateObject.commandSteps.Count)
                {
                    CommandStep nextCommand = currentCommandStateObject.AddCommandStep();
                    nextCommand.myRect.x      = currentCommandStateObject.commandSteps[windowID].myRect.xMax + 40f;
                    nextCommand.myRect.y      = currentCommandStateObject.commandSteps[windowID].myRect.center.y - 15f;
                    nextCommand.command.input = currentCommandStateObject.commandSteps[windowID].command.input;
                    nextCommand.command.state = currentCommandStateObject.commandSteps[windowID].command.state;

                    currentCommandStateObject.commandSteps[windowID].AddFollowUp(nextCommand.idIndex);
                }
                else
                {
                    currentCommandStateObject.commandSteps[windowID].AddFollowUp(nextFollowup);
                }
            }
            else
            {
                currentCommandStateObject.commandSteps[windowID].AddFollowUp(nextFollowup);
            }
            currentCommandStateObject.CleanUpBaseState();
        }

        if ((Event.current.button == 0) && (Event.current.type == EventType.MouseDown))
        {
            currentChainStep = windowID;
        }

        GUI.DragWindow();
    }
    void UpdateInput()
    {
        inputBuffer.Update();

        bool startState = false;

        GetCommandState();
        CommandState comState = GameEngine.gameEngine.CurrentMoveList().commandStates[currentCommandState];


        if (currentCommandStep >= comState.commandSteps.Count)
        {
            currentCommandStep = 0;
        }


        cancelStepList[0] = currentCommandStep;//base sub-state
        cancelStepList[1] = 0;
        int finalS          = -1;
        int finalF          = -1;
        int currentPriority = -1;

        for (int s = 0; s < cancelStepList.Length; s++)
        {
            if (comState.commandSteps[currentCommandStep].strict && s > 0)
            {
                break;
            }
            if (!comState.commandSteps[currentCommandStep].activated)
            {
                break;
            }

            for (int f = 0; f < comState.commandSteps[cancelStepList[s]].followUps.Count; f++)// (CommandStep cStep in comState.commandSteps[currentCommandStep])
            {
                CommandStep  nextStep    = comState.commandSteps[comState.commandSteps[cancelStepList[s]].followUps[f]];
                InputCommand nextCommand = nextStep.command;

                //if(inputBuffer.)
                if (CheckInputCommand(nextCommand))
                {
                    if (canCancel)
                    {
                        if (GameEngine.coreData.characterStates[nextCommand.state].ConditionsMet(this))
                        {
                            if (nextStep.priority > currentPriority)
                            {
                                currentPriority = nextStep.priority;
                                startState      = true;
                                finalS          = s;
                                finalF          = f;
                            }
                        }
                    }
                }
            }
        }
        if (startState)
        {
            CommandStep  nextStep    = comState.commandSteps[comState.commandSteps[cancelStepList[finalS]].followUps[finalF]];
            InputCommand nextCommand = nextStep.command;
            inputBuffer.UseInput(nextCommand.input);
            if (nextStep.followUps.Count > 0)
            {
                currentCommandStep = nextStep.idIndex;
            }
            else
            {
                currentCommandStep = 0;
            }
            StartState(nextCommand.state);
        }
    }
Exemple #10
0
        /// <summary>
        /// Adds the steps to configure the stateful Elasticsearch instances used to persist the log data.
        /// </summary>
        /// <param name="steps">The configuration step list.</param>
        private void AddElasticsearchSteps(ConfigStepList steps)
        {
            var esNodes = new List <SshProxy <NodeDefinition> >();

            foreach (var nodeDefinition in hive.Definition.Nodes.Where(n => n.Labels.LogEsData))
            {
                esNodes.Add(hive.GetNode(nodeDefinition.Name));
            }

            // Determine number of manager nodes and the quorum size.
            // Note that we'll deploy an odd number of managers.

            var managerCount = Math.Min(esNodes.Count, 5);   // We shouldn't ever need more than 5 managers

            if (!NeonHelper.IsOdd(managerCount))
            {
                managerCount--;
            }

            var quorumCount = (managerCount / 2) + 1;

            // Sort the nodes by name and then separate the manager and
            // worker nodes (managers will be assigned to nodes that appear
            // first in the list).

            var managerEsNodes = new List <SshProxy <NodeDefinition> >();
            var normalEsNodes  = new List <SshProxy <NodeDefinition> >();

            esNodes = esNodes.OrderBy(n => n.Name).ToList();

            foreach (var esNode in esNodes)
            {
                if (managerEsNodes.Count < managerCount)
                {
                    managerEsNodes.Add(esNode);
                }
                else
                {
                    normalEsNodes.Add(esNode);
                }
            }

            // Figure out how much RAM to allocate to the Elasticsearch Docker containers
            // as well as Java heap within.  The guidance is to set the heap size to half
            // the container RAM up to a maximum of 31GB.

            var esContainerRam = hive.Definition.Log.EsMemoryBytes;
            var esHeapBytes    = Math.Min(esContainerRam / 2, 31L * NeonHelper.Giga);

            // We're going to use explicit docker commands to deploy the Elasticsearch cluster
            // log storage containers.
            //
            // We're mounting three volumes to the container:
            //
            //      /etc/neon/host-env         - Generic host specific environment variables
            //      /etc/neon/env-log-esdata   - Elasticsearch node host specific environment variables
            //      neon-log-esdata-#          - Persistent Elasticsearch data folder

            var esBootstrapNodes = new StringBuilder();

            foreach (var esMasterNode in managerEsNodes)
            {
                esBootstrapNodes.AppendWithSeparator($"{esMasterNode.PrivateAddress}:{HiveHostPorts.LogEsDataTcp}", ",");
            }

            // Create a data volume for each Elasticsearch node and then start the node container.

            for (int i = 0; i < esNodes.Count; i++)
            {
                var esNode        = esNodes[i];
                var containerName = $"neon-log-esdata";
                var isMaster      = managerEsNodes.Contains(esNode) ? "true" : "false";
                var volumeCommand = CommandStep.CreateSudo(esNode.Name, "docker-volume-create", containerName);

                steps.Add(volumeCommand);

                ServiceHelper.AddContainerStartSteps(hive, steps, esNode, containerName, hive.Definition.Image.Elasticsearch,
                                                     new CommandBundle(
                                                         "docker run",
                                                         "--name", containerName,
                                                         "--detach",
                                                         "--restart", "always",
                                                         "--volume", "/etc/neon/host-env:/etc/neon/host-env:ro",
                                                         "--volume", $"{containerName}:/mnt/esdata",
                                                         "--env", $"ELASTICSEARCH_CLUSTER={hive.Definition.Datacenter}.{hive.Definition.Name}.neon-log-esdata",
                                                         "--env", $"ELASTICSEARCH_NODE_MASTER={isMaster}",
                                                         "--env", $"ELASTICSEARCH_NODE_DATA=true",
                                                         "--env", $"ELASTICSEARCH_NODE_COUNT={esNodes.Count}",
                                                         "--env", $"ELASTICSEARCH_HTTP_PORT={HiveHostPorts.LogEsDataHttp}",
                                                         "--env", $"ELASTICSEARCH_TCP_PORT={HiveHostPorts.LogEsDataTcp}",
                                                         "--env", $"ELASTICSEARCH_QUORUM={quorumCount}",
                                                         "--env", $"ELASTICSEARCH_BOOTSTRAP_NODES={esBootstrapNodes}",
                                                         "--env", $"ES_JAVA_OPTS=-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap",
                                                         "--memory", $"{esContainerRam / NeonHelper.Mega}M",
                                                         "--memory-reservation", $"{esContainerRam / NeonHelper.Mega}M",
                                                         "--memory-swappiness", "0",
                                                         "--network", "host",
                                                         "--log-driver", "json-file", // Ensure that we don't log to the pipeline to avoid cascading events.
                                                         ServiceHelper.ImagePlaceholderArg));
            }

            // Configure a private hive proxy route to the Elasticsearch nodes.

            steps.Add(ActionStep.Create(hive.FirstManager.Name, "setup/elasticsearch-lbrule",
                                        node =>
            {
                var rule = new TrafficHttpRule()
                {
                    Name     = "neon-log-esdata",
                    System   = true,
                    Log      = false,       // This is important: we don't want to SPAM the log database with its own traffic.
                    Resolver = null
                };

                rule.Frontends.Add(
                    new TrafficHttpFrontend()
                {
                    ProxyPort = HiveHostPorts.ProxyPrivateHttpLogEsData
                });

                foreach (var esNode in esNodes)
                {
                    rule.Backends.Add(
                        new TrafficHttpBackend()
                    {
                        Server = esNode.Metadata.PrivateAddress.ToString(),
                        Port   = HiveHostPorts.LogEsDataHttp
                    });
                }

                hive.PrivateTraffic.SetRule(rule);
            }));

            // Wait for the elasticsearch cluster to become ready and then save the
            // [logstash-*] template.  We need to do this before [neon-log-collector]
            // is started so we'll be sure that no indexes will be created before
            // we have a chance to persist the pattern.
            //
            // This works because [neon-log-collector] is the main service responsible
            // for persisting events to this index.

            steps.Add(ActionStep.Create(hive.FirstManager.Name, operationName: null,
                                        node =>
            {
                node.Status = "wait for elasticsearch cluster";

                using (var jsonClient = new JsonClient())
                {
                    var baseLogEsDataUri = hive.Definition.LogEsDataUri;
                    var timeout          = TimeSpan.FromMinutes(5);
                    var timeoutTime      = DateTime.UtcNow + timeout;
                    var esNodeCount      = hive.Definition.Nodes.Count(n => n.Labels.LogEsData);

                    // Wait for the Elasticsearch cluster.

                    jsonClient.UnsafeRetryPolicy = NoRetryPolicy.Instance;

                    while (true)
                    {
                        try
                        {
                            var response = jsonClient.GetUnsafeAsync($"{baseLogEsDataUri}/_cluster/health").Result;

                            if (response.IsSuccess)
                            {
                                var clusterStatus = response.AsDynamic();
                                var status        = (string)(clusterStatus.status);

                                status      = status.ToUpperInvariant();
                                node.Status = $"wait for [neon-log-esdata] cluster: [status={status}] [{clusterStatus.number_of_nodes}/{esNodeCount} nodes ready])";

                                // $todo(jeff.lill):
                                //
                                // We're accepting YELLOW status here due to this issue:
                                //
                                //      https://github.com/jefflill/NeonForge/issues/257

                                if ((status == "GREEN" || status == "YELLOW") && clusterStatus.number_of_nodes == esNodeCount)
                                {
                                    node.Status = "elasticsearch cluster is ready";
                                    break;
                                }
                            }
                        }
                        catch
                        {
                            if (DateTime.UtcNow >= timeoutTime)
                            {
                                node.Fault($"[neon-log-esdata] cluster not ready after waiting [{timeout}].");
                                return;
                            }
                        }

                        Thread.Sleep(TimeSpan.FromSeconds(1));
                    }

                    // Save the [logstash-*]  template pattern.

                    var templatePattern = ResourceFiles.Root.GetFolder("Elasticsearch").GetFile("logstash-template.json").Contents;

                    jsonClient.PutAsync($"{baseLogEsDataUri}/_template/logstash-*", templatePattern).Wait();
                }
            }));
        }