summaryrefslogtreecommitdiffstats
path: root/ @media only all and (prefers-color-scheme: dark) { .highlight .hll { background-color: #49483e } .highlight .c { color: #75715e } /* Comment */ .highlight .err { color: #960050; background-color: #1e0010 } /* Error */ .highlight .k { color: #66d9ef } /* Keyword */ .highlight .l { color: #ae81ff } /* Literal */ .highlight .n { color: #f8f8f2 } /* Name */ .highlight .o { color: #f92672 } /* Operator */ .highlight .p { color: #f8f8f2 } /* Punctuation */ .highlight .ch { color: #75715e } /* Comment.Hashbang */ .highlight .cm { color: #75715e } /* Comment.Multiline */ .highlight .cp { color: #75715e } /* Comment.Preproc */ .highlight .cpf { color: #75715e } /* Comment.PreprocFile */ .highlight .c1 { color: #75715e } /* Comment.Single */ .highlight .cs { color: #75715e } /* Comment.Special */ .highlight .gd { color: #f92672 } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gi { color: #a6e22e } /* Generic.Inserted */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #75715e } /* Generic.Subheading */ .highlight .kc { color: #66d9ef } /* Keyword.Constant */ .highlight .kd { color: #66d9ef } /* Keyword.Declaration */ .highlight .kn { color: #f92672 } /* Keyword.Namespace */ .highlight .kp { color: #66d9ef } /* Keyword.Pseudo */ .highlight .kr { color: #66d9ef } /* Keyword.Reserved */ .highlight .kt { color: #66d9ef } /* Keyword.Type */ .highlight .ld { color: #e6db74 } /* Literal.Date */ .highlight .m { color: #ae81ff } /* Literal.Number */ .highlight .s { color: #e6db74 } /* Literal.String */ .highlight .na { color: #a6e22e } /* Name.Attribute */ .highlight .nb { color: #f8f8f2 } /* Name.Builtin */ .highlight .nc { color: #a6e22e } /* Name.Class */ .highlight .no { color: #66d9ef } /* Name.Constant */ .highlight .nd { color: #a6e22e } /* Name.Decorator */ .highlight .ni { color: #f8f8f2 } /* Name.Entity */ .highlight .ne { color: #a6e22e } /* Name.Exception */ .highlight .nf { color: #a6e22e } /* Name.Function */ .highlight .nl { color: #f8f8f2 } /* Name.Label */ .highlight .nn { color: #f8f8f2 } /* Name.Namespace */ .highlight .nx { color: #a6e22e } /* Name.Other */ .highlight .py { color: #f8f8f2 } /* Name.Property */ .highlight .nt { color: #f92672 } /* Name.Tag */ .highlight .nv { color: #f8f8f2 } /* Name.Variable */ .highlight .ow { color: #f92672 } /* Operator.Word */ .highlight .w { color: #f8f8f2 } /* Text.Whitespace */ .highlight .mb { color: #ae81ff } /* Literal.Number.Bin */ .highlight .mf { color: #ae81ff } /* Literal.Number.Float */ .highlight .mh { color: #ae81ff } /* Literal.Number.Hex */ .highlight .mi { color: #ae81ff } /* Literal.Number.Integer */ .highlight .mo { color: #ae81ff } /* Literal.Number.Oct */ .highlight .sa { color: #e6db74 } /* Literal.String.Affix */ .highlight .sb { color: #e6db74 } /* Literal.String.Backtick */ .highlight .sc { color: #e6db74 } /* Literal.String.Char */ .highlight .dl { color: #e6db74 } /* Literal.String.Delimiter */ .highlight .sd { color: #e6db74 } /* Literal.String.Doc */ .highlight .s2 { color: #e6db74 } /* Literal.String.Double */ .highlight .se { color: #ae81ff } /* Literal.String.Escape */ .highlight .sh { color: #e6db74 } /* Literal.String.Heredoc */ .highlight .si { color: #e6db74 } /* Literal.String.Interpol */ .highlight .sx { color: #e6db74 } /* Literal.String.Other */ .highlight .sr { color: #e6db74 } /* Literal.String.Regex */ .highlight .s1 { color: #e6db74 } /* Literal.String.Single */ .highlight .ss { color: #e6db74 } /* Literal.String.Symbol */ .highlight .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #a6e22e } /* Name.Function.Magic */ .highlight .vc { color: #f8f8f2 } /* Name.Variable.Class */ .highlight .vg { color: #f8f8f2 } /* Name.Variable.Global */ .highlight .vi { color: #f8f8f2 } /* Name.Variable.Instance */ .highlight .vm { color: #f8f8f2 } /* Name.Variable.Magic */ .highlight .il { color: #ae81ff } /* Literal.Number.Integer.Long */ } @media (prefers-color-scheme: light) { .highlight .hll { background-color: #ffffcc } .highlight .c { color: #888888 } /* Comment */ .highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */ .highlight .k { color: #008800; font-weight: bold } /* Keyword */ .highlight .ch { color: #888888 } /* Comment.Hashbang */ .highlight .cm { color: #888888 } /* Comment.Multiline */ .highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */ .highlight .cpf { color: #888888 } /* Comment.PreprocFile */ .highlight .c1 { color: #888888 } /* Comment.Single */ .highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */ .highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */ .highlight .ge { font-style: italic } /* Generic.Emph */ .highlight .gr { color: #aa0000 } /* Generic.Error */ .highlight .gh { color: #333333 } /* Generic.Heading */ .highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */ .highlight .go { color: #888888 } /* Generic.Output */ .highlight .gp { color: #555555 } /* Generic.Prompt */ .highlight .gs { font-weight: bold } /* Generic.Strong */ .highlight .gu { color: #666666 } /* Generic.Subheading */ .highlight .gt { color: #aa0000 } /* Generic.Traceback */ .highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */ .highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */ .highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */ .highlight .kp { color: #008800 } /* Keyword.Pseudo */ .highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */ .highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */ .highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */ .highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */ .highlight .na { color: #336699 } /* Name.Attribute */ .highlight .nb { color: #003388 } /* Name.Builtin */ .highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */ .highlight .no { color: #003366; font-weight: bold } /* Name.Constant */ .highlight .nd { color: #555555 } /* Name.Decorator */ .highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */ .highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */ .highlight .nl { color: #336699; font-style: italic } /* Name.Label */ .highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */ .highlight .py { color: #336699; font-weight: bold } /* Name.Property */ .highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */ .highlight .nv { color: #336699 } /* Name.Variable */ .highlight .ow { color: #008800 } /* Operator.Word */ .highlight .w { color: #bbbbbb } /* Text.Whitespace */ .highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */ .highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */ .highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */ .highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */ .highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */ .highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */ .highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */ .highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */ .highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */ .highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */ .highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */ .highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */ .highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */ .highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */ .highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */ .highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */ .highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */ .highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */ .highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */ .highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */ .highlight .vc { color: #336699 } /* Name.Variable.Class */ .highlight .vg { color: #dd7700 } /* Name.Variable.Global */ .highlight .vi { color: #3333bb } /* Name.Variable.Instance */ .highlight .vm { color: #336699 } /* Name.Variable.Magic */ .highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */ }
*.java text eol=lf
*.xml text eol=lf
>282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416
.. This work is licensed under a Creative Commons Attribution 4.0 International License.
.. http://creativecommons.org/licenses/by/4.0
.. Copyright 2021 Samsung Electronics Co., Ltd.

Offline Installer Testing Guide
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

This testing guide describes how offline installer can be tested in local
development environment (laptop) without the need for actual servers.

Documentation refers to files/directories in ``ansible`` directory of this repository.

Introduction
============

Offline installer uses Molecule_ for testing all roles.

Molecule is tool for ansible roles development and testing. In this project
Molecule is used for integration type of testing for both roles and playbooks.
Role code is tested against simulated host.

Molecule is designed to test single Ansible_ role in isolation. Offline installer however
has many small roles that are dependent on each other and also execution order for roles
is meaningful. In that respect Molecule's design does not offer sufficient level
of testing as it's lacking playbook level of scenario testing by default.
Luckily Molecule is highly configurable and it is possible to achieve a higher level of
testing scenarios for the offline installer.

Testing with Molecule is divided to two levels of testing:
	1) role level testing (as per Molecule design)
	2) playbook level testing (offline installer own setup)

