.. _cli_design:

**********
CLI Design
**********
The FIREWHEEL CLI has two major objectives which make it unique.
First, it has to uniquely work across a FIREWHEEL cluster.
Second, it needs to be flexible enough to use either Shell or Python scripts.
To accomplish these objectives, the CLI has been extended with :ref:`cli_helper_section`.
:ref:`cli_helper_section` do not use the standard `Cmd <https://docs.python.org/3/library/cmd.html>`_ format, but they enable us to use Python and Shell scripting to perform various actions across the cluster (see :ref:`cli_executors`).
Additionally, the CLI automatically distributes Helpers to enable performing actions over the entire cluster.

In order to support its extensible capabilities, the CLI uses the FIREWHEEL configuration file and depends on a few externally configured entities.
Please see :ref:`firewheel_configuration` for more details.

.. _cli_helper_section:

Helpers
=======
Helpers allow the extension of the CLI with new "commands".
A Helper is a flat text file made up of different sections (see :ref:`extending_helpers`).
They are invoked by name or by using the :ref:`command_run` command.
For example, the :ref:`helper_experiment` Helper can be run by using any of the following methods.

.. code-block:: bash

       $ firewheel experiment
       $ firewheel run experiment
       fw-cli> experiment
       fw-cli> run experiment

.. note::
    Recall that the ``fw-cli`` prompt indicates the CLI is running in interactive mode.

Each Helper can define a series of sections which will execute a series of actions (via :ref:`cli_executors`) on either the `compute` or `control` nodes.

The full list of Helpers is found here: :ref:`cli_helpers`.

.. _cli_distributed_section:

Distributed CLI
===============
The CLI uses host groups to logically group hosts in the cluster.
Default host groups include ``control`` and ``compute`` and are defined in the FIREWHEEL configuration file (see :ref:`firewheel_configuration`).

Commands are distributed to local caches on each physical host in the cluster.
By default, these caches are located at ``/tmp/firewheel/fw_cli``.
Each `RUN` section in a Helper (see :ref:`extending_helpers`) becomes a file in the cache, with an appropriate extension supplied by its Executor.
The command cache is created and updated using the :ref:`command_sync` command, which uses `ClusterShell <https://clustershell.readthedocs.io/en/latest/>`_ as a back-end.
If Helpers are modified, the cache must be manually updated (using the :ref:`command_sync` command).
If a host does not have a cached file for a particular Helper, it will result in an execution error on that host.

Once the executable file has been distributed, the Executor then uses
`ClusterShell <https://clustershell.readthedocs.io/en/latest/>`_ to invoke the file and return the output.
If the command is run on multiple hosts, all output (``stdout`` and ``stderr``) will displayed and logged.

The CLI notes on screen (but does not take any action) if a Helper (most of
which are run remotely from the perspective of the CLI) fails.
The failure indication occurs when the remote command gives a non-zero exit code.

Sessions
========
To assist with distinguishing output from commands, each CLI "session" will be assigned a UUID.
A session is either one interactive CLI session or a single non-interactive command.
Each command in a session is also assigned a sequence number which is used to further identify commands within the session.
Session IDs and sequence numbers are useful when viewing the CLI history or CLI logs.

Example
=======

To provide a complete view of the CLI, we can walk through a typical example.
A user may want to quickly view the status an experiment that just launched.
They can use the :ref:`helper_vm_mix` Helper.

.. code-block:: bash

       $ firewheel vm mix
       +----------------------------------------------------+-------------+------------------+-------+
       |                      VM Image                      | Power State |   VM Resource    | Count |
       |                                                    |             |      State       |       |
       +====================================================+=============+==================+=======+
       | ubuntu-16.04.4-server-amd64.qcow2                  | RUNNING     | configuring      | 4     |
       +----------------------------------------------------+-------------+------------------+-------+
       | ubuntu-16.04.4-server-amd64.qcow2                  | RUNNING     | uninitialized    | 2     |
       +----------------------------------------------------+-------------+------------------+-------+
       | vyos-1.1.8.qc2                                     | RUNNING     | uninitialized    | 12    |
       +----------------------------------------------------+-------------+------------------+-------+
       |                                                    |             |                  |       |
       +----------------------------------------------------+-------------+------------------+-------+
       |                                                    |             | Total Scheduled  | 18    |
       +----------------------------------------------------+-------------+------------------+-------+

Then, the :ref:`command_history` command will output something like:

.. code-block:: bash

       $ firewheel history
       <Count>: <ID>:<Sequence Number> -- <command>
       0: ae9ec47e-44e1-4d4d-8caf-d21517395d5d:0 -- vm mix

The resulting messages in ``firewheel.log`` will look like::

    [2020-08-25 06:59:42 INFO FirewheelCLI] Beginning command: vm mix
    [2020-08-25 06:59:44 INFO FirewheelCLI] Command returned: 0

The ``cli.log`` will have logged the command and look similar to::

       [2020-08-25 06:59:42 INFO] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Started session ae9ec47e-44e1-4d4d-8caf-d21517395d5d
       [2020-08-25 06:59:42 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Preparing to run command=`cd /home/user 2>/dev/null; /opt/firewheel/fwpy/bin/python3 /tmp/firewheel/fw_cli/vm/mix0.py` for nodes=['node1'].
       [2020-08-25 06:59:42 INFO] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Starting to run cmd=cd /home/user 2>/dev/null; /opt/firewheel/fwpy/bin/python3 /tmp/firewheel/fw_cli/vm/mix0.py
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: ``
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: ``
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `+----------------------------------------------------+-------------+------------------+-------+`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `|                      VM Image                      | Power State |   VM Resource    | Count |`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `|                                                    |             |      State       |       |`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `+====================================================+=============+==================+=======+`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `| ubuntu-16.04.4-server-amd64.qcow2                  | RUNNING     | configuring      | 4     |`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `+----------------------------------------------------+-------------+------------------+-------+`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `| ubuntu-16.04.4-server-amd64.qcow2                  | RUNNING     | uninitialized    | 2     |`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `+----------------------------------------------------+-------------+------------------+-------+`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `| vyos-1.1.8.qc2                                     | RUNNING     | uninitialized    | 12    |`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `+----------------------------------------------------+-------------+------------------+-------+`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `|                                                    |             |                  |       |`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `+----------------------------------------------------+-------------+------------------+-------+`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `|                                                    |             | Total Scheduled  | 18    |`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: `+----------------------------------------------------+-------------+------------------+-------+`
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: ``
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Output from node1: ``
       [2020-08-25 06:59:44 DEBUG] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Node node1 returned successfully!
       [2020-08-25 06:59:44 INFO] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - Command succeeded!
       [2020-08-25 06:59:44 INFO] ae9ec47e-44e1-4d4d-8caf-d21517395d5d::0 - fatal_error_count = 0