| .. _framework-developer-guide: |
| |
| ================================================ |
| Cloud Infra Automation Framework Developer Guide |
| ================================================ |
| |
| Introduction |
| ============ |
| |
| Cloud Infra Automation Framework (aka engine) supports provisioning |
| of compute resources, deployment of various stacks, and testing on them. |
| The stacks that are currently supported by the framework are |
| |
| * Kubernetes |
| * ONAP |
| * Openstack |
| |
| Due to the differences in how these stacks are deployed, it is important |
| to keep the core of the engine agnostic to the stacks. This is achieved |
| by pushing stack specific steps into stacks themselves while keeping |
| functionality that is common across different stacks within the core |
| itself. |
| |
| In addition to engine core and stacks, the framework utilizes various tools |
| to provision resources and install stacks. These tools are also separated |
| from the core itself so they can be used by different stacks without |
| coupling them tightly with the core of the stacks themselves. |
| |
| The diagram below shows the architecture of the framework on a high level. |
| |
| .. image:: ./_static/engine.png |
| |
| Engine Components |
| ================= |
| |
| Engine Core |
| ----------- |
| |
| This is the core of the framework which drives the packaging, provisioning |
| of the compute resources, and installation of stacks. |
| |
| The responsibilities of the core are |
| |
| * user interface: Users use the main packaging and deployment scripts developed |
| within engine to drive the packaging and deployment without needing to know what |
| tool they should be using |
| * preparation of stacks: Depending on what the user wants to deploy, the core |
| pulls in the stack based on the version specified by the user and makes basic |
| preparation |
| * execution of stack install script: Once the dependencies are pulled in, the core |
| then executes stack install script and gives control to it |
| * execution of stack packaging script: If the framework is running in packaging |
| mode, the core executes stack packaging script and gives control to it |
| * provisioning of engine services: Engine core hosts the functionality to provision |
| engine services for offline deployments that are common across different stacks. The |
| actual provisioning is performed by the stacks themselves and not the core |
| * providing common libraries: The libraries common across stacks such as libraries for |
| packaging and deployment are kept within engine core |
| |
| Multiple stacks are supported by engine and the stack to package and/or deploy can |
| be controlled using -s switch. This then sets the corresponding environment variable, |
| STACK_TYPE, so the core can clone that stack during the preparation. |
| |
| The framework supports multiple versions of same stack so the users can deploy |
| the version they need. This is enabled by the use of -v switch to engine core which |
| sets corresponding environment variable for the stack to be deployed, STACK_VERSION. |
| This results in engine core to clone that version of the stack during preparation |
| phase. |
| |
| If the engine is executed as part of CI/CD for verification purposes, third environment |
| variable is also exposed, STACK_REFSPEC, so the change that is sent to stack can |
| be verified properly. Please note that it is not possible to set this variable on |
| the command line as it is internal to engine. However, it can still be used if one |
| wants to test a change that's on Gerrit locally by setting the variable manually |
| on shell before executing deploy.sh. |
| |
| Stacks |
| ------ |
| |
| Stacks are the blocks within the framework that host and perform stack specific |
| functions such as identifying the provisioner and installer to use, scenario to |
| deploy the stack with, keep list of dependencies to package for offline deployments, |
| and actually perform the packaging. |
| |
| The responsibilities of the stacks are |
| |
| * interface to engine: The scripts install.sh and package.sh are the interface |
| between the stacks and engine core |
| * determining the provisioner: Stack determines the provisioner to use by analysing |
| the provided PDF file. If a libvirt or baremetal PDF is passed to the framework, it |
| results in the use of Bifrost. If a Heat environment file is passed as PDF to the |
| framework, stack sets Heat as the provisioner. |
| * determining the installer: The installer used by the stack is specific within |
| stack configuration file (eg vars/kubernetes.yaml). Stack then sets installer accordingly. |
| * preparation of provisioner and installer: Based on what is determined in earlier |
| steps, stack prepares corresponding tools by pulling their repositories based on |
| the versions |
| * execution of provisioner: After the completion of the preparation, provision script |
| of the identified provisioner is executed |
| * execution of installer: After the completion of the provisioning, install script |
| of the identified installer is executed |
| * packaging: Stack dependencies are packaged by the stacks themselves as what is |
| needed for different stacks for offline deployments do not match |
| * provisioning of engine services: When run in offline mode, the provisioning of |
| engine services are executed by the stacks even though the actual functionality |
| is developed within engine core |
| |
| As noted in Engine core section, multiple versions of stacks are supported. This |
| in turn means that multiple versions of provisioners and installers are supported so |
| the stack can pick the right version of the tool to use for given stack version. |
| The mapping between the stacks and the tools are controlled within stack configuration |
| file and it is important the versions of the stacks and the tools map correctly. Otherwise |
| a given version of the stack may not be possible to deploy due to compatibility issues. |
| |
| Provisioners |
| ------------ |
| |
| Provisioners are just the tools to provision the resources using and they do not |
| contain stack or installer specific functionality. (this may not be the case at the |
| moment but it will be refactored over time to make them fully agnostic) |
| |
| Provisioners generally do not branch after stacks as they do not have any |
| relation to actual stack versions. Thus, provisioners have single branch/version |
| that can be used by whatever stack. The mapping between stacks/versions and |
| provisioners/versions are controlled within stack configuration files. |
| |
| Installers |
| ---------- |
| |
| Installers are the tools that installs selected stack on provisioned resources. |
| Even though they are very stack oriented, they can still be used by multiple stacks. |
| |
| Installers branch after stacks so the stacks can use the compatible versions of the |
| installers. The mapping between stacks/versions and installers/versions are controlled |
| within stack configuration files. |
| |
| Scenarios |
| ========= |
| |
| Scenarios are very specific to the stacks even though they are closely related to |
| installers themselves. In order to handle differences across various stacks, the |
| scenarios are developed within stack repositories rather than installers. (this is TBD) |
| |
| An example could explain why this is like this. |
| Kubernetes is needed by both Kubernetes stack itself and by ONAP. But the configuration |
| of Kubernetes differs between stacks. One of the differences is that the Kubernetes |
| deployment needed by ONAP requires shared storage but vanilla Kubernetes does not. |
| Differences such as this can be handled within the stacks themselves. |
| |
| Developing New Features or Bugfixes |
| =================================== |
| |
| It is frequently necessary to develop new features for the framework. With |
| the introduction of stacks, provisioners, and installers, where this work |
| needs to be done heavily depends on what is being done. |
| |
| The rule of thumb is - if you need to develop a new feature, it must always |
| be done on master branch first and then cherrypicked to all the active branches. |
| There may be cases that some bugs impact on certain versions or a functionality |
| is needed for a specific version. If this is the case, it would be better to have |
| a quick discussion with the team to determine where you can do that change rather |
| than sending change to a master branch or a maintenance branch. |
| |
| Here are some other examples. |
| |
| * developing new feature for engine core: The features developed for the engine |
| core impact all the stacks. Engine core follows one track approach so the change |
| just needs to be done on master branch. |
| * developing new feature for a specific stack: Since multiple versions of the |
| stacks are supported by the framework, a feature might need to be developed on |
| multiple branches. If this is the case, the change first must be sent to master |
| branch and cherrypicked to other branches once the verification passes. In some |
| cases the features needed by the stacks may need to be developed within provisioner |
| or installer repository. In this case, same thing is needed - the change is done |
| on master branch first and then cherrypicked to other branches accordingly. |
| * developing scenarios: The scenarios are developed and hosted within stack |
| repositories. If what is developed is an existing scenario and it is expected |
| to be used by stack versions other than master, the change needs to go to master |
| and then cherrypicked to other branches. If it is totally a new scenario and should |
| be available for a certain version of the stack, the development can be done on |
| master branch and cherrypicked to only the corresponding branch. |
| |
| Testing Your Changes |
| -------------------- |
| |
| It is always good to test your changes before you send them to Gerrit for review |
| or verification by CI. The level of testing you can do depends on the nature of |
| the change but no matter the complexity of the change, it is always important to |
| run static code analysis before sending the change for review. Further local |
| testing such as deployment and testing is good and important as well but it may |
| not be necessary for all cases. |
| |
| **Local Testing** |
| |
| Engine will pull various repositories as required for the specific stack and |
| scenario requested. To test out local changes to these repositories, Engine |
| allows you to supply additional parameters to Ansible via the environment |
| variable `ENGINE_ANSIBLE_PARAMS`. This can be used to override the locations |
| and versions of the various repositories used by Engine. When using this, be |
| sure to also specify the environment variable `ANSIBLE_HASH_BEHAVIOUR=merge`. |
| |
| To ensure Engine uses local versions of these repositories: |
| |
| * Create a local variables file (e.g. `${HOME}/local_vars.yml`) that specifies |
| the local path for each of the repositories you wish to test: |
| |
| .. code-block:: yaml |
| |
| --- |
| stacks: |
| kubernetes: |
| src: "file:///path/to/your/copy/of/stack/kubernetes" |
| version: "master" |
| provisioners: |
| bifrost: |
| src: "file:///path/to/your/copy/of/provisioner/bifrost" |
| version: "master" |
| installers: |
| kubespray: |
| src: "file:///path/to/your/copy/of/installer/kubespray" |
| version: "master" |
| |
| * Amend the command you would normally use to run `deploy.sh` by prepending |
| the following arguments: |
| |
| .. code-block:: bash |
| |
| ENGINE_ANSIBLE_PARAMS="-e @/path/to/your/local_vars.yml" ANSIBLE_HASH_BEHAVIOUR=merge ./deploy.sh ... |
| |
| * The above invocation of `deploy.sh` will now pull your local versions of the |
| specified repositories, instead of the default location. |
| |
| When testing in offline mode (`-x`), additional actions are necessary to keep the |
| offline repositories under `/opt/engine/offline` in sync. A script similar to the |
| following can be used to ensure your local changes are used: |
| |
| .. code-block:: bash |
| |
| # Change SYNC_BRANCH as required |
| SYNC_BRANCH="master" |
| declare -A REPO_MAP |
| # Change BASEDIR as required |
| BASEDIR="${HOME}" |
| REPO_MAP["${BASEDIR}/src/nordix/infra/stack/kubernetes"]="/opt/engine/offline/git/engine-kubernetes" |
| REPO_MAP["${BASEDIR}/src/nordix/infra/installer/kubespray"]="/opt/engine/offline/git/engine-kubespray" |
| REPO_MAP["${BASEDIR}/src/nordix/infra/provisioner/heat"]="/opt/engine/offline/git/engine-heat" |
| REPO_MAP["${BASEDIR}/src/nordix/infra/stack/onap"]="/opt/engine/offline/git/engine-onap" |
| REPO_MAP["${BASEDIR}/src/nordix/infra/installer/oom"]="/opt/engine/offline/git/engine-oom" |
| |
| for REPO_SRC in "${!REPO_MAP[@]}"; do |
| REPO_DEST="${REPO_MAP[$REPO_SRC]}" |
| if [ ! -d "${REPO_SRC}" ]; then |
| echo "${REPO_SRC} does not exist, skipping" |
| continue |
| fi |
| if [ ! -d "${REPO_DEST}" ]; then |
| echo "${REPO_DEST} does not exist, skipping" |
| continue |
| fi |
| echo "${REPO_DEST}:${SYNC_BRANCH} <-- ${REPO_SRC}:${SYNC_BRANCH}" |
| ( |
| cd "${REPO_DEST}" |
| git remote rm dev || true |
| git remote add dev "file://${REPO_SRC}" |
| git fetch dev |
| git reset --hard dev/${SYNC_BRANCH} |
| ) |
| done |
| |
| Simply run this script prior to running `deploy.sh`. |
| |
| **Static Code Analysis** |
| |
| Engine bash scripts, Ansible playbooks, and other YAML files are subject |
| to static code analysis using various utilities listed below. |
| |
| * `ansible-lint <https://github.com/ansible/ansible-lint>`_ |
| * `shellcheck <https://github.com/koalaman/shellcheck>`_ |
| * `yamllint <https://github.com/adrienverge/yamllint>`_ |
| |
| It is always a good idea to test your changes locally before sending them |
| for code review and for further testing in order to receive fastest possible |
| feedback and also to avoid unnecessary failures on CI. |
| |
| 1. Install python and virtualenv |
| |
| | ``sudo apt install -y python3-minimal virtualenv`` |
| |
| 2. Create and activate virtualenv |
| |
| | ``virtualenv -p python3 .venv`` |
| | ``source .venv/bin/activate`` |
| |
| 3. Install test requirements |
| |
| | ``pip install -r test-requirements.txt`` |
| |
| 4. Use tox to run static code analysis |
| |
| 4a. Run all tests |
| |
| | ``tox`` |
| |
| 4b. Run ansible-lint |
| |
| | ``tox -e ansible-lint`` |
| |
| 4c. Run shellcheck |
| |
| | ``tox -e shellcheck`` |
| |
| 4d. Run yamllint |
| |
| | ``tox -e yamllint`` |
| |
| 4e. Run multiple checks using comma seperated list |
| |
| | ``tox -e ansible-lint,shellcheck`` |
| |
| 4f. If you updated the documentation in infra/engine repo, run docs checks |
| |
| | ``tox -e docs`` |
| |
| Verifying Changes |
| ================= |
| |
| When a change is sent to Gerrit for any part of the framework, it gets tested |
| by the jobs on Jenkins. The types of tests depend on which component the change |
| is sent for but here is the list of tests on a high level, followed by more |
| detailed information. |
| |
| * static analysis: All the changes that are sent to any of the repositories must |
| pass static analysis. Ansible lint, yamllint, and shellcheck are what is currently |
| used for static analysis. |
| * packaging: The changes must not break packaging functionality |
| * deployment: The changes must not break deployment functionality - both in online |
| and offline modes |
| * testing: The changes mush not break the stack functionality. OPNFV Functest Healthcheck |
| is used to ensure the basic stack functionality still works. |
| |
| The jobs on Jenkins block what comes after them. This is implemented in order to |
| catch faults as soon as possible and provide feedback to the developers. |
| |
| |
| Static analysis jobs are the first jobs that get triggered for changes that are sent |
| to any repository. Rest of the jobs are blocked until the successful completion of |
| static analysis. If static analysis fails, the other jobs will be aborted as there is |
| no point running the rest since the failures in static analysis is so basic and must be |
| fixed before attempting even more extensive testing. |
| |
| Upon successful completion of static analysis, packaging job gets triggered. Similar to |
| static analysis, packaging job also blocks the rest of the deployment and test jobs. The |
| reason for this is that we must always keep packaging functionality working due to |
| requirement to support offline deployments. If packaging fails, the rest of the jobs are |
| aborted. |
| |
| As final step, deployment and testing are run in both online and offline modes. Online |
| mode ensures that whatever version of the stack or the tools that are pulled in during |
| the deployment works in a similar environment as upstream with no limitations. This is |
| especially important for moving to newer versions of stacks and/or tools. Offline mode |
| ensures that whatever that is packaged within the offline dependencies file can be used |
| to deploy and use the given version of the selected stack within an airgapped environment. |
| |
| Engine Core and Provisioner Changes |
| ----------------------------------- |
| |
| These jobs verify changes coming to |
| |
| * infra/engine |
| * infra/provisioner/bifrost |
| * infra/provisioner/heat |
| |
| repositories and only for master branch as the engine core and provisioners follow |
| trunk based development. |
| |
| Engine core and provisioner changes are verified as below. |
| |
| * static analysis: all the changes coming to these repositories are tested using |
| ansible-lint, yamllint, and shellcheck. In addition to these, docs verification |
| is also done for the changes that update documentation. But it is only run for the |
| changes coming to infra/engine repo as the documentation is hosted in this repo. |
| * packaging: Testing the packaging functionality. |
| * online deployment on libvirt instances: Testing the provisioning of the compute |
| resources using bifrost and deployment using the installer in online mode. |
| * online deployment on Heat instances: Testing the provisioning of the compute |
| resources using heat and deployment using the installer in online mode. |
| * offline deployment on libvirt instances: Testing the provisioning of the compute |
| resources using bifrost and deployment using the installer in offline mode using |
| the package produced in previous step. |
| * offline deployment on Heat instances: Testing the provisioning of the compute |
| resources using heat and deployment using the installer in offline mode using |
| the package produced in previous step. |
| |
| Please note that the default stack for this pipeline is Kubernetes and the default |
| scenario is k8-multus-plugins. |
| |
| Kubernetes Changes |
| ------------------ |
| |
| These jobs verify changes coming to |
| |
| * infra/stack/kubernetes |
| * infra/installer/kubespray |
| |
| repositories and all the active branches. |
| |
| * static analysis: all the changes coming to these repositories are tested using |
| using ansible-lint, yamllint, and shellcheck. |
| * packaging: Testing the packaging functionality. |
| * online deployment on libvirt instances: Testing the provisioning of the compute |
| resources using bifrost and deployment using the installer in online mode. |
| * online deployment on Heat instances: Testing the provisioning of the compute |
| resources using heat and deployment using the installer in online mode. |
| * offline deployment on libvirt instances: Testing the provisioning of the compute |
| resources using bifrost and deployment using the installer in offline mode using |
| the package produced in previous step. |
| * offline deployment on Heat instances: Testing the provisioning of the compute |
| resources using heat and deployment using the installer in offline mode using |
| the package produced in previous step. |
| |
| Please note that the default scenario is k8-multus-plugins. |
| |
| ONAP Changes |
| ------------ |
| |
| These jobs verify changes coming to |
| |
| * infra/stack/onap |
| * infra/installer/oom |
| |
| repositories and all the active branches. |
| |
| * static analysis: all the changes coming to these repositories are tested using |
| using ansible-lint, yamllint, and shellcheck. |
| * packaging: Testing the packaging functionality. |
| * online deployment on Heat instances: Testing the provisioning of the compute |
| resources using heat and deployment using the installer in online mode. |
| * offline deployment on Heat instances: Testing the provisioning of the compute |
| resources using heat and deployment using the installer in offline mode using |
| the package produced in previous step. |
| |
| Please note that the default scenario is onap-full-nofeature. |
| |
| Deployment and testing on libvirt resources provisioned using bifrost is not |
| tested as the baremetal deployments of ONAP is not enabled yet. |
| |
| OpenStack Changes |
| ----------------- |
| |
| These jobs verify changes coming to |
| |
| * infra/stack/openstack |
| * infra/installer/kolla |
| |
| repositories and all the active branches. |
| |
| * static analysis: all the changes coming to these repositories are tested using |
| using ansible-lint, yamllint, and shellcheck. |
| * packaging: Testing the packaging functionality. |
| * online deployment on libvirt instances: Testing the provisioning of the compute |
| resources using bifrost and deployment using the installer in online mode. |
| * offline deployment on libvirt instances: Testing the provisioning of the compute |
| resources using bifrost and deployment using the installer in offline mode using |
| the package produced in previous step. |
| |
| Please note that the default scenario is os-nosdn-nofeature. |
| |
| Deployment and testing on OpenStack resources provisioned using heat is not |
| tested as the cloud deployments of OpenStack is not supported. |
| |
| Releases |
| ======== |
| |
| TBD |
| |
| References |
| ========== |
| |
| TBD |