Purpose
=======

The purpose of using testing framework like Molecule is to make possible for developer to
verify ansible code changes locally in own laptop without the need for big resources.

Developer is also expected to do development of the Ansible code and the Molecule test
code at the same time.
Offline installer does not have unittest level of testing for the ansible code.

Any commit made to ansible code base needs to first pass Molecule tests before
it's merged.

Test levels
===========

To cover both testing levels (role and playbook) with maximum benefit and minimum
copy-pasting, the testing code should be written in reusable way.

Reusable test code can be achieved by writing all prepare/cleanup and other
helping code as a roles into main test directory.
Also testinfra_ test code can be shared between different roles and between different scenarios
of one role.

Testing of role and one scenario (one execution run of molecule) is fully
defined by **molecule.yml** file.

molecule.yml file is always located in directory:

	<tested-role>/molecule/<scenario>/molecule.yml

i.e. one role can have multiple scenarios (different configuration, OS etc. whatever user wants)
to execute tests for same role. Each scenario has own molecule.yml file and own testinfra
tests.

Molecule.yml file is the only file that cannot be re-used (except with symbolic links) but
all other resources can be reused by referencing those in molecule.yml file or/and indirectly
from resources molecule.yml is pointing to.

**tested-role** is clear in case of normal role level testing, but in playbook level testing the
tested-role is just an invented role name and directory with molecule directory inside but no
actual ansible role code.

