story: |
    As a tester I want to specify detailed hardware requirements
    for the guest on which the tests will be executed.

description: |
    As part of the :tmt:story:`/spec/plans/provision` step it is
    possible to use the ``hardware`` key to specify additional
    requirements for the testing environment. This provides a
    generic and an extensible way to write down essential hardware
    requirements. For example one consistent way how to specify
    `at least 2 GB of RAM` for all supported provisioners:

    .. code-block:: yaml

        provision:
            image: fedora
            hardware:
                memory: ">= 2 GB"

    The current state of support of HW requirements among plugins
    bundled with tmt is available at
    :ref:`/plugins/provision/hardware-requirement-support-matrix`.

    Introduction
    ^^^^^^^^^^^^

    Individual requirements are provided as a simple ``key:
    value`` pairs, for example the minimum amount of
    :tmt:story:`/spec/hardware/memory`, or the related information is
    grouped under a common parent, for example ``cores`` or
    ``model`` under the :tmt:story:`/spec/hardware/cpu` key.

    Comparison operators
    --------------------

    The ``value`` part of the constraint follows the schema ``[operator] actual-value``.
    The ``operator`` is optional, it may be any of the following: ``=``, ``!=``,
    ``>``, ``>=``, ``<``, ``>=``, ``~`` (regex match), and ``!~`` (regex *not*
    matches). When operator is omitted, ``=`` is assumed.

    .. warning::

        Only ``=``, ``!=``, ``>``, ``>=``, ``<`` and ``<=`` operators are
        accepted for requirements that accept numeric values, e.g.
        :tmt:story:`cpu.cores </spec/hardware/cpu>` or
        :tmt:story:`tpm.version </spec/hardware/tpm>`.

        Only ``=`` and ``!=`` operators are accepted for flag-like requirements,
        e.g. :tmt:story:`virtualization.is-virtualized </spec/hardware/virtualization>`.

        Only ``=``, ``!=``, ``~`` and ``!~`` operators are accepted for
        requirements that accept string values, e.g.
        :tmt:story:`cpu.model-name </spec/hardware/cpu>` or
        :tmt:story:`virtualization.hypervisor </spec/hardware/virtualization>`.

    .. note::

        It is **highly** recommended to wrap values with single or double quotes,
        i.e. ``memory: '>= 8 GiB'`` rather than ``memory: >= 8 GiB``. This should
        prevent any unexpected processing by parsers loading the fmf content.
        Without explicit quotes, some operators might lead to unexpected outcome.

        .. code-block:: yaml

            # This...
            memory: '8 GB'
            # ... is the same as this:
            memory: '= 8 GB'

            # Optional operators at the start of the value
            memory: '!= 8 GB'
            memory: '> 8 GB'
            memory: '>= 8 GB'
            memory: '< 8 GB'
            memory: '<= 8 GB'

    Search
    ------

    Regular expressions can be used for selected fields such as
    the :tmt:story:`/spec/hardware/cpu` model name or
    :tmt:story:`/spec/hardware/hostname`. Use ``~`` for positive and
    ``!~`` for negative regular expressions at the beginning of
    the string. Any leading or trailing white space from the
    search string is removed.

    .. code-block:: yaml

        # Search for processors using a cpu model name
        cpu:
            model-name: "~ AMD"

        # Select guests with a non-matching hostname
        hostname: "!~ kvm-01.*"

    .. note::

       The full extent of regular expressions might not be supported
       across all provision implementations. However, the "any
       character" pattern, ``.*``, should be supported everywhere.

    Logic operators
    ---------------

    When multiple environment requirements are provided the
    provision implementation should attempt to satisfy all of
    them. It is also possible to write this explicitly using the
    ``and`` operator containing a list of dictionaries with
    individual requirements. When the ``or`` operator is used, any
    of the alternatives provided in the list should be
    sufficient:

    .. code-block:: yaml

        # By default exact value expected, these are equivalent:
        cpu:
            model: 37
        cpu:
            model: '= 37'

        # Using advanced logic operators
        and:
          - cpu:
                family: 15
          - or:
              - cpu:
                    model: 65
              - cpu:
                    model: 67
              - cpu:
                    model: 69

    .. note::

        ``and`` and ``or`` operators cannot be combined with HW
        requirements on the same level:

        .. code-block:: yaml

            # Incorrect:
            hardware:
              memory: 1 GB
              or:
                - hostname: foo.redhat.com
                - hostname: bar.redhat.com

            # Use this instead:
            hardware:
              and:
                - memory: 1 GB
                - or:
                  - hostname: foo.redhat.com
                  - hostname: bar.redhat.com

    Units
    -----

    The `pint`__ library is used for processing various units,
    both decimal and binary prefixes can be used:

    .. code-block::

        1 MB = 1 000 000 B
        1 MiB = 1 048 576 B

    __ https://pint.readthedocs.io/

    Names and IDs
    -------------

    When looking for specific devices, it is often possible to
    request them either by their name or by some kind of ID. The
    specification follows a simple naming scheme when a property of
    a device can be expressed as a name as well as an ID:

    * keys with ``-name`` suffix represent human-comprehensible
      string names - model name, vendor name, microarchitecture code
      name, brand names, and so on. For example, ``Intel(R) Xeon(R)
      CPU E5-2670 v2 @ 2.50GHz`` would be a ``cpu.model-name``
      requirement.
    * IDs are left without a suffix. IDs tend to be integers, or
      groups of integers. For example, ``62`` would be a
      ``cpu.model`` requirement.

    Requirements given by a name can often make use of regular
    expression operators (``~`` and ``!~``) while IDs can be used
    very reasonably with other comparison operators like ``>=``.

    Device and vendor IDs
    ---------------------

    Besides the names, provisioning infrastructures may support
    searching for devices by device and/or vendor ID. The ID
    namespace would be determined by the guest architecture, the
    buses and other specifications the guest HW is built from.

    For example, probably the most common namespace would be the
    `PCI ID Repository <https://pci-ids.ucw.cz/>`_, collecting IDs
    and names of PCI devices, easy to encounter in both bare-metal
    and virtualized x86_64 guests. However, this ID database is not
    the only one in existence, and guest architecture may allow
    for additional device and vendor ID schemas.

    Priority of variants
    --------------------

    When the ``hardware`` field can be satisfied in more than one way,
    i.e. allowing multiple correct answers - variants - then the actual
    guest hardware configuration depends on the behavior of the
    ``provision`` plugin used. Consider the following examples:

    .. code-block:: yaml

        # Single variant
        hardware:
          memory: '>= 4 GB'

        # Multiple variants
        hardware:
          or:
            - memory: '>= 4 GB'
            - memory: '< 4 GB'

    The first example is simple: there is only one way a potential guest
    can satisfy it, i.e. by having at least 4 GB of RAM, and no other
    hardware configuration would be suitable.

    The second example offers two ways a guest can satisfy the
    requirements: by having at least 4 GB of RAM, or having strictly
    less than 4 GB of RAM. Both answers would be correct. Handling of
    these variants splits :ref:`/plugins/provision` into two groups:

    * Plugins that construct guests from the requirements:
      :ref:`/plugins/provision/virtual.testcloud`

      These plugins will take the first variant - ``memory: '>= 4 GB'``
      - and they will construct the guest to match this subset of rules.
      All other variants are ignored.
    * Plugins that select guests from a preexisting inventory of
      machines: :ref:`/plugins/provision/beaker`

      These plugins are capable of finding a machine that fits either of
      the variants, and the guest may have either at least 4 GB of RAM,
      or strictly less.

    .. note::

        :ref:`/plugins/provision/artemis` is a special case: it may
        support both behaviors depending on the selected provisioning
        pool.

    Notes
    -----

    The implementation for this common ``hardware`` key is in
    progress. Features under this section marked as implemented
    are already supported by the ``artemis`` plugin. Support in
    other provision plugins is on the way.  Check individual
    plugin documentation for additional information on the
    hardware requirement support.

    .. note::

        Some plugins may require additional configuration. For
        example, Artemis can provision machines with disks of a
        particular size, but to do so, Artemis maintainers must
        configure disk sizes for various AWS / OpenStack / Azure
        flavors. The constraint is supported and implemented, but
        it will not deliver the required virtual machine when the
        plugin's backend, the Artemis deployment, isn't configured
        correctly.

    .. note::

        Some plugins may support requirements that are impossible
        to satisfy, e.g. the :ref:`/plugins/provision/local`
        plugin can support the ``cpu.family`` requirement, but it
        is hard-locked to the CPU of one's own machine.

        When facing impossible requirements, plugins will emit a
        warning reporting this situation to the user, but in general,
        plugins will try to continue provisioning the guest.

example:
  - |
    # Use the artemis plugin to provision the latest Fedora on
    # a guest with the x86_64 architecture and 8 GB of memory
    provision:
        how: artemis
        image: fedora
        hardware:
            arch: x86_64
            memory: 8 GB