Role level testing
------------------

The target is to test single role in isolation just like Molecule is designed.
Role level testing is supposed to cover:

- Syntax checking (Yamllint_, `Ansible lint`_, flake8_)
- Ansible code testing
- Idempotence testing
- Verifying role results from target hosts (testinfra tests)

Ansible code testing can/should also cover all different options how this role
can be run (`scenario <https://molecule.readthedocs.io/en/latest/configuration.html#root-scenario>`_).
Different molecule runs can be implemented as own scenarios (in addition to default scenario)
or default scenario playbook can be extended to run role tests multiple times just adjusting
configuration between.

Single scenario example with nexus role

::

    ├── infrastructure.yml
    ├── roles
    │   ├── nexus
    │   │   ├── defaults
    │   │   ├── files
    │   │   ├── molecule
    │   │   │   └── default
    │   │   │       ├── molecule.yml
    │   │   │       ├── playbook.yml
    │   │   │       ├── prepare.yml
    │   │   │       └── tests
    │   │   ├── tasks
    │   │   └── vars

Multiple scenario example with chrony role

::

    roles/chrony
    ├── defaults
    │   └── main.yml
    ├── handlers
    │   └── main.yml
    ├── molecule
    │   ├── default
    │   │   ├── molecule.yml
    │   │   ├── playbook.yml
    │   │   └── prepare.yml
    │   └── ubuntu
    │       └── molecule.yml
    ├── tasks
    │   └── main.yml
    └── templates
        └── chrony.conf.j2

By default molecule runs just default scenario. To run specific one ``-s <scenario name>``
option must be used. The only subcommands supporting ``--all`` switch for playing with
all scenarios are ``test`` and ``destroy``. If using other ones ``-s`` must be used.

The cross-scenario code reuse paradigm should be rather implemented inside particular
scenario's ``molecule.yml`` file than by using filesystem symlinks. All provisioner
playbooks should be located in default scenarios directory then and referenced in
alternative scenarios as follows
::

    provisioner:
      name: ansible
      lint:
        name: ansible-lint
      env:
        ANSIBLE_ROLES_PATH: ../../../../test/roles
      playbooks:
        prepare: ../default/prepare.yml
        converge: ../default/playbook.yml

Playbook level testing
----------------------

Playbook level testing is this project's (offline installer) own
setup and way of using Molecule. The target is to raise testing level
from single role testing up to single playbook testing.

Playbook level testing can be used also to run multiple playbooks and/or
playbooks multiple times with different configuration.

The aim is to verify multiple roles working together i.e. higher level of
integration testing.

Practically the **tested-role** is just a wrapper directory to conform
molecule required directory structure and provide a name for the test.
Directory itself does not contain any ansible role code, but just
molecule files configured to run multiple other roles.

Playbook level test directories should be named consistently according to
tested playbook and prefix string ``play`` and with optional description
if there are multiple scenarios for single playbook:

    play-<playbookname>[-<description>]

E.g.

- ``play-infrastructure``
- ``play-resources``

As role's are tested with own molecule tests in isolation, playbook level tests
should focus to integration of the roles and should avoid of repeating same tests
as done already for individual roles.

Playbook level testing is supposed to cover:
	- Ansible code testing

Basically it's easier to highlight what is supposed to be **avoided** in playbook level
testing for the reason not to repeat the same that is done already in role level testing.

- Syntax checking is left out already by default as molecule does linting only for the
  role code where molecule is run, and in this case tested-role is empty.

- Idempotence can be tested, but should be disabled (by default) in molecule.yml because
  it takes too much time and was tested already for individual roles.

- Verifying target hosts with testinfra tests can be done but then something else
  should be tested as in role based tests. And if those 2 would overlap it's better
  to leave them out.

Example with infrastructure playbook level test files

::

    ├── infrastructure.yml
    └── test
        ├── play-infrastructure
        │   └── molecule
        │       └── default
        │           ├── molecule.yml
        │           ├── playbook.yml
        │           ├── prepare.yml
        │           └── tests

Test code reuse and naming
===========================

As both testing levels test the same Ansible roles, there are a need
to share common code for both of them.

Testinfra_ Python code should be shared when also playbook level
tests verify target hosts. However sharing is not limited only for the 2 test levels
but also between different roles.

Individual role have testinfra tests on directory:

    roles/<role>/molecule/<scenario>/tests

and any commonly usable testinfra Python code should be placed to directory:

    test/testinfra

Ansible role testing uses several resources defined by provisioner section of
molecule.yml
https://molecule.readthedocs.io/en/latest/configuration.html#provisioner

Most common resources that are written for role testing are:

- playbook.yml  (mandatory but can include specific code)
- prepare.yml
- cleanup.yml
- create.yml
- destroy.yml

all of which can be just placed to scenario directory together with playbook.yml
(without editing molecule.yml when in default directory) and all of which can
include ansible code to do something e.g. prepare role for testing.

Example molecule files:

Role level tests for nexus role:
	- roles/nexus/molecule/default/molecule.yml
	- roles/nexus/molecule/default/playbook.yml
	- roles/nexus/molecule/default/prepare.yml
playbook level tests for infrastructure playbook:
	- test/play-infrastructure/molecule/default/molecule.yml
	- test/play-infrastructure/molecule/default/playbook.yml
	- test/play-infrastructure/molecule/default/prepare.yml

Sharing all test code should be done by writing them in the form of ansible
roles and placing commonly usable roles into:

    test/roles/<testrole>

Test roles should be named consistently according to action it's needed and
role for it's for together with optional description:

    <action>-<role>[-<description>]

Examples of commonly used test roles

::

    ├── infrastructure.yml
    └── test
        ├── play-infrastructure
        └── roles
            ├── post-certificates
            ├── prepare-common
            ├── prepare-dns
            ├── prepare-docker
            ├── prepare-nexus
            └── prepare-nginx

Molecule platform images
========================

Molecule can build images of the tested hosts on the fly with default
Dockerfile template (docker driver) or from a Dockerfile provided by user.
In case of Vagrant driver used box image can be also fully customized by user.

To speed up testing and lessen the footprint of code for image preparation it's
preferred to use unmodified images from Docker Registry whenever possible (can be
pulled prior to running Molecule) or pre-build images created from Dockerfiles
listed below. Most significant feature of those is support for Systemd, so they
should be used in cases where ansible's 'systemd' module is used.

Used Dockerfiles/Box definitions are kept in following directory structure

::

    └── test
        └── images
            ├── docker
            │   ├── build-all.sh
            │   ├── centos7
            │   │   ├── build.sh
            │   │   └── Dockerfile
            │   └── ubuntu
            │       ├── build.sh
            │       └── Dockerfile
            └── vagrant

``Build-all.sh`` is a script for building all images, ``build.sh`` scripts in
particular platforms subdirs are for building just specific images. Keep in mind
that while images from Docker Registry will be downloaded automatically at run
time, the above ones **must** be built manually prior to launching Molecule.

Build images
------------

Build all platforms images before running Molecule tests. Building can be done
with the following single command:

		test/images/docker/build-all.sh

Install
=======

Molecule can be installed in multiple ways and in this guide 2 different ways is
covered.

- Install Molecule with pip in virtual environment
- Use Molecule provided docker container to run Molecule

Install with pip
----------------

This is a OS dependent and some prerequisites needs to be installed, but after
prerequisites are installed installing Molecule can be done by calling following
script:

		source test/bin/install-molecule.sh

As for the required OS packages, see example for Ubuntu in the install-molecule.sh
script's comments or from Molecule_ pages.

Note that sourcing the script is not needed to get Molecule installed but it leaves
you already into virtual environment and ready to run Molecule.

To get out from virtual environment issue:

		deactivate

And next time to activate virtual environment again before running Molecule, issue:

		source ~/molecule_venv/bin/activate

And here the directory ``~/molecule_venv`` is just the default virtual environment
path that install-molecule.sh script is using and can be overridden with
``VENV_PATH`` environment variable.

Use Molecule docker container
-----------------------------

Molecule provides docker containers images via quay.io_ where Molecule, Ansible
and all needed dependencies are build to the image.

In this way of using Molecule, no installation is needed and only docker is the
prerequisite for running Molecule.

For using provided image to test offline-installer roles, following scripts are
provided:

Build container image:
		``test/molecule-docker/build.sh``

This will build image named ``molecule-dev`` with strict version tag.

Set molecule into the PATH:
		``source test/bin/set_molecule_paths.sh``

That will add the actual Molecule run wrapper script test/bin/molecule.sh to path
usable from everywhere similarly than molecule with pip and virtual environment.

Run Molecule wrapper script:
		``test/bin/molecule.sh``

For running Molecule. Using ``molecule-dev`` image and the exact version defined by
test/docker/build.sh script.

Usage
=====

Basic usage of molecule tests. See more detailed instructions from Molecule_

Run complete testing for a role or a playbook:

1. cd roles/<role> or cd test/play-<playbook-name>
2. molecule test

Develop a role code and run testing during the coding:

1. cd roles/<role>
2. Edit ansible code and molecule test code when needed
3. molecule converge
4. Repeat steps 2 and 3 until code is ready and molecule tests are passing
5. molecule test

.. _Molecule: https://molecule.readthedocs.io
.. _quay.io: https://quay.io/repository/ansible/molecule
.. _Testinfra: https://testinfra.readthedocs.io
.. _Flake8: http://flake8.pycqa.org
.. _Yamllint: https://github.com/adrienverge/yamllint
.. _Ansible Lint: https://github.com/ansible/ansible-lint
.. _Ansible: https://www.ansible.com/