Fuel version 0 2012-2014, Mirantis December 29, 2014 Contents Table of contents Development Documentation 1 1 Fuel Architecture 1 Sequence Diagrams 3 Fuel Development Quick-Start 5 Fuel Development Examples 6 Fuel Development Environment 8 System Tests 12 Fuel Development Environment on Live Master Node 13 Nailgun 13 Contributing to Fuel Library 61 Using Fuel settings 66 Example module 67 Resource duplication and file conflicts 68 Puppet module containment 69 Puppet scope and variables 70 Where to find more information 71 Fuel Master Node Deployment over PXE 72 Health Check (OSTF) Contributor's Guide 76 User Guide 85 Devops Guide 85 Introduction 85 Installation 85 Configuration 87 Environment creation via Devops + Fuel_main 88 Important notes for Sahara and Murano tests 89 Run single OSTF tests several times 89 Fuel ISO build system 89 Quick start 89 Build system structure 90 Build targets 91 Customizing build process 91 Other options 92 Index Python Module Index 95 101 Table of contents Table of contents Development Documentation Fuel Architecture Good overview of Fuel architecture is represented on OpenStack wiki. You can find a detailed breakdown of how this works in the Sequence Diagrams. Master node is the main part of the Fuel project. It contains all the services needed for network provisioning of other managed nodes, installing an operating system, and then deploying OpenStack services to create a cloud environment. Nailgun is the most important service. It is a RESTful application written in Python that contains all the business logic of the system. A user can interact with it either using the Fuel Web interface or by the means of CLI utility. He can create a new environment, edit its settings, assign roles to the discovered nodes, and start the deployment process of the new OpenStack cluster. Nailgun stores all of its data in a PostgreSQL database. It contains the hardware configuration of all discovered managed nodes, the roles, environment settings, current deployment status and progress of running deployments. Managed nodes are discovered over PXE using a special bootstrap image and the PXE boot server located on the master node. The bootstrap image runs a special script called Nailgun agent. The agent nailgun-agent.rb collects the server's hardware information and submits it to Nailgun through the REST API. The deployment process is started by the user after he has configured a new environment. The Nailgun service creates a JSON data structure with the environment settings, its nodes and their roles and puts this file into the RabbitMQ queue. This message should be received by one of the worker processes who will actually deploy the environment. These processes are called Astute. 1 Table of contents The Astute workers are listening to the RabbitMQ queue and receives messages. They use the Astute library which implements all deployment actions. First, it starts the provisioning of the environment's nodes. Astute uses XML-RPC to set these nodes' configuration in Cobbler and then reboots the nodes using MCollective agent to let Cobbler install the base operating system. Cobbler is a deployment system that can control DHCP and TFTP services and use them to network boot the managed node and start the OS installer with the user-configured settings. Astute puts a special message into the RabbitMQ queue that contains the action that should be executed on the managed node. MCollective servers are started on all bootstrapped nodes and they constantly listen for these messages, when they receive a message, they run the required agent action with the given parameters. MCollective agents are just Ruby scripts with a set of procedures. These procedures are actions that the MCollective server can run when asked to. When the managed node's OS is installed, Astute can start the deployment of OpenStack services. First, it uploads the node's configuration to the /etc/astute.yaml file on node using the uploadfile agent. This file contains all the variables and settings that will be needed for the deployment. Next, Astute uses the puppetsync agent to synchronize Puppet modules and manifests. This agent runs an rsync process that connects to the rsyncd server on the Master node and downloads the latest version of Puppet modules and manifests. 2 Table of contents When the modules are synchronized, Astute can run the actual deployment by applying the main Puppet manifest site.pp. MCollective agent runs the Puppet process in the background using the daemonize tool. The command looks like this: daemonize puppet apply /etc/puppet/manifests/site.pp" Astute periodically polls the agent to check if the deployment has finished and reports the progress to Nailgun through its RabbitMQ queue. When started, Puppet reads the astute.yaml file content as a fact and then parses it into the $fuel_settings structure used to get all deployment settings. When the Puppet process exits either successfully or with an error, Astute gets the summary file from the node and reports the results to Nailgun. The user can always monitor both the progress and the results using Fuel Web interface or the CLI tool. Fuel installs the puppet-pull script. Developers can use it if they need to manually synchronize manifests from the Master node and run the Puppet process on node again. Astute also does some additional actions, depending on environment configuration, either before the deployment of after successful one. • Generates and uploads SSH keys that will be needed during deployment. • During network verification phase net_verify.py script. • Uploads CirrOS guest image into Glance after the deployment. • Updates /etc/hosts file on all nodes when new nodes are deployed. • Updates RadosGW map when Ceph nodes are deployed. Astute also uses MCollective agents when a node or the entire environment is being removed. It erases all boot sectors on the node and reboots it. The node will be network booted with the bootstrap image again, and will be ready to be used in a new environment. Sequence Diagrams OS Provisioning 3 Table of contents Networks Verification Details on Cluster Provisioning & Deployment (via Facter extension) 4 Table of contents Once deploy and provisioning messages are accepted by Astute, provisioning method is called. Provisioning part creates system in Cobbler and calls reboot over Cobbler. Then Astute uses MCollective direct addressing mode to check if all required nodes are available, include puppet agent on them. If some nodes are not yet ready, Astute waits for a few seconds and tries to request again. When nodes are booted in target OS, Astute uses upload_file MCollective plugin to push data to a special file /etc/astute.yaml on the target system. Data include role and all other variables needed for deployment. Then, Astute calls puppetd MCollective plugin to start deployment. Puppet is started on nodes. Accordingly, puppet agent starts its run. Modules contain facter extension, which runs before deployment. Extension reads data from /etc/astute.yaml placed by mcollective, and extends Facter data with it as a single fact, which is then parsed by parseyaml function to create $::fuel_settings data structure. This structure contains all variables as a single hash and supports embedding of other rich structures such as nodes hash or arrays. Case structure in running class chooses appropriate class to import, based on role and deployment_mode variables found in /etc/astute.yaml. Fuel Development Quick-Start If you are interested in contributing to Fuel or modifying Fuel for your own purposes, this short guide should get you pointed to all the information you need to get started. If you are new to contributing to OpenStack, read through the “How To Contribute” page on the OpenStack wiki. See: How to contribute. For this walk-through, let’s use the example of modifying an option to the “new environment wizard” in Fuel (example here: https://review.openstack.org/#/c/90687/1). This enhancement required modification to three files in the fuel-web repository: fuel-web/nailgun/static/i18n/translation.json fuel-web/nailgun/static/js/views/dialogs.js fuel-web/nailgun/static/templates/dialogs/create_cluster_wizard/storage.html In order to add, test and commit the code necessary to implement this feature, these steps were followed: 1. Create a Fuel development environment by following the instructions found here: Fuel Development Environment. 2. In your development environment, prepare your environment for Nailgun unit tests and Web UI tests by following the instructions found here: Nailgun Dev Environment. Be sure to run the tests noted in each section to ensure your environment confirms to a known good baseline. 3. Branch your fuel-web checkout (see Gerrit Workflow for more information on the gerrit workflow): cd fuel-web git fetch --all;git checkout -b vcenter-wizard-fix origin/master 4. Modify the necessary files (refer to Fuel Architecture to understand how the components of Fuel work together). 5. Test your Nailgun changes: cd fuel-web ./run_tests.sh ./run_tests.sh ./run_tests.sh ./run_tests.sh --no-lint-ui --no-webui --flake8 --lint-ui --webui 6. You should also test Nailgun in fake UI mode by following the steps found here: Running Nailgun in Fake Mode 7. When all tests pass you should commit your code, which will subject it to further testing via Jenkins and Fuel CI. Be sure to include a good commit message, guidelines can be found here: Git Commit Messages.: git commit -a git review 5 Table of contents 8. Frequently, the review process will suggest changes be made before your code can be merged. In that case, make your changes locally, test the changes, and then re-submit for review by following these steps: git commit -a --amend git review 9. Now that your code has been committed, you should change your Fuel ISO makefile to point to your specific commit. As noted in the Fuel Development documentation, when you build a Fuel ISO it pulls down the additional repositories rather than using your local repos. Even though you have a local clone of fuel-web holding the branch you just worked on, the build script will be pulling code from git for the sub-components (Nailgun, Astute, OSTF) based on the repository and commit specified in environment variables when calling “make iso”, or as found in config.mk. You will need to know the gerrit commit ID and patch number. For this example we are looking at https://review.openstack.org/#/c/90687/1 with the gerrit ID 90687, patch 1. In this instance, you would build the ISO with: cd fuel-main NAILGUN_GERRIT_COMMIT=refs/changes/32/90687/1 make iso 10 Once your ISO build is complete, you can test it. If you have access to hardware that can run the . KVM hypervisor, you can follow the instructions found in the Devops Guide to create a robust testing environment. Otherwise you can test the ISO with Virtualbox (the download link can be found at https://software.mirantis.com/) 11 Once your code has been merged, you can return your local repo to the master branch so you . can start fresh on your next commit by following these steps: cd fuel-web git remote update git checkout master git pull Fuel Development Examples This section provides examples of the Fuel development process. It builds on the information in the How to contribute document, and the Fuel Development Quick-Start Guide which illustrate the development process for a single Fuel component. These examples show how to manage development and integration of a more complicated example. Any new feature effort should start with the creation of a blueprint where implementation decisions and related commits are tracked. More information on launchpad blueprints can be found here: https://wiki.openstack.org/wiki/Blueprints. Understanding the Fuel architecture helps you understand which components any particular addition will impact. The following documents provide valuable information about the Fuel architecture, and the provisioning and deployment process: • Fuel architecture on the OpenStack wiki • Architecture section of Fuel documentation • Visual of provisioning tasks Adding Zabbix Role This section outlines the steps followed to add a new role to Fuel. In this case, monitoring service functionality was added by enabling the deployment of a Zabbix server configured to monitor an OpenStack environment deployed by Fuel. The monitoring server role was initially planned in this blueprint. Core Fuel developers provided feedback to small commits via Gerrit and IRC while the work was coming together. Ultimately the work was rolled up into two commits including over 23k lines of code, and these two commits were merged into fuel-web and fuel-library. Additions to Fuel-Web for Zabbix role 6 Table of contents In fuel-web, the Support for Zabbix commit added the additional role to Nailgun. The reader is urged to review this commit closely as a good example of where specific additions fit. In order to include this as an option in the Fuel deployment process, the following files were included in the commit for fuel-web: UI components: nailgun/static/i18n/translation.json nailgun/static/js/views/cluster_page_tabs/nodes_tab_screens/node_list_screen.js Testing additions: nailgun/nailgun/test/integration/test_cluster_changes_handler.py nailgun/nailgun/test/integration/test_orchestrator_serializer.py General Nailgun additions: nailgun/nailgun/errors/__init__.py nailgun/nailgun/fixtures/openstack.yaml nailgun/nailgun/network/manager.py nailgun/nailgun/orchestrator/deployment_serializers.py nailgun/nailgun/rpc/receiver.py nailgun/nailgun/settings.yaml nailgun/nailgun/task/task.py nailgun/nailgun/utils/zabbix.py Additions to Fuel-Library for Zabbix role In addition to the Nailgun additions, the related Puppet modules were added to the fuel-library repository. This Zabbix fuel-library integration commit included all the puppet files, many of which are brand new modules specifically for Zabbix, in addition to adjustments to the following files: deployment/puppet/openstack/manifests/logging.pp deployment/puppet/osnailyfacter/manifests/cluster_ha.pp deployment/puppet/osnailyfacter/manifests/cluster_simple.pp Once all these commits passed CI and had been reviewed by both community members and the Fuel PTLs, they were merged into master. Adding Hardware Support This section outlines the steps followed to add support for a Mellanox network card, which requires a kernel driver that is available in most Linux distributions but was not loaded by default. Adding support for other hardware would touch similar Fuel components, so this outline should provide a reasonable guide for contributors wishing to add support for new hardware to Fuel. It is important to keep in mind that the Fuel node discovery process works by providing a bootstrap image via PXE. Once the node boots with this image, a basic inventory of hardware information is gathered and sent back to the Fuel controller. If a node contains hardware requiring a unique kernel module, the bootstrap image must contain that module in order to detect the hardware during discovery. In this example, loading the module in the bootstrap image was enabled by adjusting the ISO makefile and specifying the appropriate requirements. Adding a hardware driver to bootstrap The Added bootstrap support to Mellanox commit shows how this is achieved by adding the modprobe call to load the driver specified in the requirements-rpm.txt file, requiring modification of only two files in the fuel-main repository: bootstrap/module.mk requirements-rpm.txt 7 Table of contents Note Any package specified in bootstrap building procedure must be listed in the requirements-rpm.txt file explicitly. The Fuel mirrors must be rebuilt by the OSCI team prior to merging requests like this one. Note Changes made to bootstrap do not affect package sets for target systems, so in case if you're adding support for NIC, for example, you have to add installation of all related packages to kickstart/preceed as well. The Adding OFED drivers installation commit shows the changes made to the preseed (for Ubuntu) and kickstart (for CentOS) files in the fuel-library repository: deployment/puppet/cobbler/manifests/snippets.pp deployment/puppet/cobbler/templates/kickstart/centos.ks.erb deployment/puppet/cobbler/templates/preseed/ubuntu-1204.preseed.erb deployment/puppet/cobbler/templates/snippets/centos_ofed_prereq_pkgs_if_enabled.erb deployment/puppet/cobbler/templates/snippets/ofed_install_with_sriov.erb deployment/puppet/cobbler/templates/snippets/ubuntu_packages.erb Though this example did not require it, if the hardware driver is required during the operating system installation, the installer images (debian-installer and anaconda) would also need to be repacked. For most installations though, ensuring the driver package is available during installation should be sufficient. Adding to Fuel package repositories If the addition will be committed back to the public Fuel codebase to benefit others, you will need to submit a bug in the Fuel project to request the package be added to the repositories. Let's look at this process step by step by the example of Add neutron-lbaas-agent package bug: • you create a bug in the Fuel project providing full description on the packages to be added, and assign it to the Fuel OSCI team • you create a request to add these packages to Fuel requirements-*.txt files Add all neutron packages to requirements You receive +1 vote from Fuel CI if these packages already exist on either Fuel internal mirrors or upstream mirrors for respective OS type (rpm/deb), or -1 vote in any other case. • if requested packages do not exist in the upstream OS distributive, OSCI team builds them and then places on internal Fuel mirrors • OSCI team rebuilds public Fuel mirrors with Add all neutron packages to requirements request • Add all neutron packages to requirements request is merged Note The package must include a license that complies with the Fedora project license requirements for binary firmware. See the Fedora Project licensing page for more information. Fuel Development Environment If you are modifying or augmenting the Fuel source code or if you need to build a Fuel ISO from the latest branch, you will need an environment with the necessary packages installed. This page lays out 8 Table of contents the steps you will need to follow in order to prepare the development environment, test the individual components of Fuel, and build the ISO which will be used to deploy your Fuel master node. The basic operating system for Fuel development is Ubuntu Linux. The setup instructions below assume Ubuntu 14.04 though most of them should be applicable to other Ubuntu and Debian versions, too. Each subsequent section below assumes that you have followed the steps described in all preceding sections. By the end of this document, you should be able to run and test all key components of Fuel, build the Fuel master node installation ISO, and generate documentation. Getting the Source Code Source code of OpenStack Fuel can be found on Stackforge. Follow these steps to clone the repositories for each of the Fuel components: apt-get install git git clone https://github.com/stackforge/fuel-main git clone https://github.com/stackforge/fuel-web git clone https://github.com/stackforge/fuel-astute git clone https://github.com/stackforge/fuel-ostf git clone https://github.com/stackforge/fuel-library git clone https://github.com/stackforge/fuel-docs Building the Fuel ISO The "fuel-main" repository is the only one required in order to build the Fuel ISO. The make script then downloads the additional components (Fuel Library, Nailgun, Astute and OSTF). Unless otherwise specified in the makefile, the master branch of each respective repo is used to build the ISO. The basic steps to build the Fuel ISO from trunk in an Ubuntu 14.04 environment are: apt-get install git git clone https://github.com/stackforge/fuel-main cd fuel-main ./prepare-build-env.sh make iso If you want to build an ISO using a specific commit or repository, you will need to modify the "Repos and versions" section in the config.mk file found in the fuel-main repo before executing "make iso". For example, this would build a Fuel ISO against the v5.0 tag of Fuel: # Repos and versions FUELLIB_COMMIT?=tags/5.0 NAILGUN_COMMIT?=tags/5.0 ASTUTE_COMMIT?=tags/5.0 OSTF_COMMIT?=tags/5.0 FUELLIB_REPO?=https://github.com/stackforge/fuel-library.git NAILGUN_REPO?=https://github.com/stackforge/fuel-web.git ASTUTE_REPO?=https://github.com/stackforge/fuel-astute.git OSTF_REPO?=https://github.com/stackforge/fuel-ostf.git To build an ISO image from custom gerrit patches on review, edit the "Gerrit URLs and commits" section of config.mk, e.g. for https://review.openstack.org/#/c/63732/8 (id:63732, patch:8) set: FUELLIB_GERRIT_COMMIT?=refs/changes/32/63732/8 If you are building Fuel from an older branch that does not contain the "prepare-build-env.sh" script, you can follow these steps to prepare your Fuel ISO build environment on Ubuntu 14.04: 1. ISO build process requires sudo permissions, allow yourself to run commands as root user without request for a password: echo "`whoami` ALL=(ALL) NOPASSWD: ALL" | sudo tee -a /etc/sudoers 9 Table of contents 2. Install software: sudo apt-get update sudo apt-get install apt-transport-https echo deb http://mirror.yandex.ru/mirrors/docker/ docker main | sudo tee /etc/apt/sources sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys 36A1D7869245C8950 sudo apt-get update sudo apt-get install lxc-docker sudo apt-get update sudo apt-get remove nodejs nodejs-legacy npm sudo apt-get install software-properties-common python-software-properties sudo add-apt-repository -y ppa:chris-lea/node.js sudo apt-get update sudo apt-get install build-essential make git ruby ruby-dev rubygems debootstrap creater python-setuptools yum yum-utils libmysqlclient-dev isomd5sum \ python-nose libvirt-bin python-ipaddr python-paramiko python-yaml \ python-pip kpartx extlinux unzip genisoimage nodejs multistrap \ lrzip python-daemon sudo gem install bundler -v 1.2.1 sudo gem install builder sudo pip install xmlbuilder jinja2 sudo npm install -g grunt-cli 3. If you haven't already done so, get the source code: git clone https://github.com/stackforge/fuel-main 4. Now you can build the Fuel ISO image: cd fuel-main make iso 5. If you encounter issues and need to rebase or start over: make clean make deep_clean #remove build/ directory #remove build/ and local_mirror/ Note In case you are using Virtualbox for building iso, please ensure that the build directory BUILD_DIR and LOCAL_MIRROR (see config.mk) both are OUT of the Virtualbox shared folder path Nailgun (Fuel-Web) Nailgun is the heart of Fuel project. It implements a REST API as well as deployment data management. It manages disk volume configuration data, network configuration data and any other environment specific data necessary for a successful deployment of OpenStack. It provides the required orchestration logic for provisioning and deployment of the OpenStack components and nodes in the right order. Nailgun uses a SQL database to store its data and an AMQP service to interact with workers. Requirements for preparing the nailgun development environment, along with information on how to modify and test nailgun can be found in the Nailgun Development Instructions document: Nailgun Development Instructions Astute Astute is the Fuel component that represents Nailgun's workers, and its function is to run actions according to the instructions provided from Nailgun. Astute provides a layer which encapsulates all the details about interaction with a variety of services such as Cobbler, Puppet, shell scripts, etc. and provides a universal asynchronous interface to those services. 10 Table of contents 1. Astute can be found in fuel-astute repository 2. Install Ruby dependencies: sudo apt-get install git curl curl -sSL https://get.rvm.io | bash -s stable source ~/.rvm/scripts/rvm rvm install 2.1 rvm use 2.1 git clone https://github.com/nulayer/raemon.git cd raemon git checkout b78eaae57c8e836b8018386dd96527b8d9971acc gem build raemon.gemspec gem install raemon-0.3.0.gem cd .. rm -Rf raemon 3. Install or update dependencies and run unit tests: cd fuel-astute ./run_tests.sh 4. (optional) Run Astute MCollective integration test (you'll need to have MCollective server running for this to work): cd fuel-astute bundle exec rspec spec/integration/mcollective_spec.rb Running Fuel Puppet Modules Unit Tests If you are modifying any puppet modules used by Fuel, or including additional modules, you can use the PuppetLabs RSpec Helper to run the unit tests for any individual puppet module. Follow these steps to install the RSpec Helper: 1. Install PuppetLabs RSpec Helper: cd ~ gem2deb puppetlabs_spec_helper sudo dpkg -i ruby-puppetlabs-spec-helper_0.4.1-1_all.deb gem2deb rspec-puppet sudo dpkg -i ruby-rspec-puppet_0.1.6-1_all.deb 2. Run unit tests for a Puppet module: cd fuel/deployment/puppet/module rake spec Installing Cobbler Install Cobbler from GitHub (it can't be installed from PyPi, and deb package in Ubuntu is outdated): cd ~ git clone git://github.com/cobbler/cobbler.git cd cobbler git checkout release24 sudo make install Building Documentation You should prepare your build environment before you can build this documentation. First you must install Java, using the appropriate procedure for your operating system. Java is needed to use PlantUML to automatically generate UML diagrams from the source. You can also use PlantUML Server for a quick preview of your diagrams and language documentation. Then you need to install all the packages required for creating of the Python virtual environment and dependencies installation. 11 Table of contents sudo apt-get install make postgresql postgresql-server-dev-9.1 sudo apt-get install python-dev python-pip python-virtualenv Now you can create the virtual environment and activate it. virtualenv fuel-web-venv . virtualenv/bin/activate And then install the dependencies. pip install ./shotgun pip install -r nailgun/test-requirements.txt Now you can look at the list of available formats and generate the one you need: cd docs make help make html There is a helper script build-docs.sh. It can perform all the required steps automatically. The script can build documentation in required format. Documentation build helper -o - Open generated documentation after build -c - Clear the build directory -n - Don't install any packages -f - Documentation format [html,signlehtml,latexpdf,pdf,epub] For example, if you want to build HTML documentation you can just use the following script, like this: ./build-docs.sh -f html -o It will create virtualenv, install the required dependencies and build the documentation in HTML format. It will also open the documentation with your default browser. If you don't want to install all the dependencies and you are not interested in building automatic API documentation there is an easy way to do it. First remove autodoc modules from extensions section of conf.py file in the docs directory. This section should be like this: extensions = [ 'rst2pdf.pdfbuilder', 'sphinxcontrib.plantuml', ] Then remove develop/api_doc.rst file and reference to it from develop.rst index. Now you can build documentation as usual using make command. This method can be useful if you want to make some corrections to text and see the results without building the entire environment. The only Python packages you need are Sphinx packages: Sphinx sphinxcontrib-actdiag sphinxcontrib-blockdiag sphinxcontrib-nwdiag sphinxcontrib-plantuml sphinxcontrib-seqdiag Just don't forget to rollback all these changes before you commit your corrections. System Tests To include documentation on system tests, SYSTEM_TESTS_PATH environment variable should be set before running sphinx-build or make. 12 Table of contents Fuel Development Environment on Live Master Node If you need to deploy your own developer version of FUEL on live Master Node, you will need to use the helper script, fuel-web/fuel_development/manage.py. The helper script configures development environment on masternode, deploys code or restores the production environment. Help information about manage.py can be found by running it with the '-h' parameter. Nailgun Developer Version on Live Master Node Configure the Nailgun development environment by following the instructions: Nailgun Dev Environment In your local fuel-web repository run: workon fuel cd fuel_development python manage.py -m MASTER.NODE.ADDRESS nailgun deploy Nailgun source code will be deployed, all required packages will be installed, required services will be reconfigured and restarted. After that, developer version of Nailgun can be accessed. For deploying Nailgun source code only without reconfiguring services run: python manage.py -m MASTER.NODE.ADDRESS nailgun deploy --synconly For restoring production version of Nailgun run: python manage.py -m MASTER.NODE.ADDRESS nailgun revert If you need to add a new python package or use another version of the python package, make appropriate changes in the nailgun/requirements.txt file and run: python manage.py -m MASTER.NODE.ADDRESS nailgun deploy Nailgun Nailgun Development Instructions Setting up Environment For information on how to get source code see Getting the Source Code. Preparing Development Environment Warning Nailgun requires Python 2.6. with development files. Please check installed Python version using python --version. If the version check does not match, you can use PPA (Ubuntu) or pyenv (Universal) For PPA: sudo add-apt-repository ppa:fkrull/deadsnakes sudo apt-get install python2.6 python2.6-dev 1. Nailgun can be found in fuel-web/nailgun 2. Install and configure PostgreSQL database: sudo apt-get install postgresql postgresql-server-dev-9.1 sudo -u postgres createuser -SDRP nailgun # enter password "nailgun" sudo -u postgres createdb nailgun 13 Table of contents 3. Install pip and development tools: sudo apt-get install python-dev python-pip 4. Install virtualenv. This is an optional step that increases flexibility when dealing with environment settings and package installation: sudo pip install virtualenv virtualenvwrapper source /usr/local/bin/virtualenvwrapper.sh # you can save this to .bashrc mkvirtualenv fuel # you can use any name instead of 'fuel' workon fuel # command selects the particular environment 5. Install Python dependencies. This section assumes that you use virtual environment. Otherwise, you must install all packages globally. You can install pip and use it to require all the other packages at once.: pip install ./shotgun # this fuel project is listed in setup.py requirements pip install --allow-all-external -r nailgun/test-requirements.txt 6. Create required folder for log files: sudo mkdir /var/log/nailgun sudo chown -R `whoami`.`whoami` /var/log/nailgun Setup for Nailgun Unit Tests 1. Nailgun unit tests use Tox for generating test environments. This means that you don't need to install all Python packages required for the project to run them, because Tox does this by itself. 2. First, create a virtualenv the way it's described in previous section. Then, install the Tox package: pip install tox 3. Run the Nailgun backend unit tests: ./run_tests.sh --no-lint-ui --no-webui 4. Run the Nailgun flake8 test: ./run_tests.sh --flake8 5. You can also run the same tests by hand, using tox itself: cd nailgun tox -epy26 -- -vv nailgun/test tox -epep8 6. Tox reuses the previously created environment. After making some changes with package dependencies, tox should be run with -r option to recreate existing virtualenvs: tox -r -epy26 -- -vv nailgun/test tox -r -epep8 Setup for Web UI Tests 1. Install NodeJS and JS dependencies: sudo apt-get remove nodejs nodejs-legacy sudo apt-get install software-properties-common sudo add-apt-repository ppa:chris-lea/node.js sudo apt-get update sudo apt-get install nodejs sudo npm install -g grunt-cli cd nailgun npm install 2. Install CasperJS: 14 Table of contents sudo npm install -g phantomjs cd ~ git clone git://github.com/n1k0/casperjs.git cd casperjs git checkout tags/1.0.0-RC4 sudo ln -sf `pwd`/bin/casperjs /usr/local/bin/casperjs 3. Run full Web UI test suite (this will wipe your Nailgun database in PostgreSQL): cd fuel-web ./run_tests.sh --lint-ui ./run_tests.sh --webui Running Nailgun in Fake Mode 1. Fetch JS dependencies: cd nailgun npm install grunt bower 2. Populate the database from fixtures: ./manage.py syncdb ./manage.py loaddefault # It loads all basic fixtures listed in settings.yaml ./manage.py loaddata nailgun/fixtures/sample_environment.json # Loads fake nodes 3. Start application in "fake" mode, when no real calls to orchestrator are performed: python manage.py run -p 8000 --fake-tasks | egrep --line-buffered -v '^$|HTTP' >> /var/l 4. (optional) You can also use --fake-tasks-amqp option if you want to make fake environment use real RabbitMQ instead of fake one: python manage.py run -p 8000 --fake-tasks-amqp | egrep --line-buffered -v '^$|HTTP' >> / 5. (optional) To create a compressed version of UI and put it into static_compressed dir: grunt build --static-dir=static_compressed Note: Diagnostic Snapshot is not available in a Fake mode. Running the Fuel System Tests For fuel-devops configuration info please refer to Devops Guide article. 1. Run the integration test: cd fuel-main make test-integration 2. To save time, you can execute individual test cases from the integration test suite like this (nice thing about TestAdminNode is that it takes you from nothing to a Fuel master with 9 blank nodes connected to 3 virtual networks): cd fuel-main export PYTHONPATH=$(pwd) export ENV_NAME=fuelweb export PUBLIC_FORWARD=nat export ISO_PATH=`pwd`/build/iso/fuelweb-centos-6.5-x86_64.iso ./fuelweb_tests/run_tests.py --group=test_cobbler_alive 3. The test harness creates a snapshot of all nodes called 'empty' before starting the tests, and creates a new snapshot if a test fails. You can revert to a specific snapshot with this command: dos.py revert --snapshot-name <snapshot_name> <env_name> 4. To fully reset your test environment, tell the Devops toolkit to erase it: 15 Table of contents dos.py list dos.py erase <env_name> Fuel UI Internationalization Guidelines Fuel UI internationalization is done using i18next library. Please read i18next documentation first. All translations are stored in nailgun/static/i18n/translation.json If you want to add new strings to the translations file, follow these rules: 1. Use words describing placement of strings like "button", "title", "summary", "description", "label" and place them at the end of the key (like "apply_button", "cluster_description", etc.). One-word strings may look better without any of these suffixes. 2. Do NOT use shortcuts ("bt" instead of "button", "descr" instead of "description", etc.) 3. Nest keys if it makes sense, for example, if there are a few values for statuses, etc. 4. If some keys are used in a few places (for example, in utils), move them to "common.*" namespace. 5. Use defaultValue ONLY with dynamically generated keys. Validating translations To search for absent and unnecessary translation keys you can perform the following steps: 1. Open terminal and cd to fuel-web/nailgun directory. 2. Run "grunt i18n:validate" to start the validation. If there are any mismatches, you'll see the list of mismatching keys. Grunt task "i18n:validate" has one optional argument - a comma-separated list of languages to compare with base English en-US translations. Run "grunt i18n:validate:zh-CN" to perform comparison only between English and Chinese keys. You can also run "grunt i18n:validate:zh-CN,ru-RU" to perform comparison between English-Chinese and English-Russian keys. Nailgun database migrations Nailgun uses Alembic (http://alembic.readthedocs.org/en/latest/) for database migrations, allowing access to all common Alembic commands through "python manage.py migrate" This command creates DB tables for Nailgun service: python manage.py syncdb This is done by applying one by one a number of database migration files, which are located in nailgun/nailgun/db/migration/alembic_migrations/versions. This command does not create corresponding DB tables unless you created another migration file or updated an existing one, even if you're making some changes in SQLAlchemy models or creating the new ones. A new migration file can be generated by running: python manage.py migrate revision -m "Revision message" --autogenerate There are two important points here: 1. This command always creates a "diff" between the current database state and the one described by your SQLAlchemy models, so you should always run "python manage.py syncdb" before this command. This prevents running the migrate command with an empty database, which would cause it to create all tables from scratch. 2. Some modifications may not be detected by "--autogenerate", which require manual addition to the migration file. For example, adding a new value to ENUM field is not detected. After creating a migration file, you can upgrade the database to a new state by using this command: python manage.py migrate upgrade +1 16 Table of contents To merge your migration with an existing migration file, you can just move lines of code from the "upgrade()" and "downgrade()" methods to the bottom of corresponding methods in previous migration file. As of this writing, the migration file is called "current.py". For all additional features and needs, you http://alembic.readthedocs.org/en/latest/tutorial.html may refer to Alembic documentation: Interacting with Nailgun using Shell Launching shell 17 Interaction 17 Objects approach 17 SQLAlchemy approach 18 Frequently Asked Questions 18 Launching shell Development shell for Nailgun can only be accessed inside its virtualenv, which can be activated by launching the following command: source /opt/nailgun/bin/activate After that, the shell is accessible through this command: python /opt/nailgun/bin/manage.py shell Its appearance depends on availability of ipython on current system. This package is not available by default on the master node but you can use the command above to run a default Python shell inside the Nailgun environment: Python 2.7.3 (default, Feb 27 2014, 19:58:35) [GCC 4.6.3] on linux2 Type "help", "copyright", "credits" or "license" for more information. (InteractiveConsole) >>> Interaction There are two ways user may interact with Nailgun object instances through shell: • Using Nailgun objects abstraction • Using raw SQLAlchemy queries IMPORTANT NOTE: Second way (which is equal to straightforward modifying objects in DB) should only be used if nothing else works. Objects approach Importing objects may look like this: >>> from nailgun import objects >>> objects.Release <class 'nailgun.objects.release.Release'> >>> objects.Cluster <class 'nailgun.objects.cluster.Cluster'> >>> objects.Node <class 'nailgun.objects.node.Node'> These are common abstractions around basic items Nailgun is dealing with. The reference on how to work with them can be found here: Objects Reference. 17 Table of contents These objects allow user to interact with items in DB on higher level, which includes all necessary business logic which is not executed then values in DB are changed by hands. For working examples continue to Frequently Asked Questions. SQLAlchemy approach Using raw SQLAlchemy models and queries allows user to modify objects through ORM, almost the same way it can be done through SQL CLI. First, you need to get a DB session and import models: >>> from nailgun.db import db >>> from nailgun.db.sqlalchemy import models >>> models.Release <class 'nailgun.db.sqlalchemy.models.release.Release'> >>> models.Cluster <class 'nailgun.db.sqlalchemy.models.cluster.Cluster'> >>> models.Node <class 'nailgun.db.sqlalchemy.models.node.Node'> and then get necessary instances from DB, modify them and commit current transaction: >>> node = db().query(models.Node).get(1) # getting object by ID >>> node <nailgun.db.sqlalchemy.models.node.Node object at 0x3451790> >>> node.status = 'error' >>> db().commit() You may refer to SQLAlchemy documentation to find some more info on how to do queries. Frequently Asked Questions As a first step in any case objects should be imported as is described here: Objects approach. Q: How can I change status for particular node? A: Just retrieve node by its ID and update it: >>> node = objects.Node.get_by_uid(1) >>> objects.Node.update(node, {"status": "ready"}) >>> objects.Node.save(node) Q: How can I remove node from cluster by hands? A: Get node by ID and call its method: >>> node = objects.Node.get_by_uid(1) >>> objects.Node.remove_from_cluster(node) >>> objects.Node.save(node) REST API Reference Releases API 19 Clusters API 21 Nodes API 25 Disks API 29 Network Configuration API 31 Notifications API 35 Tasks API 36 Logs API 38 Version API 40 18 Table of contents Releases API Handlers dealing with releases class nailgun.api.v1.handlers.release.ReleaseHandler Bases: nailgun.api.v1.handlers.base.SingleHandler URL: /api/releases/%obj_id%/ Release single handler single alias of Release DELETE (obj_id) Returns: Http: Empty string • 204 (object successfully deleted) • 404 (object not found in db) GET (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) PUT (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.release.ReleaseCollectionHandler Bases: nailgun.api.v1.handlers.base.CollectionHandler URL: /api/releases/ Release collection handler 19 Table of contents collection alias of ReleaseCollection GET () Returns: Http: Sorted releases' collection in JSON format • 200 (OK) POST () Returns: Http: JSONized REST object. • 201 (object successfully created) • 400 (invalid object data specified) • 409 (object with such parameters already exists) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.release.ReleaseNetworksHandler Bases: nailgun.api.v1.handlers.base.SingleHandler URL: /api/releases/%obj_id%/networks/ Release Handler for network metadata single alias of Release GET (obj_id) Read release networks metadata Returns: Http: Release networks metadata • 201 (object successfully created) • 400 (invalid object data specified) • 404 (release object not found) PUT (obj_id) Updates release networks metadata 20 Table of contents Returns: Http: Release networks metadata • 201 (object successfully created) • 400 (invalid object data specified) • 404 (release object not found) POST (obj_id) Creation of metadata disallowed Http: • 405 (method not supported) DELETE (obj_id) Deletion of metadata disallowed Http: • 405 (method not supported) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Clusters API Handlers dealing with clusters class nailgun.api.v1.handlers.cluster.ClusterHandler Bases: nailgun.api.v1.handlers.base.SingleHandler URL: /api/clusters/%obj_id%/ Cluster single handler single alias of Cluster DELETE (obj_id) Returns: Http: {} • 202 (cluster deletion process launched) • 400 (failed to execute cluster deletion process) • 404 (cluster not found in db) 21 Table of contents GET (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) PUT (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.cluster.ClusterCollectionHandler Bases: nailgun.api.v1.handlers.base.CollectionHandler URL: /api/clusters/ Cluster collection handler collection alias of ClusterCollection GET () Returns: Http: POST () Returns: Http: Collection of JSONized REST objects. • 200 (OK) JSONized REST object. • 201 (object successfully created) • 400 (invalid object data specified) • 409 (object with such parameters already exists) get_object_or_404 (obj, *args, **kwargs) 22 Table of contents Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.cluster.ClusterAttributesHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/clusters/%cluster_id%/attributes/ Cluster attributes handler GET (cluster_id) Returns: Http: JSONized Cluster attributes. • 200 (OK) • 404 (cluster not found in db) • 500 (cluster has no attributes) PUT (cluster_id) Returns: Http: JSONized Cluster attributes. • 200 (OK) • 400 (wrong attributes data specified) • 404 (cluster not found in db) • 500 (cluster has no attributes) PATCH (cluster_id) Returns: Http: JSONized Cluster attributes. • 200 (OK) • 400 (wrong attributes data specified) • 404 (cluster not found in db) • 500 (cluster has no attributes) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: 23 404 when not found Table of contents Returns: object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.cluster.ClusterAttributesDefaultsHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/clusters/%cluster_id%/attributes/defaults/ Cluster default attributes handler GET (cluster_id) Returns: Http: JSONized default Cluster attributes. • 200 (OK) • 404 (cluster not found in db) • 500 (cluster has no attributes) PUT (cluster_id) Returns: Http: JSONized Cluster attributes. • 200 (OK) • 400 (wrong attributes data specified) • 404 (cluster not found in db) • 500 (cluster has no attributes) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) 24 Table of contents Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.cluster.ClusterGeneratedData Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/clusters/%cluster_id%/generated/ Cluster generated data GET (cluster_id) Returns: Http: JSONized cluster generated data • 200 (OK) • 404 (cluster not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Nodes API Handlers dealing with nodes class nailgun.api.v1.handlers.node.NodeCollectionHandler Bases: nailgun.api.v1.handlers.base.CollectionHandler URL: /api/nodes/ Node collection handler collection alias of NodeCollection GET () May receive cluster_id parameter to filter list of nodes Returns: 25 Collection of JSONized Node objects. Table of contents Http: • 200 (OK) PUT () Returns: Http: Collection of JSONized Node objects. • 200 (nodes are successfully updated) • 400 (invalid nodes data specified) POST () Returns: Http: JSONized REST object. • 201 (object successfully created) • 400 (invalid object data specified) • 409 (object with such parameters already exists) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.node.NodeNICsHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/nodes/%node_id%/interfaces/ Node network interfaces handler GET (node_id) Returns: Http: Collection of JSONized Node interfaces. • 200 (OK) • 404 (node not found in db) PUT (node_id) Returns: Http: Collection of JSONized Node objects. • 200 (nodes are successfully updated) • 400 (invalid nodes data specified) 26 Table of contents get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.node.NodeCollectionNICsHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/nodes/interfaces/ Node collection network interfaces handler PUT () Returns: Http: Collection of JSONized Node objects. • 200 (nodes are successfully updated) • 400 (invalid nodes data specified) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.node.NodeNICsDefaultHandler 27 Table of contents Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/nodes/%node_id%/interfaces/default_assignment/ Node default network interfaces handler GET (node_id) Returns: Http: Collection of default JSONized interfaces for node. • 200 (OK) • 404 (node not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.node.NodeCollectionNICsDefaultHandler Bases: nailgun.api.v1.handlers.node.NodeNICsDefaultHandler URL: /api/nodes/interfaces/default_assignment/ Node collection default network interfaces handler GET () May receive cluster_id parameter to filter list of nodes Returns: Http: Collection of JSONized Nodes interfaces. • 200 (OK) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: 28 • model -- model object • ids -- list of ids 404 when not found list of object instances Table of contents classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.node.NodesAllocationStatsHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/nodes/allocation/stats/ Node allocation stats handler GET () Returns: Http: Total and unallocated nodes count. • 200 (OK) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Disks API Handlers dealing with disks class nailgun.api.v1.handlers.disks.NodeDisksHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/nodes/%node_id%/disks/ Node disks handler GET (node_id) Returns: Http: JSONized node disks. • 200 (OK) • 404 (node not found in db) PUT (node_id) 29 Table of contents Returns: Http: JSONized node disks. • 200 (OK) • 400 (invalid disks data specified) • 404 (node not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.disks.NodeDefaultsDisksHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/nodes/%node_id%/disks/defaults/ Node default disks handler GET (node_id) Returns: Http: JSONized node disks. • 200 (OK) • 404 (node or its attributes not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. 30 Table of contents Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.disks.NodeVolumesInformationHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/nodes/%node_id%/volumes/ Node volumes information handler GET (node_id) Returns: Http: JSONized volumes info for node. • 200 (OK) • 404 (node not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Network Configuration API Handlers dealing with network configurations class nailgun.api.v1.handlers.network_configuration.ProviderHandler Bases: nailgun.api.v1.handlers.base.BaseHandler Base class for network configuration handlers get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: • model -- model object • ids -- list of ids 31 Table of contents Http: Returns: 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.network_configuration.NovaNetworkConfigurationHandler Bases: nailgun.api.v1.handlers.network_configuration.ProviderHandler URL: /api/clusters/%cluster_id%/network_configuration/nova_network/ Network configuration handler GET (cluster_id) Returns: Http: JSONized network configuration for cluster. • 200 (OK) • 404 (cluster not found in db) PUT (cluster_id) Returns: Http: JSONized Task object. • 202 (network checking task created) • 404 (cluster not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.network_configuration.NeutronNetworkConfigurationHandler Bases: nailgun.api.v1.handlers.network_configuration.ProviderHandler URL: /api/clusters/%cluster_id%/network_configuration/neutron/ Neutron Network configuration handler GET (cluster_id) 32 Table of contents Returns: Http: JSONized network configuration for cluster. • 200 (OK) • 404 (cluster not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.network_configuration.NetworkConfigurationVerifyHandler Bases: nailgun.api.v1.handlers.network_configuration.ProviderHandler Network configuration verify handler base PUT (cluster_id) IMPORTANT: Returns: Http: this method should be rewritten to be more RESTful JSONized Task object. • 202 (network checking task failed) • 200 (network verification task started) • 404 (cluster not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) 33 Table of contents Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.network_configuration.NovaNetworkConfigurationVerifyHandler Bases: nailgun.api.v1.handlers.network_configuration.NetworkConfigurationVerifyHandler URL: /api/clusters/%cluster_id%/network_configuration/nova_network/verify/ Nova-Network configuration verify handler PUT (cluster_id) IMPORTANT: Returns: Http: this method should be rewritten to be more RESTful JSONized Task object. • 202 (network checking task failed) • 200 (network verification task started) • 404 (cluster not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.network_configuration.NeutronNetworkConfigurationVerifyH andler Bases: nailgun.api.v1.handlers.network_configuration.NetworkConfigurationVerifyHandler URL: /api/clusters/%cluster_id%/network_configuration/neutron/verify/ Neutron network configuration verify handler PUT (cluster_id) IMPORTANT: Returns: 34 this method should be rewritten to be more RESTful JSONized Task object. Table of contents Http: • 202 (network checking task failed) • 200 (network verification task started) • 404 (cluster not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Notifications API Handlers dealing with notifications class nailgun.api.v1.handlers.notifications.NotificationHandler Bases: nailgun.api.v1.handlers.base.SingleHandler URL: /api/notifications/%obj_id%/ Notification single handler DELETE (obj_id) Returns: Http: Empty string • 204 (object successfully deleted) • 404 (object not found in db) GET (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) PUT (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) get_object_or_404 (obj, *args, **kwargs) 35 Table of contents Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Tasks API class nailgun.api.v1.handlers.tasks.TaskHandler Bases: nailgun.api.v1.handlers.base.SingleHandler URL: /api/tasks/%obj_id%/ Task single handler DELETE (obj_id) Returns: Http: Empty string • 204 (object successfully deleted) • 404 (object not found in db) GET (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) PUT (obj_id) Returns: Http: JSONized REST object. • 200 (OK) • 404 (object not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects 36 Table of contents Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.tasks.TaskCollectionHandler Bases: nailgun.api.v1.handlers.base.CollectionHandler URL: /api/tasks/ Task collection handler GET () May receive cluster_id parameter to filter list of tasks Returns: Http: Collection of JSONized Task objects. • 200 (OK) • 404 (task not found in db) POST () Returns: Http: JSONized REST object. • 201 (object successfully created) • 400 (invalid object data specified) • 409 (object with such parameters already exists) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary 37 Table of contents Logs API Handlers dealing with logs class nailgun.api.v1.handlers.logs.LogEntryCollectionHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/logs/ Log entry collection handler GET () Receives following parameters: • date_before - get logs before this date • date_after - get logs after this date • source - source of logs • node - node id (for getting node logs) • level - log level (all levels showed by default) • to - number of entries • max_entries - max number of entries to load Returns: Collection of log entries, log file size and if there are new entries. Http: • 200 (OK) • 400 (invalid date_before value) • 400 (invalid date_after value) • 400 (invalid source value) • 400 (invalid node value) • 400 (invalid level value) • 400 (invalid to value) • 400 (invalid max_entries value) • 404 (log file not found) • 404 (log files dir not found) • 404 (node not found) • 500 (node has no assigned ip) • 500 (invalid regular expression in config) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) 38 Table of contents Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.logs.LogPackageHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/logs/package/ Log package handler PUT () Returns: Http: JSONized Task object. • 200 (task successfully executed) • 400 (failed to execute task) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.logs.LogSourceCollectionHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/logs/sources/ Log source collection handler GET () Returns: Http: Collection of log sources (from settings) • 200 (OK) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 39 404 when not found object instance Table of contents get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary class nailgun.api.v1.handlers.logs.LogSourceByNodeCollectionHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/logs/sources/nodes/%node_id%/ Log source by node collection handler GET (node_id) Returns: Http: Collection of log sources by node (from settings) • 200 (OK) • 404 (node not found in db) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Version API Product info handlers class nailgun.api.v1.handlers.version.VersionHandler Bases: nailgun.api.v1.handlers.base.BaseHandler URL: /api/version/ 40 Table of contents Version info handler GET () Returns: Http: FUEL/FUELWeb commit SHA, release version. • 200 (OK) get_object_or_404 (obj, *args, **kwargs) Get object instance by ID Http: Returns: 404 when not found object instance get_objects_list_or_404 (obj, ids) Get list of objects Parameters: Http: Returns: • model -- model object • ids -- list of ids 404 when not found list of object instances classmethod http (status_code, message='', headers=None) Raise an HTTP status code, as specified. Useful for returning status codes like 401 Unauthorized or 403 Forbidden. Parameters: • status_code -- the HTTP status code as an integer • message -- the message to send along, as a string • headers -- the headers to send along, as a dictionary Objects Reference Base Objects 41 Release-related Objects 44 Cluster-related Objects 46 Node-related Objects 49 Base Objects Base classes for objects and collections class nailgun.objects.base.NailgunObject Bases: object Base class for objects serializer Serializer class for object alias of BasicSerializer model = None SQLAlchemy model for object schema = {'properties': {}} JSON schema for object classmethod check_field (field) Check if field is described in object's JSON schema 41 Table of contents Parameters: Returns: Raises: field -- name of the field as string None errors.InvalidField classmethod get_by_uid (uid, fail_if_not_found=False, lock_for_update=False) Get instance by it's uid (PK in case of SQLAlchemy) Parameters: • uid -- uid of object • fail_if_not_found -- raise an exception if object is not found Returns: • lock_for_update -- lock returned object for update (DB mutex) instance of an object (model) classmethod create (data) Create object instance with specified parameters in DB Parameters: Returns: data -- dictionary of key-value pairs as object fields instance of an object (model) classmethod update (instance, data) Update existing instance with specified parameters Parameters: Returns: • instance -- object (model) instance • data -- dictionary of key-value pairs as object fields instance of an object (model) classmethod delete (instance) Delete object (model) instance Parameters: Returns: instance -- object (model) instance None classmethod save (instance=None) Save current changes for instance in DB. Current transaction will be commited (in case of SQLAlchemy). Parameters: Returns: instance -- object (model) instance None classmethod to_dict (instance, fields=None) Serialize instance to Python dict Parameters: Returns: • instance -- object (model) instance • fields -- exact fields to serialize serialized object (model) as dictionary classmethod to_json (instance, fields=None) Serialize instance to JSON Parameters: Returns: • instance -- object (model) instance • fields -- exact fields to serialize serialized object (model) as JSON string class nailgun.objects.base.NailgunCollection Bases: object Base class for object collections single Single object class alias of NailgunObject 42 Table of contents classmethod all () Get all instances of this object (model) Returns: iterable (SQLAlchemy query) classmethod order_by (iterable, order_by) Order given iterable by specified order_by. Parameters: order_by (tuple of strings or string) -- tuple of model fields names or single field name for ORDER BY criterion to SQLAlchemy query. If name starts with '-' desc ordering applies, else asc. classmethod filter_by (iterable, **kwargs) Filter given iterable by specified kwargs. In case if iterable=None filters all object instances Parameters: Returns: • iterable -- iterable (SQLAlchemy query) • order_by -- tuple of model fields names for ORDER BY criterion to SQLAlchemy query. If name starts with '-' desc ordering applies, else asc. filtered iterable (SQLAlchemy query) classmethod filter_by_not (iterable, **kwargs) Filter given iterable by specified kwargs with negation. In case of iterable is None filters all object instances. Parameters: Returns: iterable -- iterable (SQLAlchemy query) filtered iterable (SQLAlchemy query) classmethod lock_for_update (iterable) Use SELECT FOR UPDATE on a given iterable (query). In case if iterable=None returns all object instances Parameters: Returns: iterable -- iterable (SQLAlchemy query) filtered iterable (SQLAlchemy query) classmethod filter_by_list (iterable, field_name, list_of_values, order_by=()) Filter given iterable by list of list_of_values. In case if iterable=None filters all object instances Parameters: • iterable -- iterable (SQLAlchemy query) • field_name -- filtering field name Returns: • list_of_values -- list of values for objects filtration filtered iterable (SQLAlchemy query) classmethod filter_by_id_list (iterable, uid_list) Filter given iterable by list of uids. In case if iterable=None filters all object instances Parameters: Returns: • iterable -- iterable (SQLAlchemy query) • uid_list -- list of uids for objects filtered iterable (SQLAlchemy query) classmethod eager_base (iterable, options) Eager load linked object instances (SQLAlchemy FKs). In case if iterable=None applies to all object instances Parameters: Returns: • iterable -- iterable (SQLAlchemy query) • options -- list of sqlalchemy eagerload types iterable (SQLAlchemy query) classmethod eager (iterable, fields) Eager load linked object instances (SQLAlchemy FKs). By default joinedload will be applied to every field. If you want to use custom eagerload method - use eager_base In case if iterable=None applies to all object instances 43 Table of contents Parameters: Returns: • iterable -- iterable (SQLAlchemy query) • fields -- list of links (model FKs) to eagerload iterable (SQLAlchemy query) classmethod to_list (iterable=None, fields=None) Serialize iterable to list of dicts In case if iterable=None serializes all object instances Parameters: Returns: • iterable -- iterable (SQLAlchemy query) • fields -- exact fields to serialize collection of objects as a list of dicts classmethod to_json (iterable=None, fields=None) Serialize iterable to JSON In case if iterable=None serializes all object instances Parameters: Returns: • iterable -- iterable (SQLAlchemy query) • fields -- exact fields to serialize collection of objects as a JSON string classmethod create (data) Create object instance with specified parameters in DB Parameters: Returns: data -- dictionary of key-value pairs as object fields instance of an object (model) nailgun.objects.base.and_ (*clauses) Produce a conjunction of expressions joined by AND. E.g.: from sqlalchemy import and_ stmt = select([users_table]).where( and_( users_table.c.name == 'wendy', users_table.c.enrolled == True ) ) The and_() conjunction is also available using the Python & operator (though note that compound expressions need to be parenthesized in order to function with Python operator precedence behavior): stmt = select([users_table]).where( (users_table.c.name == 'wendy') & (users_table.c.enrolled == True) ) The and_() operation is also implicit in some cases; the Select.where() method for example can be invoked multiple times against a statement, which will have the effect of each clause being combined using and_(): stmt = select([users_table]).\ where(users_table.c.name == 'wendy').\ where(users_table.c.enrolled == True) Seealso or_() Release-related Objects 44 Table of contents Release object and collection class nailgun.objects.release.ReleaseOrchestratorData Bases: nailgun.objects.base.NailgunObject ReleaseOrchestratorData object model SQLAlchemy model alias of ReleaseOrchestratorData serializer Serializer for ReleaseOrchestratorData alias of ReleaseOrchestratorDataSerializer schema = {'description': 'Serialized ReleaseOrchestratorData object', 'title': 'ReleaseOrchestratorData', 'required': ['release_id'], '$schema': 'http://json-schema.org/draft-04/schema#', 'type': 'object', 'properties': {'puppet_manifests_source': {'type': 'string'}, 'repo_metadata': {'type': 'object'}, 'release_id': {'type': 'number'}, 'id': {'type': 'number'}, 'puppet_modules_source': {'type': 'string'}}} JSON schema class nailgun.objects.release.Release Bases: nailgun.objects.base.NailgunObject Release object model SQLAlchemy model for Release alias of Release serializer Serializer for Release alias of ReleaseSerializer schema = {'description': 'Serialized Release object', 'title': 'Release', 'required': ['name', 'operating_system'], '$schema': 'http://json-schema.org/draft-04/schema#', 'type': 'object', 'properties': {'roles': {'type': 'array'}, 'operating_system': {'type': 'string'}, 'name': {'type': 'string'}, 'networks_metadata': {'type': 'array'}, 'description': {'type': 'string'}, 'volumes_metadata': {'type': 'object'}, 'wizard_metadata': {'type': 'object'}, 'state': {'enum': ['not_available', 'downloading', 'error', 'available'], 'type': 'string'}, 'version': {'type': 'string'}, 'roles_metadata': {'type': 'object'}, 'modes_metadata': {'type': 'object'}, 'is_deployable': {'type': 'boolean'}, 'clusters': {'type': 'array'}, 'id': {'type': 'number'}, 'attributes_metadata': {'type': 'object'}, 'can_update_from_versions': {'type': 'array'}}} Release JSON schema classmethod create (data) Create Release instance with specified parameters in DB. Corresponding roles are created in DB using names specified in "roles" field. See update_roles() Parameters: Returns: data -- dictionary of key-value pairs as object fields Release instance classmethod update (instance, data) Update existing Release instance with specified parameters. Corresponding roles are updated in DB using names specified in "roles" field. See update_roles() Parameters: Returns: • instance -- Release instance • data -- dictionary of key-value pairs as object fields Release instance classmethod update_roles (instance, roles) Update existing Release instance with specified roles. Previous ones are deleted. 45 Table of contents IMPORTANT NOTE: attempting to remove roles that are already assigned to nodes will lead to an Exception. Parameters: Returns: • instance -- Release instance • roles -- list of new roles names None classmethod is_deployable (instance) Returns whether a given release deployable or not. Parameters: Returns: instance -- a Release instance True if a given release is deployable; otherwise - False class nailgun.objects.release.ReleaseCollection Bases: nailgun.objects.base.NailgunCollection Release collection single Single Release object class alias of Release Cluster-related Objects Cluster-related objects and collections class nailgun.objects.cluster.Attributes Bases: nailgun.objects.base.NailgunObject Cluster attributes object model SQLAlchemy model for Cluster attributes alias of Attributes classmethod generate_fields (instance) Generate field values for Cluster attributes using generators. Parameters: Returns: instance -- Attributes instance None classmethod merged_attrs (instance) Generates merged dict which includes generated Cluster attributes recursively updated by new values from editable attributes. Parameters: Returns: instance -- Attributes instance dict of merged attributes classmethod merged_attrs_values (instance) Transforms raw dict of attributes returned by merged_attrs() into dict of facts for sending to orchestrator. Parameters: Returns: instance -- Attributes instance dict of merged attributes class nailgun.objects.cluster.Cluster Bases: nailgun.objects.base.NailgunObject Cluster object model SQLAlchemy model for Cluster alias of Cluster serializer 46 Table of contents Serializer for Cluster alias of ClusterSerializer schema = {'$schema': 'http://json-schema.org/draft-04/schema#', 'type': 'object', 'description': 'Serialized Cluster object', 'properties': {'status': {'enum': ['new', 'deployment', 'stopped', 'operational', 'error', 'remove', 'update', 'update_error'], 'type': 'string'}, 'release_id': {'type': 'number'}, 'replaced_provisioning_info': {'type': 'object'}, 'replaced_deployment_info': {'type': 'object'}, 'fuel_version': {'type': 'string'}, 'id': {'type': 'number'}, 'is_customized': {'type': 'boolean'}, 'name': {'type': 'string'}, 'net_provider': {'enum': ['nova_network', 'neutron'], 'type': 'string'}, 'mode': {'enum': ['multinode', 'ha_full', 'ha_compact'], 'type': 'string'}, 'grouping': {'enum': ['roles', 'hardware', 'both'], 'type': 'string'}, 'pending_release_id': {'type': 'number'}}, 'title': 'Cluster'} Cluster JSON schema classmethod create (data) Create Cluster instance with specified parameters in DB. This includes: • creating Cluster attributes and generating default values (see create_attributes()) • creating NetworkGroups for Cluster • adding default pending changes (see add_pending_changes()) • if "nodes" are specified in data then they are added to Cluster (see update_nodes()) Parameters: data -- dictionary of key-value pairs as object fields Returns: Cluster instance classmethod create_attributes (instance) Create attributes for current Cluster instance and generate default values for them (see Attributes.generate_fields()) Parameters: Returns: instance -- Cluster instance None classmethod get_default_editable_attributes (instance) Get editable attributes from release metadata Parameters: Returns: instance -- Cluster instance Dict object classmethod get_attributes (instance) Get attributes for current Cluster instance Parameters: Returns: instance -- Cluster instance Attributes instance classmethod get_network_manager (instance=None) Get network manager for Cluster instance. If instance is None then the default NetworkManager is returned Parameters: Returns: instance -- Cluster instance NetworkManager/NovaNetworkManager/NeutronManager classmethod add_pending_changes (instance, changes_type, node_id=None) Add pending changes for current Cluster. If node_id is specified then links created changes with node. Parameters: • instance -- Cluster instance • changes_type -- name of changes to add Returns: • node_id -- node id for changes None classmethod clear_pending_changes (instance, node_id=None) 47 Table of contents Clear pending changes for current Cluster. If node_id is specified then only clears changes connected to this node. Parameters: Returns: • instance -- Cluster instance • node_id -- node id for changes None classmethod update (instance, data) Update Cluster object instance with specified parameters in DB. If "nodes" are specified in data then they will replace existing ones (see update_nodes()) Parameters: Returns: • instance -- Cluster instance • data -- dictionary of key-value pairs as object fields Cluster instance classmethod update_nodes (instance, nodes_ids) Update Cluster nodes by specified node IDs. Nodes with specified IDs will replace existing ones in Cluster Parameters: Returns: • instance -- Cluster instance • nodes_ids -- list of nodes ids None classmethod get_ifaces_for_network_in_cluster (instance, net) Method for receiving node_id:iface pairs for all nodes in specific cluster Parameters: Returns: • instance -- Cluster instance • net (str) -- Nailgun specific network name List of node_id, iface pairs for all nodes in cluster. classmethod should_assign_public_to_all_nodes (instance) Determine whether Public network is to be assigned to all nodes in this cluster. Parameters: Returns: instance -- cluster instance True when Public network is to be assigned to all nodes classmethod set_primary_role (intance, nodes, role_name) Method for assigning primary attribute for specific role. - verify that there is no primary attribute of specific role assigned to cluster nodes with this role in role list or pending role list, and this node is not marked for deletion - if there is no primary role assigned, filter nodes which have current role in roles_list or pending_role_list - if there is nodes with ready state - they should have higher priority - if role was in primary_role_list - change primary attribute for that association, same for role_list, this is required because deployment_serializer used by cli to generate deployment info Parameters: • instance -- Cluster db objects • nodes -- list of Node db objects • role_name -- string with known role name classmethod set_primary_roles (instance, nodes) Idempotent method for assignment of all primary attribute for all roles that requires it. To mark role as primary add has_primary: true attribute to release Parameters: • instance -- Cluster db object • nodes -- list of Node db objects class nailgun.objects.cluster.ClusterCollection Bases: nailgun.objects.base.NailgunCollection 48 Table of contents Cluster collection single Single Cluster object class alias of Cluster nailgun.objects.cluster.or_ (*clauses) Produce a conjunction of expressions joined by OR. E.g.: from sqlalchemy import or_ stmt = select([users_table]).where( or_( users_table.c.name == 'wendy', users_table.c.name == 'jack' ) ) The or_() conjunction is also available using the Python | operator (though note that compound expressions need to be parenthesized in order to function with Python operator precedence behavior): stmt = select([users_table]).where( (users_table.c.name == 'wendy') | (users_table.c.name == 'jack') ) Seealso and_() Node-related Objects Node-related objects and collections class nailgun.objects.node.Node Bases: nailgun.objects.base.NailgunObject Node object model SQLAlchemy model for Node alias of Node serializer Serializer for Node alias of NodeSerializer schema = {'$schema': 'http://json-schema.org/draft-04/schema#', 'type': 'object', 'description': 'Serialized Node object', 'properties': {'status': {'enum': ['ready', 'discover', 'provisioning', 'provisioned', 'deploying', 'error'], 'type': 'string'}, 'os_platform': {'type': 'string'}, 'name': {'type': 'string'}, 'roles': {'type': 'array'}, 'pending_roles': {'type': 'array'}, 'agent_checksum': {'type': 'string'}, 'error_type': {'enum': ['deploy', 'provision', 'deletion'], 'type': 'string'}, 'pending_addition': {'type': 'boolean'}, 'fqdn': {'type': 'string'}, 'error_msg': {'type': 'string'}, 'platform_name': {'type': 'string'}, 'kernel_params': {'type': 'string'}, 'mac': {'type': 'string'}, 'meta': {'type': 'object'}, 'cluster_id': {'type': 'number'}, 'online': {'type': 'boolean'}, 'progress': {'type': 'number'}, 'pending_deletion': {'type': 'boolean'}, 'group_id': {'type': 'number'}, 'id': {'type': 'number'}, 'manufacturer': {'type': 'string'}}, 'title': 'Node'} Node JSON schema classmethod get_by_mac_or_uid (mac=None, node_uid=None) 49 Table of contents Get Node instance by MAC or ID. Parameters: Returns: • mac -- MAC address as string • node_uid -- Node ID Node instance classmethod get_by_meta (meta) Search for instance using mac, node id or interfaces Parameters: Returns: meta -- dict with nodes metadata Node instance classmethod search_by_interfaces (interfaces) Search for instance using MACs on interfaces Parameters: Returns: interfaces -- dict of Node interfaces Node instance classmethod should_have_public (instance) Determine whether this node has Public network. Parameters: Returns: instance -- Node DB instance True when node has Public network classmethod create (data) Create Node instance with specified parameters in DB. This includes: • generating its name by MAC (if name is not specified in data) • adding node to Cluster (if cluster_id is not None in data) (see add_into_cluster()) with specified roles (see update_roles() and update_pending_roles()) • creating interfaces for Node in DB (see update_interfaces()) • creating default Node attributes (see create_attributes()) • creating default volumes allocation for Node (see update_volumes()) • creating Notification about newly discovered Node (see create_discover_notification()) Parameters: data -- dictionary of key-value pairs as object fields Returns: Node instance classmethod create_attributes (instance) Create attributes for Node instance Parameters: Returns: instance -- Node instance NodeAttributes instance classmethod update_interfaces (instance) Update interfaces for Node instance get_network_manager()) Parameters: Returns: using Cluster network manager (see instance -- Node instance None classmethod set_volumes (instance, volumes_data) Set volumes for Node instance from JSON data. Adds pending "disks" changes for Cluster which Node belongs to Parameters: Returns: • instance -- Node instance • volumes_data -- JSON with new volumes data None classmethod update_volumes (instance) 50 Table of contents Update volumes for Node instance. Adds pending "disks" changes for Cluster which Node belongs to Parameters: Returns: instance -- Node instance None classmethod create_discover_notification (instance) Create notification about discovering new Node Parameters: Returns: instance -- Node instance None classmethod update (instance, data) Update Node instance with specified parameters in DB. This includes: • adding node to Cluster (if cluster_id is not None in data) (see add_into_cluster()) • updating roles for Node update_pending_roles()) if it belongs to Cluster (see update_roles() and • removing node from Cluster (if cluster_id is None in data) (see remove_from_cluster()) • updating interfaces for Node in DB (see update_interfaces()) • creating default Node attributes (see create_attributes()) • updating volumes allocation for Node using Cluster's update_volumes()) Parameters: data -- dictionary of key-value pairs as object fields Returns: Node instance Release metadata classmethod reset_to_discover (instance) Flush database objects which is not consistent with actual node configuration in the event of resetting node to discover state Parameters: Returns: instance -- Node database object None classmethod update_by_agent (instance, data) Update Node instance with some specific cases for agent. • don't update provisioning or error state back to discover • don't update volume information if disks arrays is empty Parameters: data -- dictionary of key-value pairs as object fields Returns: Node instance classmethod update_roles (instance, new_roles) Update roles for Node instance. Logs an error if node doesn't belong to Cluster Parameters: Returns: • instance -- Node instance • new_roles -- list of new role names None classmethod update_pending_roles (instance, new_pending_roles) Update pending_roles for Node instance. Logs an error if node doesn't belong to Cluster Parameters: Returns: • instance -- Node instance • new_pending_roles -- list of new pending role names None classmethod add_into_cluster (instance, cluster_id) Adds Node to Cluster by its ID. Also assigns networks by default for Node. 51 (see Table of contents Parameters: Returns: • instance -- Node instance • cluster_id -- Cluster ID None classmethod add_pending_change (instance, change) Add pending change into Cluster. Parameters: Returns: • instance -- Node instance • change -- string value of cluster change None classmethod get_network_manager (instance=None) Get network manager for Node instance. If instance is None then default NetworkManager is returned Parameters: Returns: • instance -- Node instance • cluster_id -- Cluster ID None classmethod remove_from_cluster (instance) Remove Node from Cluster. Also drops networks assignment for Node and clears both roles and pending roles Parameters: Returns: instance -- Node instance None classmethod move_roles_to_pending_roles (instance) Move roles to pending_roles classmethod get_kernel_params (instance) Return cluster kernel_params if they wasnot replaced by custom params. class nailgun.objects.node.NodeCollection Bases: nailgun.objects.base.NailgunCollection Node collection single Single Node object class alias of Node classmethod eager_nodes_handlers (iterable) Eager load objects instances that is used in nodes handler. Parameters: Returns: iterable -- iterable (SQLAlchemy query) iterable (SQLAlchemy query) classmethod prepare_for_deployment (instances) Prepare environment for deployment, assign management, public, storage ips classmethod prepare_for_provisioning (instances) Prepare environment for provisioning, update fqdns, assign admin IPs classmethod lock_nodes (instances) Locking nodes instances, fetched before, but required to be locked :param instances: list of nodes :return: list of locked nodes Managing UI Dependencies The UI has 2 types of dependencies: managed by NPM (run on node.js) and managed by Bower (run in browser). 52 Table of contents Managing NPM Packages NPM packages such as grunt, bower and others are used in a development environment only. Used NPM packages are listed in the devDependencies section of a package.json file. To install all required packages, run: npm install To use grunt you also need to install the grunt-cli package globally: sudo npm install -g grunt-cli To add a new package, it is not enough just to add a new entry to a package.json file because npm-shrinkwrap is used to lock down package versions. First you need to install the clingwrap package globally: sudo npm install -g clingwrap Then you need to remove the existing npm-shrinkwrap.json file: rm npm-shrinkwrap.json Then make required changes to a package.json file and run: npm install to remove old packages and install new ones. Then regenerate npm-shrinkwrap.json by running: npm shrinkwrap --dev clingwrap npmbegone Managing Bower Packages Bower is used to download libraries that run in browser. To add a new package, just add an entry to dependencies section of a bower.json file and run: grunt bower to download it. The new package will be placed in the nailgun/static/js/libs/bower/ directory. If the package contains more than one JS file, you must add a new entry to the exportsOverride section with a path to the appropriate file, in order to prevent unwanted JS files from appearing in the final UI build. If a library does not exist in the nailgun/static/js/libs/custom/ directory. bower repository, it should be placed in the Code testing policy When writing tests, please note the following rules: 1. Each code change MUST be covered with tests. The test for specific code change must fail if that change to code is reverted, i.e. the test must really cover the code change and not the general case. Bug fixes should have tests for failing case. 2. The tests MUST be in the same patchset with the code changes. 3. It's permitted not to write tests in extreme cases. The extreme cases are: • hot-fix / bug-fix with Critical status. • patching during Feature Freeze (FF) or Hard Code Freeze (HCF). In this case, request for writing tests should be reported as a bug with technical-debt tag. It has to be related to the bug which was fixed with a patchset that didn't have the tests included. 4. Before writing tests please consider which type(s) of testing is suitable for the unit/module you're covering. 5. Test coverage should not be decreased. 53 Table of contents 6. Nailgun application can be sliced up to tree layers (Presentation, Object, Model). Consider usage of the unit testing if it is performed within one of the layers or implementing mock objects is not complicated. 7. The tests have to be isolated. The order and count of executions must not influence test results. 8. Tests must be repetitive and must always pass regardless of how many times they are run. 9. Parametrize tests to avoid testing many times the same behaviour but with different data. This gives an additional flexibility in the methods' usage. 10 Follow DRY principle in tests code. If common code parts are present, please extract them to a . separate method/class. 11 Unit tests are grouped by namespaces as corresponding unit. For instance, the unit is located at: . nailgun/db/dl_detector.py, corresponding test would be placed in nailgun/test/unit/nailgun.db/test_dl_detector.py 12 Integration tests are grouped at the discretion of the developer. . 13 Consider implementing performance tests for the cases: . • new handler is added which depends on number of resources in the database. • new logic is added which parses/operates on elements like nodes. Nailgun Customization Instructions Creating Partitions on Nodes Fuel generates Anaconda Kickstart scripts for Red Hat based systems and preseed files for Ubuntu to partition block devices on new nodes. Most of the work is done in the pmanager.py Cobbler script using the data from the "ks_spaces" variable generated by the Nailgun VolumeManager class based on the volumes metadata defined in the openstack.yaml release fixture. Volumes are created following best practices for OpenStack and other components. Following volume types are supported: vg an LVM volume group that can contain one or more volumes with type set to "lv" partition plain non-LVM partition raid a Linux software RAID-1 array of LVM volumes Typical slave node will always have an "os" volume group and one or more volumes of other types, depending on the roles assigned to that node and the role-to-volumes mapping defined in the "volumes_roles_mapping" section of openstack.yaml. There are a few different ways to add another volume to a slave node: 1. Add a new logical volume definition to one of the existing LVM volume groups. 2. Create a new volume group containing your new logical volumes. 3. Create a new plain partition. Adding an LV to an Existing Volume Group If you need to add a new volume to an existing volume group, for example "os", your volume definition in openstack.yaml might look like this: - id: "os" type: "vg" min_size: {generator: "calc_min_os_size"} label: "Base System" 54 Table of contents volumes: - mount: "/" type: "lv" name: "root" size: {generator: "calc_total_root_vg"} file_system: "ext4" - mount: "swap" type: "lv" name: "swap" size: {generator: "calc_swap_size"} file_system: "swap" - mount: "/mnt/some/path" type: "lv" name: "LOGICAL_VOLUME_NAME" size: generator: "calc_LOGICAL_VOLUME_size" generator_args: ["arg1", "arg2"] file_system: "ext4" Make sure that your logical volume name ("LOGICAL_VOLUME_NAME" in the example above) is not the same as the volume group name ("os"), and refer to current version of openstack.yaml for up-to-date format. Adding Generators to Nailgun VolumeManager The "size" field in a volume definition can be defined either directly as an integer number in megabytes, or indirectly via a so called generator. Generator is a Python lambda that can be called to calculate logical volume size dynamically. In the json example above size is defined as a dictionary with two keys: "generator" is the name of the generator lambda and "generator_args" is the list of arguments that will be passed to the generator lambda. There is the method in the VolumeManager class where generators are defined. New volume generator 'NEW_GENERATOR_TO_CALCULATE_SIZ' needs to be added in the generators dictionary inside this method. class VolumeManager(object): ... def call_generator(self, generator, *args): generators = { ... 'NEW_GENERATOR_TO_CALCULATE_SIZE': lambda: 1000, ... } Creating a New Volume Group Another way to add new volume to slave nodes is to create new volume group and to define one or more logical volume inside the volume group definition: - id: "NEW_VOLUME_GROUP_NAME" type: "vg" min_size: {generator: "calc_NEW_VOLUME_NAME_size"} label: "Label for NEW VOLUME GROUP as it will be shown on UI" volumes: - mount: "/path/to/mount/point" type: "lv" name: "LOGICAL_VOLUME_NAME" size: generator: "another_generator_to_calc_LOGICAL_VOLUME_size" generator_args: ["arg"] file_system: "xfs" 55 Table of contents Creating a New Plain Partition Some node roles may be incompatible with LVM and would require plain partitions. If that's the case, you may have to define a standalone volume with type "partition" instead of "vg": - id: "NEW_PARTITION_NAME" type: "partition" min_size: {generator: "calc_NEW_PARTITION_NAME_size"} label: "Label for NEW PARTITION as it will be shown on UI" mount: "none" disk_label: "LABEL" file_system: "xfs" Note how you can set mount point to "none" and define a disk label to identify the partition instead. Its only possible to set a disk label on a formatted portition, so you have to set "file_system" parameter to use disk labels. Updating the Node Role to Volumes Mapping Unlike a new logical volume added to a pre-existing logical volume group, a new logical volume group or partition will not be allocated on the node unless it is included in the role-to-volumes mapping corresponding to one of the node's roles, like this: volumes_roles_mapping: controller: - {allocate_size: "min", id: "os"} - {allocate_size: "all", id: "image"} compute: ... • controller - is a role for which partitioning information is given • id - is id of volume group or plain partition • allocate_size - can be "min" or "all" * min - allocate volume with minimal size * all - allocate all free space for volume, if several volumes have this key then free space will be allocated equally Setting Volume Parameters from Nailgun Settings In addition to VolumeManager generators, it is also possible to define sizes or whatever you want in the nailgun configuration file (/etc/nailgun/settings.yaml). All fixture files are templated using Jinja2 templating engine just before being loaded into nailgun database. For example, we can define mount point for a new volume as follows: "mount": "{{settings.NEW_LOGICAL_VOLUME_MOUNT_POINT}}" Of course, NEW_LOGICAL_VOLUME_MOUNT_POINT must be defined in the settings file. Nailgun is the core of FuelWeb. To allow an enterprise features be easily connected, and open source commity to extend it as well, Nailgun must have simple, very well defined and documented core, with the great pluggable capabilities. Reliability All software contains bugs and may fail, and Nailgun is not an exception of this rule. In reality, it is not possible to cover all failure scenarios, even to come close to 100%. The question is how we can design the system to avoid bugs in one module causing the damage of the whole system. Example from the Nailgun's past: Agent collected hardware information, include current_speed param on the interfaces. One of the interfaces had current_speed=0. At the registration attempt, Nailgun's validator checked that current_speed > 0, and validator raised an exception InvalidData, which declined node discovery. current_speed is one of the attibutes which we can easily skip, it is not even used for deployment in any way at the moment and used only for the information provided to the user. But it prevented node discovery, and it made the server unusable. 56 Table of contents Another example. Due to the coincedence of bug and wrong metadata of one of the nodes, GET request on that node would return 500 Internal Server Error. Looks like it should affect the only one node, and logically we could remove such failing node from the environment to get it discovered again. However, UI + API handlers were written in the following way: • UI calls /api/nodes to fetch info about all nodes to just show how many nodes are allocated, and how many are not • NodesCollectionHandler would return 500 if any of nodes raise an exception It is simple to guess, that the whole UI was completely destroyed by just one failed node. It was impossible to do any action on UI. These two examples give us the starting point to rethink on how to avoid Nailgun crash just if one of the meta attr is wrong. First, we must devide the meta attributes discovered by agent on two categories: • absolutely required for node discovering (i.e. MAC address) • non-required for discovering • required for deployment (i.e. disks) • non-required for deployment (i.e. current_speed) Second, we must have UI refactored to fetch only the information required, not the whole DB to just show two numbers. To be more specific, we have to make sure that issues in one environment must not affect the other environment. Such a refactoring will require additional handlers in Nailgun, as well as some additions, such as pagination and etc. From Nailgun side, it is bad idea to fail the whole CollectionHandler if one of the objects fail to calculate some attribute. My(mihgen) idea is to simply set attrubute to Null if failed to calculate, and program UI to handle it properly. Unit tests must help in testing of this. Another idea is to limit the /api/nodes, /api/networks and other calls to work only if cluster_id param provided, whether set to None or some of cluster Ids. In such a way we can be sure that one env will not be able to break the whole UI. Creating roles Each release has its own role list which can be customized. A plain list of roles is stored in the "roles" section of each release in the openstack.yaml: roles: - controller - compute - cinder The order in which the roles are listed here determines the order in which they are displayed on the UI. For each role in this list there should also be entry in "roles_metadata" section. It defines role name, description and conflicts with other roles: roles_metadata: controller: name: "Controller" description: "..." conflicts: - compute compute: name: "Compute" description: "..." conflicts: - controller cinder: name: "Storage - Cinder LVM" description: "..." 57 Table of contents "conflicts" section should contain a list of other roles that cannot be placed on the same node. In this example, "controller" and "compute" roles cannot be combined. Extending OpenStack Settings Each release has a list of OpenStack settings that can be customized. The settings configuration is stored in the "attributes_metadata.editable" release section in the openstack.yaml file. Settings are divided into groups. Each group should have a "metadata" section with the following attributes: metadata: toggleable: true enabled: false weight: 40 • toggleable defines an ability to enable/disable the whole setting group on UI (checkbox control is presented near a setting group label) • enabled indicates whether the group is checked on the UI • weight defines the order in which this group is displayed on the tab. • restrictions: see restrictions. Other sections of a setting group represent separate settings. A setting structure includes the following attributes: syslog_transport: value: "tcp" label: "Syslog transport protocol" description: "" weight: 30 type: "radio" values: - data: "udp" label: "UDP" description: "" restrictions: - "cluster:net_provider != 'neutron'" - data: "tcp" label: "TCP" description: "" regex: source: "^[A-z0-9]+$" error: "Invalid data" • label is a setting title that is displayed on UI • weight defines the order in which this setting is displayed in its group. This attribute is desirable • type defines the type of UI control to use for the setting • regex section is applicable for settings of "text" type. "regex.source" is used when validating with a regular expression. "regex.error" contains a warning displayed near invalid field • restrictions: see restrictions. • description section should also contain information about setting restrictions (dependencies, conflicts) • values list is needed for settings of "radio" or "select" type to declare its possible values. Options from "values" list also support dependencies and conflcits declaration. Restrictions 58 Table of contents Restrictions define when settings and setting groups should be available. Each restriction is defined as a condition with optional action and message: restrictions: - condition: "settings:common.libvirt_type.value != 'kvm'" message: "KVM only is supported" - condition: "not ('experimental' in version:feature_groups)" action: hide • condition is an expression written in Expression DSL. If returned value is true, then action is performed and message is shown (if specified). • action defines what to do if condition is satisfied. Supported values are "disable", "hide" and "none". "none" can be used just to display message. This field is optional (default value is "disable"). • message is a message that is shown if condition is satisfied. This field is optional. There are also short forms of restrictions: restrictions: - "settings:common.libvirt_type.value != 'kvm'": "KVM only is supported" - "settings:storage.volumes_ceph.value == true" Expression Syntax Expression DSL can describe arbitrarily complex conditions that compare fields of models and scalar values. Supported types are: • Number (123, 5.67) • String ("qwe", 'zxc') • Boolean (true, false) • Null value (null) • ModelPath (settings:common.libvirt_type.value, cluster:net_provider) ModelPaths consist of a model name and a field name separated by ":". Nested fields (like in settings) are supported, separated by ".". Models available for usage are "cluster", "settings", "networking_parameters" and "version". Supported operators are: • "==". Returns true if operands are equal: settings:common.libvirt_type.value == 'qemu' • "!=". Returns true if operands are not equal: cluster:net_provider != 'neutron' • "in". Returns true if the right operand (Array or String) contains the left operand: 'ceph-osd' in release:roles • Boolean operators: "and", "or", "not": cluster:mode == "ha_compact" and not (settings:common.libvirt_type.value == 'kvm' or 'ex Parentheses can be used to override the order of precedence. Bonding in UI/Nailgun Abstract The NIC bonding allows you to aggregate multiple physical links to one link to increase speed and provide fault tolerance. 59 Table of contents Design docs https://etherpad.openstack.org/p/fuel-bonding-design Fuel Support The Puppet module L23network has support for OVS and native Linux bonding, so we can use it for both NovaNetwork and Neutron deployments. Only Native OVS bonding (Neutron only) is implemented in Nailgun now. Vlan splinters cannot be used on bonds now. Three modes are supported now: 'active-backup', 'balance-slb', 'lacp-balance-tcp' (see nailgun.consts.OVS_BOND_MODES). Deployment serialization Most detailed docs on deployment serialization for neutron are here: 1. http://docs.mirantis.com/fuel/fuel-4.0/reference-architecture.html#advanced-network-configuration-using-open-vswit 2. https://etherpad.openstack.org/p/neutron-orchestrator-serialization Changes related to bonding are in the “transformations” section: 1. "add-bond" section { "action": "add-bond", "name": "bond-xxx", # name is generated in UI "interfaces": [], # list of NICs; ex: ["eth1", "eth2"] "bridge": "br-xxx", "properties": [] # info on bond's policy, mode; ex: ["bond_mode=active-backup"] } 2. Instead of creating separate OVS bridges for every bonded NIC we need to create one bridge for the bond itself { "action": "add-br", "name": "br-xxx" } REST API NodeNICsHandler and NodeCollectionNICsHandler are used for bonds creation, update and removal. Operations with bonds and networks assignment are done in single request fashion. It means that creation of bond and appropriate networks reassignment is done using one request. Request parameters must contain sufficient and consistent data for construction of new interfaces topology and proper assignment of all node's networks. Request/response data example: [ { "name": "ovs-bond0", # only name is set for bond, not id "type": "bond", "mode": "balance-slb", # see nailgun.consts.OVS_BOND_MODES for modes list "slaves": [ {"name": "eth1"}, # only “name” must be in slaves list {"name": "eth2"}], "assigned_networks": [ { "id": 9, "name": "public" } ] }, { 60 Table of contents "name": "eth0", "state": "up", "mac": "52:54:00:78:55:68", "max_speed": null, "current_speed": null, "assigned_networks": [ { "id": 1, "name": "fuelweb_admin" }, { "id": 10, "name": "management" }, { "id": 11, "name": "storage" } ], "type": "ether", "id": 5 }, { "name": "eth1", "state": "up", "mac": "52:54:00:88:c8:78", "max_speed": null, "current_speed": null, "assigned_networks": [], # empty for bond slave interfaces "type": "ether", "id": 2 }, { "name": "eth2", "state": "up", "mac": "52:54:00:03:d1:d2", "max_speed": null, "current_speed": null, "assigned_networks": [], # empty for bond slave interfaces "type": "ether", "id": 1 } ] Following fields are required in request body for bond interface: name, type, mode, slaves. Following fields are required in request body for NIC: id, type. Nailgun DB Now we have separate models for bond interfaces and NICs: NodeBondInterface and NodeNICInterface. Node's interfaces can be accessed through Node.nic_interfaces and Node.bond_interfaces separately or through Node.interfaces (property, read-only) all together. Relationship between them (bond:NIC ~ 1:M) is expressed in “slaves” field in NodeBondInterface model. Two more new fields in NodeBondInterface are: “flags” and “mode”. Bond's “mode” can accept values from nailgun.consts.OVS_BOND_MODES. Bond's “flags” are not in use now. “type” property (read-only) indicates whether it is a bond or NIC (see nailgun.consts.NETWORK_INTERFACE_TYPES). Contributing to Fuel Library 61 Table of contents This chapter will explain how to add new module or project into Fuel Library, how to integrate with other components and how to avoid different problems and potential mistakes. Fuel Library is a very big project and even experienced Puppet user will have problems understanding its structure and internal workings. Adding new modules to fuel-library Case A. Pulling in an existing module If you are adding a module that is the work of another project and is already tracked in separate repo then: 1. Create a review request with a unmodified copy of the upstream module from whichever point you are working from and no other related modifications. • This review should also contain the commit hash from the upstream repo in the commit message. • The review should be evaluated to determine its suitability and either rejected (for licensing, code quality, outdated version requested) or accepted without requiring modifications. • The review should not include code that calls this new module. 2. Any changes necessary to make it work with Fuel should then be proposed as a dependent change(s). Case B. Adding a new module If you are adding a new module that is a work purely for Fuel and will not be tracked in a separate repo then submit incremental reviews that consist of working implementation of features for your module. If you have features that are necessary, but do not work fully yet, then prevent them from running during the deployment. Once your feature is complete, submit a review to activate the module during deployment. Contributing to existing fuel-library modules As developers of Puppet modules, we tend to collaborate with the Puppet OpenStack community. As a result, we contribute to upstream modules all of the improvements, fixes and customizations we make to improve Fuel as well. That implies that every contributor must follow Puppet DSL basics, puppet-openstack dev docs and Puppet rspec tests requirements. The most common and general rule is that upstream modules should be modified only when bugfixes and improvements could benefit everyone in the community. And appropriate patch should be proposed to the upstream project prior to Fuel project. In other cases (like applying some very specific custom logic or settings) contributor should submit patches to openstack::* classes Fuel library includes custom modules as well as ones forked from upstream sources. Note that Modulefile, if any exists, should be used in order to recognize either given module is forked upstream one or not. In case there is no Modulefile in module's directory, the contributor may submit a patch directly to this module in Fuel library. Otherwise, he or she should submit patch to upstream module first, and once merged or +2 recieved from a core reviewer, the patch should be backported to Fuel library as well. Note that the patch submitted for Fuel library should contain in commit message the upstream commit SHA or link to github pull-request (if the module is not on stackforge) or Change-Id of gerrit patch. The Puppet modules structure First let's start with Puppet modules structure. If you want to contribute you code into the Fuel Library it should be organized into a Puppet module. Modules are self-contained sets of Puppet code that usually are made to perform specific function. For example you could have a module for every service you are going to configure or for every part of your project. Usually it's a good idea to make a module independent but sometimes it could require or be required by other modules so module can be thinked about as a library. 62 Table of contents The most important part of every Puppet module is its manifests folder. This folder contains Puppet classes and definitions which also contain resources managed by this module. Modules and classes also form namespaces. Each class or definition should be placed each into single file inside manifests folder and this file should be named same as class or definition. Module should have top level class that serves as a module's entry point and is named same as the module. This class should be placed into init.pp file. This example module shows the standard structure every Puppet module should follow.: example example/manifests/init.pp example/manifests/params.pp example/manifests/client.pp example/manifests/server example/manifests/server/vhost.pp example/manifests/server/service.pp example/templates example/templates/server.conf.erb example/files example/files/client.data The first file in manifests folder is named init.pp and should contain entry point class of this module. This class should be named same as our module.: class example { } The second file is params.pp. These files are not mandatory but are often used to store different configuration values and parameters used by other classes of the module. For example, it could contain service name and package name of our hypothetical example module. There could be conditional statements if you need to change default values in different environments. Params class should be named as child to module's namespace as all other classes of the module.: class example::params { $service = 'example' $server_package = 'example-server' $client_package = 'example-client' $server_port = '80' } All other inside the manifests folder contain classes as well and can perform any action you might want to identify as a separate piece of code. This generally falls into sub-classes that don't require its users to configure the parameters explicitly, or possibly these are simply optional classes that are not required in all cases. In the following example, we create a client class to define a client package that will be installed, placed into a file called client.pp.: class example::client { include example::params package { $example::params::client_package : ensure => installed, } } As you can see we have used package name from params class. Consolidating all values that might require editing into a single class, as opposed to hardcoding them, allows you to reduce the effort required to maintain and develop the module further in the future. If you are going to use any values from params class you should not forget to include it first to force its code to execute and create all required variables. You can add more levels into the namespace structure if you want. Let's create server folder inside our manifests folder and add service.pp file there. It would be responsible for installation and running 63 Table of contents server part of our imaginary software. Placing the class inside subfolder adds one level into name of contained class.: class example::server::service ( $port = $example::params::server_port, ) inherits example::params { $package = $example::params::server_package $service = $example::params::service package { $package : ensure => installed, } service { $service : ensure => running, enabled => true, hasstatus => true, hasrestart => true, } file { 'example_config' : ensure => present, path => '/etc/example.conf', owner => 'root', group => 'root', mode => '0644', content => template('example/server.conf.erb'), } file { 'example_config_dir' : ensure => directory, path => '/etc/example.d', owner => 'example', group => 'example', mode => '0755', } Package[$package] -> File['example_config', 'example_config_dir'] ~> Service['example_config'] } This example is a bit more complex. Let's see what it does. Class example::server::service is parametrized and can accept one parameter - port to which server process should bind to. It also uses a popular "smart defaults" hack. This class inherits the params class and uses its values default only if no port parameter is provided. In this case, you can't use include params to load the default values because it's called by the inherits example::params clause of the class definition. Then inside our class we take several variable from params class and declare them as variable of the local scope. This is conveniency hack to make their names shorter. Next we declare our resources. These resources are package, service, config file and config dir. Package resource will install package which name is taken from variable if it's not already installed. File resources create config file and config dir and service resource would start the daemon process and enable its autostart. And the last but not least part of this class is dependency declaration. We have used "chain" syntax to specify the order of evaluation of these resources. Of course it's important first to install package, then configuration files and only then start the service. Trying to start service before installing 64 Table of contents package will definitely fail. So we need to tell Puppet that there are dependencies between our resources. The arrow operator that has a tilde instead of a minus sign (~>) means not only dependency relationship but also notifies the object to the right of the arrow to refresh itself. In our case any changes in configuration file would make the service to restart and load new configuration file. Service resource react to notification event by restating managed service. Other resources may perform different actions instead if they support it. Ok, but where do we get our configuration file content from? It's generated by template function. Templates are text files with Ruby's erb language tags that are used to generate needed text file using pre-defined text and some variables from manifest. These template files are located inside the templates folder of the module and usually have erb extension. Calling template function with template name and module name prefix will try to load this template and compile it using variables from the local scope of the class function was called from. For example we want to set bind port of our service in its configuration file so we write template like this and save it inside templates folder as server.conf.erb file.: bind_port = <%= @port %> Template function will replace 'port' tag with value of port variable from our class during Puppet's catalog compilation. Ok, now we have our service running and client package installed. But what if our service needs several virtual hosts? Classes cannot be declared several times with different parameters so it's where definitions come to the rescue. Definitions are very similar to classes, but unlike classes, they have titles like resources do and can be used many times with different title to produce many instances of managed resources. Defined types can also accept parameters like parametrized classes do. Definitions are placed in single files inside manifests directories same as classes and are similarly named using namespace hierarchy. Let's create our vhost definition.: define example::server::vhost ( $path = '/var/data', ) { include example::params $config = “/etc/example.d/${title}.conf” $service = $example::params::service file { $config : ensure => present, owner => 'example', group => 'example', mode => '0644', content => template('example/vhost.conf.erb'), } File[$config] ~> Service[$service] } This defined type only creates a file resource with its name populated by the title used when it gets defined and sets notification relationship with service to make it restart when vhost file is changed. This defined type can be used by other classes like a simple resource type to create as many vhost files as we need.: example::server::vhost { 'mydata' : path => '/path/to/my/data', } Defined types can form relationships in a same way as resources do but you need to capitalize all elements of path to make reference.: 65 Table of contents File['/path/to/my/data'] -> Example::Server::Vhost['mydata'] Now we can work with text files using templates but what if we need to manage binary data files? Binary files or text files that will always be same can be placed into files directory of our module and then be taken by file resource. Let's imagine that our client package need some binary data file we need to redistribute with it. Let's add file resource to our example::client class.: file { 'example_data' : path => '/var/lib/example.data', owner => 'example', group => 'example', mode => '0644', source => 'puppet:///modules/example/client.data', } We have specified source as a special puppet URL scheme with module's and file's name. This file will be placed to specified location during puppet run. But on each run Puppet will check this files checksum overwriting it if it changes so don't use this method with mutable data. Puppet's fileserving works both in client-server and masterless modes. Ok, we have all classes and resources we need to manage our hypothetical example service. Let's try to put everything together. Our example class defined inside init.pp is still empty so we can use it to declare all other classes.: class example { include example::params include example::client class { 'example::server::service' : port => '100', } example::server::vhost { 'site1' : path => '/data/site1', } example::server::vhost { 'site2' : path => '/data/site2', } example::server::vhost { 'test' : path => '/data/test', } } Now we have entire module packed inside example class and we can just include this class to any node where we want to see our service running. Declaration of parametrized class also did override default port number from params file and we have three separate virtual hosts for out service. Client package is also included into this class. Using Fuel settings Fuel uses a special way to pass setting from Nailgun to Puppet manifests. Before the start of deployment process Astute uploads all settings, each server should have to the file /etc/astute.yaml placed on every node. When Puppet is run facter reads this file entirely into a single fact $astute_settings_yaml. Then these settings are parsed by parseyaml function at the very beginning of site.pp file and set as rich data structure called $fuel_settings. All of the setting used during node deployment are stored there and can be used anywhere in Puppet code. For example, single top level variables are available as $::fuel_settings['debug']. More complex structures are also available as values of $::fuel_settings hash keys and can be accessed like usual hashes and arrays. There are also 66 Table of contents a lot of aliases and generated values that help you get needed values easier. You can always create variables from any of settings hash keys and work with this variable within your local scope or from other classes using fully qualified paths.: $debug = $::fuel_settings['debug'] Some variables and structures are generated from settings hash by filtering and transformation functions. For example there is $node structure.: $node = filter_nodes($nodes_hash, 'name', $::hostname) It contains only settings of current node filtered from all nodes hash. If you are going to use your module inside Fuel Library and need some settings you can just get them from this $::fuel_settings structure. Most variables related to network and OpenStack services configuration are already available there and you can use them as they are. But if your modules requires some additional or custom settings you'll have to either use Custom Attributes by editing json files before deployment, or, if you are integrating your project with Fuel Library, you should contact Fuel UI developers and ask them to add your configuration options to Fuel setting panel. Once you have finished definition of all classes you need inside your module you can add this module's declaration either into the Fuel manifests such as cluster_simple.pp and cluster_ha.pp located inside osnailyfacter/manifests folder or to the other classes that are already being used if your additions are related to them. Example module Let's demonstrate how to add new module to the Fuel Library by adding a simple class that will change terminal color of Red Hat based systems. Our module will be named profile and have only one class.: profile profile/manifests profile/manifests/init.pp profile/files profile/files/colorcmd.sh init.pp could have a class definition like this.: class profile { if $::osfamily == 'RedHat' { file { 'colorcmd.sh' : ensure => present, owner => 'root', group => 'root', mode => '0644', path => "/etc/profile.d/colorcmd.sh", source => 'puppet:///modules/profile/colorcmd.sh', } } } This class just downloads colorcmd.sh file and places it to the defined location if this class is run on Red Hat or CentOS system. The profile module can be added to Fuel modules by uploading its folder to /etc/puppet/modules on the Fuel Master node. Now we need to declare this module somewhere inside Fuel manifests. Since this module should be run on every server, we can use our main site.pp manifest found inside the osnailyfacter/examples folder. On the deployed master node this file will be copied to /etc/puppet/manifests and used to deploy Fuel on all other nodes. The only thing we need to do here is to add include profile to the end of /etc/puppet/manifests/site.pp file on already deployed master node and to osnailyfacter/examples/site.pp file inside Fuel repository. 67 Table of contents Declaring a class outside of node block will force this class to be included everywhere. If you want to include you module only on some nodes, you can add its declaration inside cluster_simple and cluster_ha classed to the blocks associated with required node's role. You can add some additional logic to allow used to enable or disable this module from Fuel UI or at least by passing Custom Attributes to Fuel configuration.: if $::fuel_settings['enable_profile'] { include 'profile' } This block uses the enable_profile variable to enable or disable inclusion of profile module. The variable should be passed from Nailgun and saved to /etc/astute.yaml files of managed nodes. You can do it by either downloading settings files and manually editing them before deployment or by asking Fuel UI developers to include additional options to the settings panel. Resource duplication and file conflicts If you have been developing your module that somehow uses services which are already in use by other components of OpenStack, most likely you will try to declare some of the same resources that have already been declared. Puppet architecture doesn't allow declaration of resources that have same type and title even if they do have same attributes. For example, your module could be using Apache and has Service['apache'] declared. When you are running your module outside Fuel nothing else tries to control this service to and everything work fine. But when you will try to add this module to Fuel you will get resource duplication error because Apache is already managed by Horizon module. There is pretty much nothing you can do about this problem because uniqueness of Puppet resources is one on its core principles. But you can try to solve the problem by one of following ways. The best thing you can do is to try to use an already declared resource by settings dependencies to the other class that does use it. This will not work in many cases and you may have to modify both modules or move conflicting resource elsewhere to avoid conflicts. Puppet does provide a good solution to this problem - virtual resources. The idea behind it is that you move resource declaration to separate class and make them virtual. Virtual resources will not be evaluated until you realize them and you can do it in all modules that do require this resources. The trouble starts when these resources have different attributes and complex dependencies. Most current Puppet modules doesn't use virtual resources and will require major refactoring to add them. Puppet style guidelines advise to move all classes related with the same service inside a single module instead of using many modules to work with same service to minimize conflicts, but in many cases this approach doesn't work. There are also some hacks such are defining resource inside if ! defined(Service['apache']) { ... } block or using ensure_resource function from Puppet's stdlib. Similar problems often arise then working with configuration files. Even using templates doesn't allow several modules to directly edit same file. There are a number of solutions to this starting from using configurations directories and snippets if service supports them to representing lines or configuration options as resources and managing them instead of entire files. Many services does support configuration directories where you can place configuration files snippets. Daemon will read them all, concatenate and use like it was a single file. Such services are the most convenient to manage with Puppet. You can just separate you configuration and manage its pieces as templates. If your service doesn't know how to work with snippets you still can use them. You only need to create parts of your configuration file in some directory and then just combine them all using simple exec with cat command. There is also a special concat resource type to make this approach easier. Some configuration files could have standard structure and can be managed by custom resource types. For example, there is the ini_file resource type to manage values in compatible configuration as single resources. There is also augeas resource type that can manage many popular configuration file formats. 68 Table of contents Each approach has its own limitations and editing single file from many modules is still non-trivial task in most cases. Both resource duplication and file editing problems doesn't have a good solution for every possible case and significantly limit possibility of code reuse. The last approach to solving this problem you can try is to modify files by scripts and sed patches ran by exec resources. This can have unexpected results because you can't be sure of what other operations are performed on this configuration file, what text patterns exist there, and if your script breaks another exec. Puppet module containment Fuel Library consists of many modules with a complex structure and several dependencies defined between the provided modules. There is a known Puppet problem related to dependencies between resources contained inside classes declared from other classes. If you declare resources inside a class or definition they will be contained inside it and entire container will not be finished until all of its contents have been evaluated. For example, we have two classes with one notify resource each.: class a { notify { 'a' :} } class b { notify { 'b' :} } Class['a'] -> Class['b'] include a include b Dependencies between classes will force contained resources to be executed in declared order. But if we add another layer of containers dependencies between them will not affect resources declared in first two classes.: class a { notify { 'a' :} } class b { notify { 'b' :} } class l1 { include a } class l2 { include b } Class['l1'] -> Class['l2'] include 'l1' include 'l2' This problem can lead to unexpected and in most cases unwanted behaviour when some resources 'fall out' from their classes and can break the logic of the deployment process. The most common solution to this issue is Anchor Pattern. Anchors are special 'do-nothing' resources found in Puppetlab's stdlib module. Anchors can be declared inside top level class and be contained 69 Table of contents inside as any normal resource. If two anchors was declared they can be named as start and end anchor. All classes, that should be contained inside the top-level class can have dependencies with both anchors. If a class should go after the start anchor and before the end anchor it will be locked between them and will be correctly contained inside the parent class.: class a { notify { 'a' :} } class b { notify { 'b' :} } class l1 { anchor { 'l1-start' :} include a anchor { 'l1-end' :} Anchor['l1-start'] -> Class['a'] -> Anchor['l1-end'] } class l2 { anchor { 'l2-start' :} include b anchor { 'l2-end' :} Anchor['l2-start'] -> Class['b'] -> Anchor['l2-end'] } Class['l1'] -> Class['l2'] include 'l1' include 'l2' This hack does help to prevent resources from randomly floating out of their places, but look very ugly and is hard to understand. We have to use this technique in many of Fuel modules which are rather complex and require such containment. If your module is going to work with dependency scheme like this, you could find anchors useful too. There is also another solution found in the most recent versions of Puppet. Contain function can force declared class to be locked within its container.: class l1 { contain 'a' } class l2 { contain 'b' } Puppet scope and variables The way Puppet looks for values of variables from inside classes can be confusing too. There are several levels of scope in Puppet. Top scope contains all facts and built-in variables and goes from the start of site.pp file before any class or node declaration. There is also a node scope. It can be different for every node block. Each class and definition start their own local scopes and their variables and resource defaults are available their. They can also have parent scopes. Reference to a variable can consist of two parts $(class_name)::(variable_name) for example $apache::docroot. Class name can also be empty and such record will explicitly reference top level scope for example $::ipaddress. 70 Table of contents If you are going to use value of a fact or top-scope variable it's usually a good idea to add two colons to the start of its name to ensure that you will get the value you are looking for. If you want to reference variable found in another class and use fully qualified name like this $apache::docroot. But you should remember that referenced class should be already declared. Just having it inside your modules folder is not enough for it. Using include apache before referencing $apache::docroot will help. This technique is commonly used to make params classes inside every module and are included to every other class that use their values. And finally if you reference a local variable you can write just $myvar. Puppet will first look inside local scope of current class of defined type, then inside parent scope, then node scope and finally top scope. If variable is found on any of this scopes you get the first match value. Definition of what the parent scope is varies between Puppet 2.* and Puppet 3.*. Puppet 2.* thinks about parent scope as a class from where current class was declared and all of its parents too. If current class was inherited from another class base class also is parent scope allowing to do popular Smart Defaults trick.: class a { $var = ‘a’ } class b( $a = $a::var, ) inherits a { } Puppet 3.* thinks about parent scope only as a class from which current class was inherited if any and doesn't take declaration into account. For example: $msg = 'top' class a { $msg = "a" } class a_child inherits a { notify { $msg :} } Will say 'a' in puppet 2.* and 3.* both. But.: $msg = 'top' class n1 { $msg = 'n1' include 'n2' } class n2 { notify { $msg :} } include 'n1' Will say 'n1' in puppet 2.6, will say 'n1' and issue deprecation warning in 2.7, and will say 'top' in puppet 3.* Finding such variable references replacing them with fully qualified names is very important part Fuel of migration to Puppet 3.* Where to find more information 71 Table of contents The best place to start learning Puppet is Puppetlabs' official learning course (http://docs.puppetlabs.com/learning/). There is also a special virtual machine image you can use to safely play with Puppet manifests. Then you can continue to read Puppet reference and other pages of Puppetlabs documentation. You can also find a number of printed book about Puppet and how to use it to manage your IT infrastructure. Pro Puppet http://www.apress.com/9781430230571 Pro Puppet. 2nd Edition http://www.apress.com/9781430260400 Puppet 2.7 Cookbook http://www.packtpub.com/puppet-2-7-for-reliable-secure-systems-cloud-computing- cookbook/book Puppet 3 Cookbook http://www.packtpub.com/puppet-3-cookbook/book Puppet 3: Beginners Guide http://www.packtpub.com/puppet-3-beginners-guide/book Instant Puppet 3 Starter http://www.packtpub.com/puppet-3-starter/book Pulling Strings with Puppet http://www.apress.com/9781590599785 Configuration Management Puppet Types and Providers http://shop.oreilly.com/product/0636920026860.do Extending Managing Infrastructure with Puppet. http://shop.oreilly.com/product/0636920020875.do Configuration Puppet Management Made Easy with Ruby at Scale Fuel Master Node Deployment over PXE Tech Explanation of the process In some cases (such as no installed CD-ROM or no physical access to the servers) we need to install Fuel Master node somehow other way from CD or USB Flash drive. Starting from Fuel 4.0 it's possible to deploy Master node with PXE The process of deployment of Fuel master node over network consists of booting linux kernel by DHCP and PXE. Then anaconda installer will download configuration file and all packages needed to complete the installation. • PXE firmware of the network card makes DHCP query and gets IP address and boot image name. • Firmware downloads boot image file using TFTP protocol and starts it. • This bootloader downloads configuration file with kernel boot option, kernel and initramfs and starts the installer. • Installer downloads kickstart configuration file by mounting contents of Fuel ISO file over NFS. • Installer partitions hard drive, installs the system by downloading packages over NFS, copies all additional files, installs the bootloader and reboots into new system. So we need: • Working system to serve as network installer. • DHCP server • TFTP server • NFS server • PXE bootloader and its configuration file • Extracted or mounted Fuel ISO file In our test we will use 10.20.0.0/24 network. 10.20.0.1/24 will be IP address of our host system. 72 Table of contents Installing packages We will be using Ubuntu or Debian system as an installation server. Other linux or even BSD-based systems could be used too, but paths to configuration files and init scripts may differ. First we need to install the software: # TFTP server and client apt-get install tftp-hpa tftpd-hpa # DHCP server apt-get install isc-dhcp-server # network bootloader apt-get install syslinux syslinux-common # nfs server apt-get install nfs-server Setting up DHCP server Standalone ISC DHCPD First we are going to create configuration file located at /etc/dhcp/dhcpd.conf: ddns-update-style none; default-lease-time 600; max-lease-time 7200; authoritative; log-facility local7; subnet 10.20.0.0 netmask 255.255.255.0 { range 10.20.0.2 10.20.0.2; option routers 10.20.0.1; option domain-name-servers 10.20.0.1; } host fuel { hardware ethernet 52:54:00:31:38:5a; fixed-address 10.20.0.2; filename "pxelinux.0"; } We have declared a subnet with only one IP address available that we are going to give to our master node. We are not going to serve entire range of IP addresses because it will disrupt Fuel’s own DHCP service. There is also a host definition with a custom configuration that matches a specific MAC address. This address should be set to the MAC address of the system that you are going to make Fuel master node. Other systems on this subnet will not receive any IP addresses and will load bootstrap from master node when it starts serving DHCP requests. We also give a filename that will be used to boot the Fuel master node. Using 10.20.0.0/24 subnet requires you to set 10.20.0.1 on the network interface connected to this network. You may also need to set the interface manually using the INTERFACES variable in /etc/default/isc-dhcp-server file. Start DHCP server: /etc/init.d/isc-dhcp-server restart Simple with dnsmasq: sudo dnsmasq -d --enable-tftp --tftp-root=/var/lib/tftpboot \ --dhcp-range=10.20.0.2,10.20.0.2 \ --port=0 -z -i eth2 \ --dhcp-boot='pxelinux.0' Libvirt with dnsmasq 73 Table of contents If you are using libvirt virtual network to install your master node, then you can use its own DHCP service. Use virsh net-edit default to modify network configuration: <network> <name>default</name> <bridge name="virbr0" /> <forward /> <ip address="10.20.0.1" netmask="255.255.255.0"> <tftp root="/var/lib/tftpboot"/> <dhcp> <range start="10.20.0.2" end="10.20.0.2" /> <host mac="52:54:00:31:38:5a" ip="10.20.0.2" /> <bootp file="pxelinux.0"/> </dhcp> </ip> </network> This configuration includes TFTP server and DHCP server with only one IP address set to your master node’s MAC address. You don't need to install neither external DHCP server nor TFTP server. Don’t forget to restart the network after making edits: virsh net-destroy default virsh net-start default Dnsmasq without libvirt You can also use dnsmasq as a DHCP and TFTP server without libvirt: strict-order domain-needed user=libvirt-dnsmasq local=// pid-file=/var/run/dnsmasq.pid except-interface=lo bind-dynamic interface=virbr0 dhcp-range=10.20.0.2,10.20.0.2 dhcp-no-override enable-tftp tftp-root=/var/lib/tftpboot dhcp-boot=pxelinux.0 dhcp-leasefile=/var/lib/dnsmasq/leases dhcp-lease-max=1 dhcp-hostsfile=/etc/dnsmasq/hostsfile In /etc/dnsmasq/hostsfile you can specify hosts and their mac addresses: 52:54:00:31:38:5a,10.20.0.2 Dnsmasq provides both DHCP, TFTP, as well as acts as a DNS caching server, so you don't need to install additional external services. Setting our TFTP server If you are not using a libvirt virtual network, then you need to install tftp server. On Debian or Ubuntu system its configuration file will be located here /etc/default/tftpd-hpa. Checking if all we want are there: TFTP_USERNAME="tftp" TFTP_DIRECTORY="/var/lib/tftpboot" TFTP_ADDRESS="10.20.0.1:69" TFTP_OPTIONS="--secure --blocksize 512" 74 Table of contents Don’t forget to set blocksize here. Some hardware switches have problems with larger block sizes. And star it: /etc/init.d/tftpd-hpa restart Setting up NFS server You will also need to setup NFS server on your install system. Edit the NFS exports file: vim /etc/exports Add the following line: /var/lib/tftpboot 10.20.0.2(ro,async,no_subtree_check,no_root_squash,crossmnt) And start it: /etc/init.d/nfs-kernel-server restart Set up tftp root Our tftp root will be located here: /var/lib/tftpboot Let’s create a folder called "fuel" to store ISO image contents and syslinux folder for bootloader files. If you have installed syslinux package you can find them in /usr/lib/syslinux folder. Copy this files from /usr/lib/syslinux to /var/lib/tftpboot: memdisk menu.c32 poweroff.com Now we need to write the /var/lib/tftpboot/pxelinux.cfg/default: pxelinux.0 pxelinux reboot.c32 configuration file. It will be located here DEFAULT menu.c32 prompt 0 MENU TITLE My Distro Installer TIMEOUT 600 LABEL localboot MENU LABEL ^Local Boot MENU DEFAULT LOCALBOOT 0 LABEL fuel MENU LABEL Install ^FUEL KERNEL /fuel/isolinux/vmlinuz INITRD /fuel/isolinux/initrd.img APPEND biosdevname=0 ks=nfs:10.20.0.1:/var/lib/tftpboot/fuel/ks.cfg repo=nfs:10.20.0.1:/var/ LABEL reboot MENU LABEL ^Reboot KERNEL reboot.c32 LABEL poweroff MENU LABEL ^Poweroff KERNEL poweroff.com You can ensure silent installation without any Anaconda prompts by adding the following APPEND directives: • ksdevice=INTERFACE • installdrive=DEVICENAME • forceformat=yes For example: installdrive=sda ksdevice=eth0 forceformat=yes 75 Table of contents Now we need to unpack the Fuel ISO file we have downloaded: mkdir -p /var/lib/tftpboot/fuel /mnt/fueliso mount -o loop /path/to/your/fuel.iso /mnt/fueliso rsync -a /mnt/fueliso/ /var/lib/tftpboot/fuel/ umount /mnt/fueliso && rmdir /mnt/fueliso So that's it! We can boot over the network from this PXE server. Troubleshooting After implementing one of the described configuration you should see something like that in your /var/log/syslog file: dnsmasq-dhcp[16886]: DHCP, IP range 10.20.0.2 -- 10.20.0.2, lease time 1h dnsmasq-tftp[16886]: TFTP root is /var/lib/tftpboot To make sure all of daemon listening sockets as they should: # netstat -upln | egrep ':(67|69|2049) ' udp 0 0 0.0.0.0:67 udp 0 0 10.20.0.1:69 udp 0 0 0.0.0.0:2049 0.0.0.0:* 0.0.0.0:* 0.0.0.0:* 30791/dnsmas 30791/dnsmas - • NFS - udp/2049 • DHCP - udp/67 • TFTP - udp/69 So all of daemons listening as they should. To test DHCP server does provide an IP address you can do something like that on the node in the defined PXE network. Please note, it should have Linux system installed or any other OS to test configuration properly: # dhclient -v eth0 Internet Systems Consortium DHCP Client 4.1.1-P1 Copyright 2004-2010 Internet Systems Consortium. All rights reserved. For info, please visit https://www.isc.org/software/dhcp/ Listening on LPF/eth0/00:25:90:c4:7a:64 Sending on LPF/eth0/00:25:90:c4:7a:64 Sending on Socket/fallback DHCPREQUEST on eth0 to 255.255.255.255 port 67 (xid=0x7b6e25dc) DHCPACK from 10.20.0.1 (xid=0x7b6e25dc) bound to 10.20.0.2 -- renewal in 1659 seconds. After running dhclient you should see how it asks one or few times DHCP server with DHCPDISCOVER and then get 10.20.0.2. If you have more then one NIC you should run dhclient on every one to determine where our network in connected to. TFTP server can be tested with tftp console client: # tftp (to) 10.20.0.1 tftp> get /pxelinux.0 NFS could be tested with mounting it: mkdir /mnt/nfsroot mount -t nfs 10.20.0.1:/var/lib/tftpboot /mnt/nfsroot Health Check (OSTF) Contributor's Guide 76 Table of contents Health Check or OSTF? Main goal of OSTF Main rules of code contributions How to setup my environment? How should my modules look like? How to execute my tests? Now I'm done, what's next? General OSTF architecture OSTF packages architecture OSTF Adapter architecture Appendix 1 Health Check or OSTF? Fuel UI has tab which is called Health Check. In development team though, there is an established acronym OSTF, which stands for OpenStack Testing Framework. This is all about the same. For simplicity, this document will use widely accepted term OSTF. Main goal of OSTF After OpenStack installation via Fuel, it`s very important to understand whether it was successful and if it`s ready for work. OSTF provides a set of health checks - sanity, smoke, HA and additional components tests that check the proper operation of all system components in typical conditions. There are tests for OpenStack scenarios validation and other specific tests useful in validating an OpenStack deployment. Main rules of code contributions There are a few rules you need to follow to successfully pass the code review and contribute high-quality code. How to setup my environment? OSTF repository is located on Stackforge: https://github.com/stackforge/fuel-ostf. You also have to install and hook-up gerrit, because otherwise you will not be able to contribute code. To do that you need to follow registration and installation instructions in the document https://wiki.openstack.org/wiki/CLA#Contributors_License_Agreement After you've completed the instructions, you're all set to begin editing/creating code. How should my modules look like? The rules are quite simple: • follow Python coding rules • follow OpenStack contributor's rules • watch out for mistakes in docstrings • follow correct test structure • always execute your tests after you wrote them before sending them to review Speaking of following Python coding standards, you can find the style guide here: http://www.python.org/dev/peps/pep-0008/. You should read it carefully once and after implementing scripts you need to run some checks that will ensure that your code corresponds the standards. Without correcting issues with coding stadards your scripts will not be merged to master. You should always follow the following implementation rules: • name the test module, test class and test method beginning with the word "test" • if you have some tests that should be ran in a specific order, add a number to test method name, for example: test_001_create_keypair • use verify(), verify_response_body_content() and other methods from mixins (see OSTF package architecture fuel_health/common/test_mixins.py section) with giving them failed step parameter • always list all steps you are checking using test_mixins methods in the docstring in Scenario section in correct order • always use verify() method when you want to check an operation that can go to an infinite loop 77 Table of contents The test docstrings are another important piece and you should always stick to the following docstring structure: • test title - test description that will be always shown on UI (the remaining part of docstring will only be shown in cases when test failed) • target component (optional) - component name that is tested (e.g. Nova, Keystone) • blank line • test scenario, example: Scenario: 1. Create a new small-size volume. 2. Wait for volume status to become "available". 3. Check volume has correct name. 4. Create new instance. 5. Wait for "Active" status. 6. Attach volume to an instance. 7. Check volume status is "in use". 8. Get information on the created volume by its id. 9. Detach volume from the instance. 10. Check volume has "available" status. 11. Delete volume. • test duration - an estimate of how much a test will take deployment tags (optional) - gives information about what kind of environment the test will be run, possible values are CENTOS, Ubuntu, RHEL nova_network, Heat, Murano, Sahara) Here's a test example which confirms the above explanations: Test run ordering and profiles Each test set (sanity, smoke, ha and platform_tests) contains a special variable in __init__.py module which is called __profile__. The profile variable makes it possible to set different rules, such as test run order, set up deployment tags, information gathering on cleanup and expected time estimate for running a test set. If you are develop a new set of tests, you need to create __init__.py module and place __profile__ dict in it. It is important that your profile matches the following structure: 78 Table of contents __profile__ = { "test_runs_ordering_priority": 4, "id": "platform_tests", "driver": "nose", "test_path": "fuel_health/tests/platform_tests", "description": ("Platform services functional tests." " Duration 3 min - 60 min"), "cleanup_path": "fuel_health.cleanup", "deployment_tags": ['additional_components'], "exclusive_testsets": [] } Take note of each field in the profile, along with acceptable values. • test_runs_ordering_priority is a field responsible for setting the priority in which the test set will be displayed, for example, if you set "6" for sanity tests and "3" for smoke tests, smoke test set will be displayed first on the HealthCheck tab; • id is just the unique id of a test set; • driver field is used for setting the test runner; • test_path is the field representing path where test set is located starting from fuel_health directory; • description is the field which contains the value to be shown on the UI as the tests duration; • cleanup_path is the field that specifies path to module responsible for cleanup mechanism (if you do not specify this value, cleanup will not be started after your test set); • deployment_tags field is used for defining when these tests should be available depending on cluster settings; • exclusive_testsets field gives you an opportunity to specify test sets that will be run successively. For example, you can specify "smoke_sanity" for smoke and sanity test set profiles, then these tests will be ran not simultaneously, but successively. It is necessary to specify a value for each of the attributes. The optional attribute is "deployment_tags", meaning optionally you may not specify it in your profile at all. You can leave the "exclusive_testsets" empty ([]) to run your testset simultaneously with other ones. How to execute my tests? Simplest way is to install Fuel, and OSTF will be installed as part of it. • install virtualbox • build Fuel ISO: Building the Fuel ISO • use virtualbox scripts to run an ISO • once the installation is finished, go to Fuel UI (usually it's 10.20.0.2:8000) and create a new cluster with necessary configuration • execute: rsync -avz <path to fuel_health>/ root@10.20.0.2:/opt/fuel_plugins/ostf/lib/python2.6 • execute: ssh root@10.20.0.2 ps uax | grep supervisor kill <supervisord process number> service supervisord start • go to Fuel UI and run your new tests Now I'm done, what's next? 79 Table of contents • don't forget to run pep8 on modified part of code • commit your changes • execute git review • ask to review in IRC From this part you'll only need to fix and commit review comments (if there are any) by doing the same steps. If there are no review comments left, the reviewers will accept your code and it will be automatically merged to master. General OSTF architecture Tests are included to Fuel, so they will be accessible as soon as you install Fuel on your lab. OSTF architecture is quite simple, it consists of two main packages: • fuel_health which contains the test set itself and related modules • fuel_plugin which contains OSTF-adapter that forms necessary test list in context of cluster deployment options and transfers them to UI using REST_API On the other hand, there is some information necessary for test execution itself. There are several modules that gather information and parse them into objects which will be used in the tests themselves. All information is gathered from Nailgun component. OSTF package architecture The main modules used in fuel_health package are: config module is responsible of getting data which is necessary for tests. All data is gathered from Nailgun component or a text config. Nailgun provides us with the following data: • OpenStack admin user name • OpenStack admin user password • OpenStack admin user tenant • ip of controllers node • ip of compute node - easily get data from nailgun by parsing role key in response json • deployment mode (HA /non-HA) • deployment os (RHEL/CENTOS) • keystone / horizon urls • tiny proxy address All other information we need is stored in config.py itself and remains default in this case. In case you are using data from Nailgun (OpenStack installation using Fuel) you should to the following: initialize NailgunConfig() class. Nailgun is running on Fuel master node, so you can easily get data for each cluster by invoking curl http:/localhost:8000/api/<uri_here>. Cluster id can be get from OS environment (provided by Fuel) If you want run OSTF for non Fuel installation, change the initialization of NailgunConfig() to FileConfig() and set parameters marked with green color in config - see Appendix 1 (default config file path fuel_health/etc/test.conf) cleanup.py - invoked by OSTF adapter in case if user stops test execution in Web UI. This module is responsible for deleting all test resources created during test suite run. It simply finds all resources whose name starts with ‘ost1_test-’ and destroys each of them using _delete_it method. 80 Table of contents Important: if you decide to add additional cleanup for this resource, you have to keep in mind: All resources depend on each other, that's why deleting a resource that is still in use will give you an exception; Don't forget that deleting several resources requires an ID for each resource, but not its name. You'll need to specify delete_type optional argument in _delete_it method to ‘id’ nmanager.py contains base classes for tests. Each base class contains setup, teardown and methods that act as an interlayer between tests and OpenStack python clients (see nmanager architecture diagram). fuel_health/common/test_mixins.py - provides mixins to pack response verification into a human-readable message. For assertion failure cases, the method requires a step on which we failed and a descriptive message to be provided. The verify() method also requires a timeout value to be set. This method should be used when checking OpenStack operations (such as instance creation). Sometimes a cluster operation taking too long may be a sign of a problem, so this will secure the tests from such a situation or even from going into infinite loop. fuel_health/common/ssh.py - provides an easy way to ssh to nodes or instances. This module uses the paramiko library and contains some useful wrappers that make some routine tasks for you (such as ssh key authentication, starting transport threads, etc). Also, it contains a rather useful method exec_command_on_vm(), which makes an ssh to an instance through a controller and then executes the necessary command on it. OSTF Adapter architecture The important thing to remember about OSTF Adapter is that just like when writing tests, all code should follow pep8 standard. Appendix 1 IdentityGroup = [ cfg.StrOpt('catalog_type', default='identity', may be changes on keystone help="Catalog type of the Identity service."), cfg.BoolOpt('disable_ssl_certificate_validation', default=False, 81 Table of contents help="Set to True if using self-signed SSL certificates."), cfg.StrOpt('uri', default='http://localhost/' (If you are using FileConfig set here appropriate addre help="Full URI of the OpenStack Identity API (Keystone), v2"), cfg.StrOpt('url', default='http://localhost:5000/v2.0/', (If you are using FileConfig set here approp help="Dashboard Openstack url, v2"), cfg.StrOpt('uri_v3', help='Full URI of the OpenStack Identity API (Keystone), v3'), cfg.StrOpt('strategy', default='keystone', help="Which auth method does the environment use? " "(basic|keystone)"), cfg.StrOpt('region', default='RegionOne', help="The identity region name to use."), cfg.StrOpt('admin_username', default='nova' , (If you are using FileConfig set appropriate value here) help="Administrative Username to use for" "Keystone API requests."), cfg.StrOpt('admin_tenant_name', (If you are using FileConfig set appropriate value here) default='service', help="Administrative Tenant name to use for Keystone API " "requests."), cfg.StrOpt('admin_password', (If you are using FileConfig set appropriate value here) default='nova', help="API key to use when authenticating as admin.", secret=True), ] ComputeGroup = [ cfg.BoolOpt('allow_tenant_isolation', default=False, help="Allows test cases to create/destroy tenants and " "users. This option enables isolated test cases and " "better parallel execution, but also requires that " "OpenStack Identity API admin credentials are known."), cfg.BoolOpt('allow_tenant_reuse', default=True, help="If allow_tenant_isolation is True and a tenant that " "would be created for a given test already exists (such " "as from a previously-failed run), re-use that tenant " "instead of failing because of the conflict. Note that " "this would result in the tenant being deleted at the " "end of a subsequent successful run."), cfg.StrOpt('image_ssh_user', default="root", (If you are using FileConfig set appropriate value here) help="User name used to authenticate to an instance."), cfg.StrOpt('image_alt_ssh_user', default="root", (If you are using FileConfig set appropriate value here) help="User name used to authenticate to an instance using " "the alternate image."), cfg.BoolOpt('create_image_enabled', default=True, help="Does the test environment support snapshots?"), cfg.IntOpt('build_interval', default=10, help="Time in seconds between build status checks."), cfg.IntOpt('build_timeout', default=160, 82 Table of contents help="Timeout in seconds to wait for an instance to build."), cfg.BoolOpt('run_ssh', default=False, help="Does the test environment support snapshots?"), cfg.StrOpt('ssh_user', default='root', (If you are using FileConfig set appropriate value here) help="User name used to authenticate to an instance."), cfg.IntOpt('ssh_timeout', default=50, help="Timeout in seconds to wait for authentication to " "succeed."), cfg.IntOpt('ssh_channel_timeout', default=20, help="Timeout in seconds to wait for output from ssh " "channel."), cfg.IntOpt('ip_version_for_ssh', default=4, help="IP version used for SSH connections."), cfg.StrOpt('catalog_type', default='compute', help="Catalog type of the Compute service."), cfg.StrOpt('path_to_private_key', default='/root/.ssh/id_rsa', (If you are using FileConfig set appropriate value here help="Path to a private key file for SSH access to remote " "hosts"), cfg.ListOpt('controller_nodes', default=[], (If you are using FileConfig set appropriate value here) help="IP addresses of controller nodes"), cfg.ListOpt('compute_nodes', default=[], (If you are using FileConfig set appropriate value here) help="IP addresses of compute nodes"), cfg.StrOpt('controller_node_ssh_user', default='root', (If you are using FileConfig set appropriate value here) help="ssh user of one of the controller nodes"), cfg.StrOpt('controller_node_ssh_password', default='r00tme', (If you are using FileConfig set appropriate value here) help="ssh user pass of one of the controller nodes"), cfg.StrOpt('image_name', default="TestVM", (If you are using FileConfig set appropriate value here) help="Valid secondary image reference to be used in tests."), cfg.StrOpt('deployment_mode', default="ha", (If you are using FileConfig set appropriate value here) help="Deployments mode"), cfg.StrOpt('deployment_os', default="RHEL", (If you are using FileConfig set appropriate value here) help="Deployments os"), cfg.IntOpt('flavor_ref', default=42, help="Valid primary flavor to use in tests."), ] ImageGroup = [ cfg.StrOpt('api_version', default='1', help="Version of the API"), cfg.StrOpt('catalog_type', default='image', help='Catalog type of the Image service.'), cfg.StrOpt('http_image', 83 Table of contents default='http://download.cirros-cloud.net/0.3.1/' 'cirros-0.3.1-x86_64-uec.tar.gz', help='http accessable image') ] NetworkGroup = [ cfg.StrOpt('catalog_type', default='network', help='Catalog type of the Network service.'), cfg.StrOpt('tenant_network_cidr', default="10.100.0.0/16", help="The cidr block to allocate tenant networks from"), cfg.IntOpt('tenant_network_mask_bits', default=29, help="The mask bits for tenant networks"), cfg.BoolOpt('tenant_networks_reachable', default=True, help="Whether tenant network connectivity should be " "evaluated directly"), cfg.BoolOpt('neutron_available', default=False, help="Whether or not neutron is expected to be available"), ] VolumeGroup = [ cfg.IntOpt('build_interval', default=10, help='Time in seconds between volume availability checks.'), cfg.IntOpt('build_timeout', default=180, help='Timeout in seconds to wait for a volume to become' 'available.'), cfg.StrOpt('catalog_type', default='volume', help="Catalog type of the Volume Service"), cfg.BoolOpt('cinder_node_exist', default=True, help="Allow to run tests if cinder exist"), cfg.BoolOpt('multi_backend_enabled', default=False, help="Runs Cinder multi-backend test (requires 2 backends)"), cfg.StrOpt('backend1_name', default='BACKEND_1', help="Name of the backend1 (must be declared in cinder.conf)"), cfg.StrOpt('backend2_name', default='BACKEND_2', help="Name of the backend2 (must be declared in cinder.conf)"), ] ObjectStoreConfig = [ cfg.StrOpt('catalog_type', default='object-store', help="Catalog type of the Object-Storage service."), cfg.StrOpt('container_sync_timeout', default=120, help="Number of seconds to time on waiting for a container" "to container synchronization complete."), cfg.StrOpt('container_sync_interval', default=5, help="Number of seconds to wait while looping to check the" 84 User Guide "status of a container to container synchronization"), ] User Guide User guide has been moved to docs.mirantis.com. If you want to contribute, checkout the sources from github. Devops Guide Introduction Fuel-Devops is a sublayer between application and target environment (currently only supported under libvirt). This application is used for testing purposes like grouping virtual machines to environments, booting KVM VMs locally from the ISO image and over the network via PXE, creating, snapshotting and resuming back the whole environment in single action, create virtual machines with multiple NICs, multiple hard drives and many other customizations with a few lines of code in system tests. For sources please refer to fuel-devops repository on github. Installation The installation procedure can be implemented in two different ways (suppose you are using Ubuntu 12.04 or Ubuntu 14.04): • from deb packages (using apt) • from python packages (using PyPI) (also in virtualenv) Each of the above approaches is described in detail below. Before use one of them please install dependencies that are required in all cases: sudo apt-get install git \ postgresql \ postgresql-server-dev-all \ libyaml-dev \ libffi-dev \ python-dev \ python-libvirt \ python-pip \ qemu-kvm \ qemu-utils \ libvirt-bin \ ubuntu-vm-builder \ bridge-utils sudo apt-get update && sudo apt-get upgrade -y Devops installation from packages 1. Adding the repository, checking and applying latest updates wget -qO - http://mirror.fuel-infra.org/devops/ubuntu/Release.key | sudo apt-key add sudo add-apt-repository "deb http://mirror.fuel-infra.org/devops/ubuntu /" sudo apt-get update && sudo apt-get upgrade -y 2. Installing required packages 85 User Guide sudo apt-get install python-psycopg2 \ python-ipaddr \ python-libvirt \ python-paramiko \ python-django \ python-django-openstack \ python-libvirt \ python-django-south \ python-xmlbuilder \ python-mock \ python-devops Note Depending on your Linux distribution some of the above packages may not exists in your upstream repositories. In this case, please exclude them from the installation list and repeat step 2. Missing packages will be installed by python (from PyPI) during the next step Note In case of Ubuntu 12.04 LTS we need to update pip and Django<1.7: sudo pip install pip --upgrade hash -r sudo pip install Django\<1.7 --upgrade 3. Next, follow Configuration section Devops installation using PyPI The installation procedure should be implemented by following the next steps: 1. Install packages needed for building python eggs sudo apt-get install libpq-dev \ libgmp-dev 2. In case you are using Ubuntu 12.04 let's update pip, otherwise you can skip this step sudo pip install pip --upgrade hash -r 3. Install devops package using python setup tools. Clone fuel-devops and run setup.py git clone git://github.com/stackforge/fuel-devops.git cd fuel-devops sudo python ./setup.py install 4. Next, follow Configuration section Devops installation in virtualenv Installation procedure is the same as in the case of Devops installation using PyPI, but we should also configure virtualenv 1. Install packages needed for building python eggs sudo apt-get install python-virtualenv 86 User Guide 2. In case you are using Ubuntu 12.04 let's update pip and virtualenv, otherwise you can skip this step sudo pip install pip virtualenv --upgrade hash -r 4. Create virtualenv for the devops project virtualenv --system-site-packages <path>/fuel-devops-venv <path> represents the path where your Python virtualenv will be located. (e.g. ~/venv). If it is not specified, it will use the current working directory. 5. Activate virtualenv and install devops package using python setup tools . <path>/fuel-devops-venv/bin/activate pip install git+https://github.com/stackforge/fuel-devops.git --upgrade setup.py in fuel-devops repository does everything required. Hint You can also use virtualenvwrapper which can help you manage virtual environments 6. Next, follow Configuration section Configuration Basically devops requires that the following system-wide settings are configured: • Default libvirt storage pool is active (called 'default') • Current user must have permission to run KVM VMs with libvirt • PostgreSQL server running with appropriate grants and schema for devops • [Optional] Nested Paging is enabled Configuring libvirt pool Create libvirt's pool sudo virsh pool-define-as --type=dir --name=default --target=/var/lib/libvirt/images sudo virsh pool-autostart default sudo virsh pool-start default Permissions to run KVM VMs with libvirt with current user Give current user permissions to use libvirt (Do not forget to log out and log back in!) sudo usermod $(whoami) -a -G libvirtd,sudo Configuring Postgresql database Set local peers to be trusted by default and load fixtures sudo sed -ir sudo service django-admin django-admin 's/peer/trust/' /etc/postgresql/9.*/main/pg_hba.conf postgresql restart syncdb --settings=devops.settings migrate devops --settings=devops.settings If you install from python packages or use virtualenv 87 User Guide django-admin.py syncdb --settings=devops.settings django-admin.py migrate devops --settings=devops.settings Note Depending on your Linux distribution, django-admin may refer to system-wide django installed from package. If this happens you could get an exception that says that devops.settings module is not resolvable. To fix this, run django-admin.py (or django-admin) with a relative path ./bin/django-admin syncdb --settings=devops.settings ./bin/django-admin migrate devops --settings=devops.settings [Optional] Enabling Nested Paging This option is enabled by default in the KVM kernel module $ cat /etc/modprobe.d/qemu-system-x86.conf options kvm_intel nested=1 In order to be sure that this feature is enabled on your system, please run: sudo kvm-ok && cat /sys/module/kvm_intel/parameters/nested The result should be: INFO: /dev/kvm exists KVM acceleration can be used Y Environment creation via Devops + Fuel_main 1. Clone fuel main GIT repository git clone https://github.com/stackforge/fuel-main cd fuel-main/ 2. Install requirements If you use virtualenv . <path>/fuel-devops-venv/bin/activate pip install -r ./fuelweb_test/requirements.txt --upgrade If you do not use virtualenv just sudo pip install -r ./fuelweb_test/requirements.txt --upgrade 3. Check Configuration section 4. Prepare environment Download Fuel ISO from Nightly builds or build it yourself (please, refer to Building the Fuel ISO) Next, you need to define several variables for the future environment export ISO_PATH=<path_to_iso> export NODES_COUNT=<number_nodes> export ENV_NAME=<name_of_env> If you use virtualenv export VENV_PATH=<path>/fuel-devops-venv Alternatively, you can edit this file to set them as a default values 88 Fuel ISO build system fuelweb_test/settings.py Start tests by running this command ./utils/jenkins/system_tests.sh -t test -w $(pwd) -j fuelweb_test -i $ISO_PATH -o --group=se For more information about how tests work, read the usage information ./utils/jenkins/system_tests.sh -h Important notes for Sahara and Murano tests • It is not recommended to start tests without KVM. • For the best performance Put Sahara image savanna-0.3-vanilla-1.2.1-ubuntu-13.04.qcow2 (md5: 9ab37ec9a13bb005639331c4275a308d) in /tmp/ before start, otherwise (If Internet access is available) the image will download automatically. • Put Murano image ubuntu-murano-agent.qcow2 b0a0fdc0b4a8833f79701eb25e6807a3) in /tmp before start. (md5: • Running Murano tests on instances without an Internet connection will fail. • For Murano tests execute 'export SLAVE_NODE_MEMORY=5120' before starting. • Heat autoscale tests require the image F17-x86_64-cfntools.qcow2 afab0f79bac770d61d24b4d0560b5f70) be placed in /tmp before starting. (md5: Run single OSTF tests several times • Export environment variable OSTF_TEST_NAME. Example: export OSTF_TEST_NAME='Request list of networks' • Export environment variable OSTF_TEST_RETRIES_COUNT=120 OSTF_TEST_RETRIES_COUNT. Example: export • Execute test_ostf_repetable_tests from tests_strength package Run tests sh "utils/jenkins/system_tests.sh" -t test \ -w $(pwd) \ -j "fuelweb_test" \ -i "$ISO_PATH" \ -V $(pwd)/venv/fuelweb_test \ -o \ --group=create_delete_ip_n_times_nova_flat Fuel ISO build system Use the fuel-main repository to build Fuel components such as an ISO or an upgrade tarball. This repository contains a set of GNU Make build scripts. Quick start 1. You must use one of the following distributions to build Fuel components or the build process may fail. Note that build only works for x64 platforms. • Ubuntu 12.04 • Ubuntu 14.04 2. Check whether you have git installed in your system. To do that, use the following command: 89 Fuel ISO build system which git If git is not found, install it with the following command: apt-get install git 3. Clone the fuel-main git repository to the location where you will work. The root of your repo will be named fuel-main. In this example, it will be located under the ~/fuel directory: mkdir ~/fuel cd ~/fuel git clone https://github.com/stackforge/fuel-main.git cd fuel-main 4. Run the shell script: ./prepare-build-env.sh and wait until **prepare-build-env.sh** installs the Fuel build evironment on your computer. 5. After the script runs successfully, issue the following command to build a Fuel ISO: make iso 6. Use the following command to list the available make targets: make help For the full list of available targets with description, see Build targets section below. Build system structure Fuel consists of several components such as web interface, puppet modules, orchestration components, testing components. Source code of all those components is split into multiple git repositories like: • https://github.com/stackforge/fuel-web • https://github.com/stackforge/fuel-astute • https://github.com/stackforge/fuel-ostf • https://github.com/stackforge/fuel-library • https://github.com/stackforge/fuel-docs The main component of the Fuel build system is fuel-main directory. Fuel build processes are quite complicated, so to make the fuel-main code easily maintainable, it is split into a bunch of files and directories. Those files and directories contain independent (or at least almost independent) pieces of Fuel build system: • Makefile - the main Makefile which includes all other make modules. • config.mk - contains parameters used to customize the build process, specifying items such as build paths, upstream mirrors, source code repositories and branches, built-in default Fuel settings and ISO name. • rules.mk - defines frequently used macros. • repos.mk - contains make scripts to download the other repositories to develop Fuel components put into separate repos. • sandbox.mk - shell script definitions that create and destroy the special chroot environment required to build some components. For example, for building RPM packages, CentOS images we use CentOS chroot environment. 90 Fuel ISO build system • mirror - contains the code which is used to download all necessary packages from upstream mirrors and build new ones which are to be copied on Fuel ISO even if Internet connection is down. • puppet - contains the code used to pack Fuel puppet modules into a tarball that is afterwards put on Fuel ISO. • packages - contains DEB and RPM specs as well as make code for building those packages, included in Fuel DEB and RPM mirrors. • bootstrap - contains a make script intended to build CentOS-based miniroot image (a.k.a initrd or initramfs). • image - contains make scripts for building CentOS and Ubuntu images using the Fuel mirrors, built from the scripts in the mirror directory. The images are alternative to using the standard anaconda and debian installers. • docker - contains the make scripts to build docker containers, deployed on the Fuel Master node. • upgrade - contains make scripts for building Fuel upgrade tarball. • iso - contains make scripts for building Fuel ISO file. Fuel-main also contains a set of directories which are not directly related to Fuel build processes: • virtualbox - contains a set of shell scripts which allow one to deploy Fuel demo lab easily using VirtualBox. • utils - contains a set of utilities used for maintaining Fuel components. • fuelweb_test and fuelweb_ui_test - contain the code of Fuel system tests. Build targets • all - used for building all Fuel artifacts. Currently, it is an alias for iso target. • bootstrap - used for building in-memory bootstrap image which is used for auto-discovering. • mirror - used for building local mirrors (the copies of CentOS and Ubuntu mirrors which are then placed into Fuel ISO). They contain all necessary packages including those listed in requirements-.txt* files with their dependencies as well as those which are Fuel packages. Packages listed in requirements-.txt* files are downloaded from upstream mirrors while Fuel packages are built from source code. • iso - used for building Fuel ISO. If build succeeds, ISO is put into build/artifacts folder. • img - used for building Fuel flash stick image, binary copied to a flash stick. That stick is then used as a bootable device and contains Fuel ISO as well as some auxiliary boot files. • clean - removes build directory. • deep_clean - removes build directory and local mirror. Note that if you remove a local mirror, then next time the ISO build job will download all necessary packages again. So, the process goes faster if you keep local mirrors. You should also mind the following: it is better to run make deep_clean every time when building an ISO to make sure the local mirror is consistent. Customizing build process There are plenty of variables in make files. Some of them represent a kind of build parameters. They are defined in config.mk file: • TOP_DIR - a default current directory. All other build directories are relative to this path. • BUILD_DIR - contains all files, used during build process. By default, it is $(TOP_DIR)/build. • ARTS_DIR - contains $(BUILD_DIR)/artifacts. build • LOCAL_MIRROR - contains $(TOP_DIR)/local_mirror. 91 artifacts local such CentOS as and ISO and Ubuntu IMG files mirrors By By default, default, it is it is Fuel ISO build system • DEPS_DIR - contains build targets that depend on artifacts of the previous build jobs, placed there before build starts. By default, it is $(TOP_DIR)/deps. • ISO_NAME - a name of Fuel ISO without file extension: if ISO_NAME = MY_CUSTOM_NAME, then Fuel ISO file will be placed into $(MY_CUSTOM_NAME).iso. • ISO_PATH - used to specify Fuel ISO full path instead of defining just ISO name. By default, it is $(ARTS_DIR)/$(ISO_NAME).iso. • UPGRADE_TARBALL_NAME - defines $(UPGRADE_TARBALL_NAME).tar. the name of upgrade tarball. By default, it is • UPGRADE_TARBALL_PATH - used to define full upgrade tarball path. By default, it is $(ARTS_DIR)/$(UPGRADE_TARBALL_NAME).tar. • VBOX_SCRIPTS_NAME - defines the name of the archive with VirtualBox scripts. By default, it is placed into $(VBOX_SCRIPTS_NAME).zip. • VBOX_SCRIPTS_PATH - defines full path for VirtualBox scripts archive. By default, it is $(ARTS_DIR)/$(VBOX_SCRIPTS_NAME).zip • Fuel ISO contains some default settings for the Fuel Master node. These settings can be customized during Fuel Master node installation. One can customize those settings using the following variables: • MASTER_IP - the Fuel Master node IP address. By default, it is 10.20.0.2. • MASTER_NETMASK - Fuel Master node IP netmask. By default, it is 255.255.255.0. • MASTER_GW - Fuel Master node default gateway. By default, it is is 10.20.0.1. • MASTER_DNS - the upstream DNS location for the Fuel master node. FUel Master node DNS will redirect there all DNS requests that it is not able to resolve itself. By default, it is 10.20.0.1. Other options • BUILD_OPENSTACK_PACKAGES - list of Openstack packages to be rebuilt from source. • [repo]_REPO - remote source code repo. URL or git repository can be specified for each of the Fuel components. (FUELLIB, NAILGUN, ASTUTE, OSTF). • [repo]_COMMIT - source branch for each of the Fuel components to build. • [repo]_GERRIT_URL - gerrit repo. • [repo]_GERRIT_COMMIT - list of extra commits from gerrit. • [repo]_SPEC_REPO - repo for RPM/DEB specs of OpenStack packages. • [repo]_SPEC_COMMIT - branch for checkout. • [repo]_SPEC_GERRIT_URL - gerrit repo for OpenStack specs. • [repo]_SPEC_GERRIT_COMMIT - list of extra commits from gerrit for specs. • USE_MIRROR - pre-built mirrors from Fuel infrastructure. The following mirrors can be used: * ext (external mirror, available from outside of Mirantis network) * none (reserved for building local mirrors: in this case CentOS and Ubuntu packages will be fetched from upstream mirrors, so that it will make the build process much slower). • MIRROR_CENTOS - download CentOS packages from a specific remote repo. • MIRROR_UBUNTU - download Ubuntu packages from a specific remote repo. • MIRROR_DOCKER - download docker images from a specific remote url. • EXTRA_RPM_REPOS - extra repos with RPM packages. Each repo must be comma separated tuple with repo-name and repo-path: <first_repo_name>,<repo_path> <second_repo_name>,<second_repo_path> For example, qemu2,http://osci-obs.vm.mirantis.net:82/centos-fuel-5.1-stable-15943/centos/ libvirt,http://osci-obs.vm.mirantis.net:82/centos-fuel-5.1-stable-17019/centos/. 92 Fuel ISO build system S - extra repos with DEB packages. Each repo must consist of an url, distro and section parts. Repos must |<second_repo_path> For ory.mirantis.com/repos/ubuntu-fuel-5.1-stable-15955/ubuntu/|http://fuel-repository.mirantis.com/repos/ubuntu-fuel-5.1** FEATURE_GROUPS - options for the ISO. Combination of the following: • mirantis (use mirantis logos and logic) • experimental (allow experimental features on Fuel web UI) Note that if you want to add more packages to the Fuel Master node, you should update the requirements-rpm.txt and the requirements-deb.txt files. 93 create_attributes() (nailgun.objects.cluster.Cluster class method) Index (nailgun.objects.node.Node class method) A add_into_cluster() class method) (nailgun.objects.node.Node create_discover_notification() (nailgun.objects.node.Node class method) D add_pending_change() (nailgun.objects.node.Node class method) DELETE() (nailgun.api.v1.handlers.cluster.ClusterHandler method) add_pending_changes() (nailgun.objects.cluster.Cluster class method) all() (nailgun.objects.base.NailgunCollection class method) (nailgun.api.v1.handlers.notifications.NotificationHandler method) and_() (in module nailgun.objects.base) (nailgun.api.v1.handlers.release.ReleaseHandler method) Attributes (class in nailgun.objects.cluster) (nailgun.api.v1.handlers.release.ReleaseNetworksHandler method) C (nailgun.api.v1.handlers.tasks.TaskHandler method) check_field() (nailgun.objects.base.NailgunObject method) class E clear_pending_changes() (nailgun.objects.cluster.Cluster class method) eager() (nailgun.objects.base.NailgunCollection class method) Cluster (class in nailgun.objects.cluster) ClusterAttributesDefaultsHandler nailgun.api.v1.handlers.cluster) (class in ClusterAttributesHandler (class nailgun.api.v1.handlers.cluster) in ClusterCollection nailgun.objects.cluster) in (class delete() (nailgun.objects.base.NailgunObject class method) ClusterCollectionHandler (class nailgun.api.v1.handlers.cluster) in ClusterGeneratedData (class nailgun.api.v1.handlers.cluster) in ClusterHandler (class nailgun.api.v1.handlers.cluster) in collection (nailgun.api.v1.handlers.cluster.Cluste rCollectionHandler attribute) (nailgun.api.v1.handlers.node.NodeCollectionHandler attribute) eager_base() (nailgun.objects.base.NailgunCollection method) class eager_nodes_handlers() (nailgun.objects.node.NodeCollection method) class F filter_by() (nailgun.objects.base.NailgunCollection method) class filter_by_id_list() (nailgun.objects.base.NailgunCollection method) class filter_by_list() (nailgun.objects.base.NailgunCollection method) class (nailgun.api.v1.handlers.release.ReleaseCollectionHandler filter_by_not() attribute) (nailgun.objects.base.NailgunCollection method) create() (nailgun.objects.base.NailgunCollection class method) (nailgun.objects.base.NailgunObject method) class (nailgun.objects.cluster.Cluster method) class (nailgun.objects.node.Node class method) (nailgun.objects.release.Release method) class class G generate_fields() (nailgun.objects.cluster.Attributes class method) GET() (nailgun.api.v1.handlers.cluster.ClusterAtt ributesDefaultsHandler method) (nailgun.api.v1.handlers.cluster.ClusterAttributesHandler method) .api.v1.handlers.cluster.ClusterCollectionHandler (nailgun.api.v1.handlers.cluster.ClusterCollectionHandler method) method) .api.v1.handlers.cluster.ClusterGeneratedData (nailgun.api.v1.handlers.cluster.ClusterGeneratedData method) method) .api.v1.handlers.cluster.ClusterHandler method) (nailgun.api.v1.handlers.cluster.ClusterHandler method) .api.v1.handlers.disks.NodeDefaultsDisksHandler (nailgun.api.v1.handlers.disks.NodeDefaultsDisksHandler method) method) .api.v1.handlers.disks.NodeDisksHandler method) (nailgun.api.v1.handlers.disks.NodeDisksHandler method) .api.v1.handlers.disks.NodeVolumesInformationHandler (nailgun.api.v1.handlers.disks.NodeVolumesInformationHandler method) method) .api.v1.handlers.logs.LogEntryCollectionHandler (nailgun.api.v1.handlers.logs.LogEntryCollectionHandler method) method) .api.v1.handlers.logs.LogSourceByNodeCollectionHandler (nailgun.api.v1.handlers.logs.LogPackageHandler method) method) .api.v1.handlers.logs.LogSourceCollectionHandler (nailgun.api.v1.handlers.logs.LogSourceByNodeCollectionHandler method) method) .api.v1.handlers.network_configuration.NeutronNetworkConfigurationHandler (nailgun.api.v1.handlers.logs.LogSourceCollectionHandler method) ) (nailgun.api.v1.handlers.network_configuration.NetworkConfigurationVerifyH .api.v1.handlers.network_configuration.NovaNetworkConfigurationHandler method) ) (nailgun.api.v1.handlers.network_configuration.NeutronNetworkConfiguratio .api.v1.handlers.node.NodeCollectionHandler method) method) .api.v1.handlers.node.NodeCollectionNICsDefaultHandler (nailgun.api.v1.handlers.network_configuration.NeutronNetworkConfiguratio method) method) .api.v1.handlers.node.NodeNICsDefaultHandler method) (nailgun.api.v1.handlers.network_configuration.NovaNetworkConfigurationH .api.v1.handlers.node.NodeNICsHandler method) method) .api.v1.handlers.node.NodesAllocationStatsHandler method) (nailgun.api.v1.handlers.network_configuration.NovaNetworkConfigurationVe .api.v1.handlers.notifications.NotificationHandler method) method) .api.v1.handlers.release.ReleaseCollectionHandler (nailgun.api.v1.handlers.network_configuration.ProviderHandler method) method) .api.v1.handlers.release.ReleaseHandler method) (nailgun.api.v1.handlers.node.NodeCollectionHandler method) .api.v1.handlers.release.ReleaseNetworksHandler (nailgun.api.v1.handlers.node.NodeCollectionNICsDefaultHandler method) method) .api.v1.handlers.tasks.TaskCollectionHandler (nailgun.api.v1.handlers.node.NodeCollectionNICsHandler method) method) .api.v1.handlers.tasks.TaskHandler method) (nailgun.api.v1.handlers.node.NodeNICsDefaultHandler method) .api.v1.handlers.version.VersionHandler method) (nailgun.api.v1.handlers.node.NodeNICsHandler method) get_attributes() class method) (nailgun.objects.cluster.Cluster (nailgun.api.v1.handlers.node.NodesAllocationStatsHandler method) (nailgun.api.v1.handlers.notifications.NotificationHandler method) get_by_mac_or_uid() (nailgun.objects.node.Node (nailgun.api.v1.handlers.release.ReleaseCollectionHandler method) class method) (nailgun.api.v1.handlers.release.ReleaseHandler method) get_by_meta() (nailgun.objects.node.Node class method) (nailgun.api.v1.handlers.release.ReleaseNetworksHandler method) get_by_uid() (nailgun.objects.base.NailgunObject (nailgun.api.v1.handlers.tasks.TaskCollectionHandler method) class method) (nailgun.api.v1.handlers.tasks.TaskHandler method) get_default_editable_attributes() (nailgun.api.v1.handlers.version.VersionHandler method) (nailgun.objects.cluster.Cluster class method) get_objects_list_or_404() (nailgun.api.v1.handler get_ifaces_for_network_in_cluster() s.cluster.ClusterAttributesDefaultsHandler (nailgun.objects.cluster.Cluster class method) method) get_kernel_params() (nailgun.objects.node.Node (nailgun.api.v1.handlers.cluster.ClusterAttributesHandler class method) method) get_network_manager() (nailgun.api.v1.handlers.cluster.ClusterCollectionHandler (nailgun.objects.cluster.Cluster class method) method) (nailgun.objects.node.Node class method) (nailgun.api.v1.handlers.cluster.ClusterGeneratedData get_object_or_404() (nailgun.api.v1.handlers.clu method) ster.ClusterAttributesDefaultsHandler method) (nailgun.api.v1.handlers.cluster.ClusterHandler method) (nailgun.api.v1.handlers.cluster.ClusterAttributesHandler (nailgun.api.v1.handlers.disks.NodeDefaultsDisksHandler method) method) pi.v1.handlers.disks.NodeDisksHandler method) (nailgun.api.v1.handlers.disks.NodeVolumesInformationHandler class metho pi.v1.handlers.disks.NodeVolumesInformationHandler (nailgun.api.v1.handlers.logs.LogEntryCollectionHandler method) class method) pi.v1.handlers.logs.LogEntryCollectionHandler (nailgun.api.v1.handlers.logs.LogPackageHandler method) class method) pi.v1.handlers.logs.LogPackageHandler method) (nailgun.api.v1.handlers.logs.LogSourceByNodeCollectionHandler class meth pi.v1.handlers.logs.LogSourceByNodeCollectionHandler (nailgun.api.v1.handlers.logs.LogSourceCollectionHandler method) class method) pi.v1.handlers.logs.LogSourceCollectionHandler (nailgun.api.v1.handlers.network_configuration.NetworkConfigurationVerifyH method) method) pi.v1.handlers.network_configuration.NetworkConfigurationVerifyHandler (nailgun.api.v1.handlers.network_configuration.NeutronNetworkConfiguratio method) pi.v1.handlers.network_configuration.NeutronNetworkConfigurationHandler (nailgun.api.v1.handlers.network_configuration.NeutronNetworkConfiguratio class method) pi.v1.handlers.network_configuration.NeutronNetworkConfigurationVerifyHandler (nailgun.api.v1.handlers.network_configuration.NovaNetworkConfigurationH method) pi.v1.handlers.network_configuration.NovaNetworkConfigurationHandler (nailgun.api.v1.handlers.network_configuration.NovaNetworkConfigurationVe class method) pi.v1.handlers.network_configuration.NovaNetworkConfigurationVerifyHandler (nailgun.api.v1.handlers.network_configuration.ProviderHandler class metho pi.v1.handlers.network_configuration.ProviderHandler (nailgun.api.v1.handlers.node.NodeCollectionHandler method) class method) pi.v1.handlers.node.NodeCollectionHandler (nailgun.api.v1.handlers.node.NodeCollectionNICsDefaultHandler method) class meth pi.v1.handlers.node.NodeCollectionNICsDefaultHandler (nailgun.api.v1.handlers.node.NodeCollectionNICsHandler method) class method) pi.v1.handlers.node.NodeCollectionNICsHandler (nailgun.api.v1.handlers.node.NodeNICsDefaultHandler method) class method) pi.v1.handlers.node.NodeNICsDefaultHandler (nailgun.api.v1.handlers.node.NodeNICsHandler method) class method) pi.v1.handlers.node.NodeNICsHandler method) (nailgun.api.v1.handlers.node.NodesAllocationStatsHandler class method) pi.v1.handlers.node.NodesAllocationStatsHandler (nailgun.api.v1.handlers.notifications.NotificationHandler method) class method) pi.v1.handlers.notifications.NotificationHandler (nailgun.api.v1.handlers.release.ReleaseCollectionHandler method) class method) pi.v1.handlers.release.ReleaseCollectionHandler (nailgun.api.v1.handlers.release.ReleaseHandler method) class method) pi.v1.handlers.release.ReleaseHandler method) (nailgun.api.v1.handlers.release.ReleaseNetworksHandler class method) pi.v1.handlers.release.ReleaseNetworksHandler (nailgun.api.v1.handlers.tasks.TaskCollectionHandler method) class method) pi.v1.handlers.tasks.TaskCollectionHandler (nailgun.api.v1.handlers.tasks.TaskHandler method) class method) pi.v1.handlers.tasks.TaskHandler method) (nailgun.api.v1.handlers.version.VersionHandler class method) pi.v1.handlers.version.VersionHandler method) I H http() (nailgun.api.v1.handlers.cluster.ClusterAtt ributesDefaultsHandler class method) is_deployable() (nailgun.objects.release.Release class method) L (nailgun.api.v1.handlers.cluster.ClusterAttributesHandler class method) lock_for_update() (nailgun.objects.base.NailgunCollection class (nailgun.api.v1.handlers.cluster.ClusterCollectionHandler method) class method) lock_nodes() (nailgun.api.v1.handlers.cluster.ClusterGeneratedData (nailgun.objects.node.NodeCollection class class method) method) (nailgun.api.v1.handlers.cluster.ClusterHandler class LogEntryCollectionHandler (class in method) nailgun.api.v1.handlers.logs) (nailgun.api.v1.handlers.disks.NodeDefaultsDisksHandler LogPackageHandler (class in class method) nailgun.api.v1.handlers.logs) (nailgun.api.v1.handlers.disks.NodeDisksHandler class LogSourceByNodeCollectionHandler (class in method) nailgun.api.v1.handlers.logs) LogSourceCollectionHandler nailgun.api.v1.handlers.logs) (class in NodeCollectionHandler nailgun.api.v1.handlers.node) (class in M NodeCollectionNICsDefaultHandler nailgun.api.v1.handlers.node) merged_attrs() (nailgun.objects.cluster.Attributes class method) NodeCollectionNICsHandler nailgun.api.v1.handlers.node) (class in merged_attrs_values() (nailgun.objects.cluster.Attributes class method) NodeDefaultsDisksHandler nailgun.api.v1.handlers.disks) (class in model attribute) NodeDisksHandler (class nailgun.api.v1.handlers.disks) in NodeNICsDefaultHandler nailgun.api.v1.handlers.node) in (nailgun.objects.base.NailgunObject (nailgun.objects.cluster.Attributes attribute) (nailgun.objects.cluster.Cluster attribute) (nailgun.objects.node.Node attribute) (nailgun.objects.release.Release attribute) (nailgun.objects.release.ReleaseOrchestratorData attribute) move_roles_to_pending_roles() (nailgun.objects.node.Node class method) N nailgun.api.v1.handlers.cluster (module) nailgun.api.v1.handlers.disks (module) nailgun.api.v1.handlers.logs (module) nailgun.api.v1.handlers.network_configuration (module) nailgun.api.v1.handlers.node (module) nailgun.api.v1.handlers.notifications (module) nailgun.api.v1.handlers.release (module) nailgun.api.v1.handlers.tasks (module) nailgun.api.v1.handlers.version (module) nailgun.objects.base (module) nailgun.objects.cluster (module) nailgun.objects.node (module) nailgun.objects.release (module) NailgunCollection (class in nailgun.objects.base) (class (class in NodeNICsHandler (class nailgun.api.v1.handlers.node) in NodesAllocationStatsHandler nailgun.api.v1.handlers.node) in (class NodeVolumesInformationHandler nailgun.api.v1.handlers.disks) (class NotificationHandler (class nailgun.api.v1.handlers.notifications) in in NovaNetworkConfigurationHandler (class in nailgun.api.v1.handlers.network_configuration) NovaNetworkConfigurationVerifyHandler (class in nailgun.api.v1.handlers.network_configuration) O or_() (in module nailgun.objects.cluster) order_by() (nailgun.objects.base.NailgunCollection method) class P PATCH() (nailgun.api.v1.handlers.cluster.Cluster AttributesHandler method) POST() (nailgun.api.v1.handlers.cluster.ClusterC ollectionHandler method) (nailgun.api.v1.handlers.node.NodeCollectionHandler method) NailgunObject (class in nailgun.objects.base) (nailgun.api.v1.handlers.release.ReleaseCollectionHandler NetworkConfigurationVerifyHandler (class in method) nailgun.api.v1.handlers.network_configuration) (nailgun.api.v1.handlers.release.ReleaseNetworksHandler NeutronNetworkConfigurationHandler (class in method) nailgun.api.v1.handlers.network_configuration) (nailgun.api.v1.handlers.tasks.TaskCollectionHandler NeutronNetworkConfigurationVerifyHandler method) (class in prepare_for_deployment() nailgun.api.v1.handlers.network_configuration) (nailgun.objects.node.NodeCollection class Node (class in nailgun.objects.node) method) NodeCollection (class in nailgun.objects.node) prepare_for_provisioning() (nailgun.objects.node.NodeCollection method) class ProviderHandler (class in nailgun.api.v1.handlers.network_configuration) (nailgun.objects.node.Node attribute) (nailgun.objects.release.Release attribute) PUT() (nailgun.api.v1.handlers.cluster.ClusterAtt ributesDefaultsHandler method) pi.v1.handlers.cluster.ClusterAttributesHandler method) (nailgun.objects.release.ReleaseOrchestratorData attribute) search_by_interfaces() (nailgun.objects.node.Node class method) pi.v1.handlers.cluster.ClusterHandler method) pi.v1.handlers.disks.NodeDisksHandler method) serializer attribute) pi.v1.handlers.logs.LogPackageHandler method) (nailgun.objects.base.NailgunObject (nailgun.objects.cluster.Cluster attribute) pi.v1.handlers.network_configuration.NetworkConfigurationVerifyHandler (nailgun.objects.node.Node attribute) pi.v1.handlers.network_configuration.NeutronNetworkConfigurationVerifyHandler (nailgun.objects.release.Release attribute) (nailgun.objects.release.ReleaseOrchestratorData pi.v1.handlers.network_configuration.NovaNetworkConfigurationHandler attribute) set_primary_role() pi.v1.handlers.network_configuration.NovaNetworkConfigurationVerifyHandler (nailgun.objects.cluster.Cluster class method) set_primary_roles() (nailgun.objects.cluster.Cluster class method) pi.v1.handlers.node.NodeCollectionHandler method) pi.v1.handlers.node.NodeCollectionNICsHandler method) pi.v1.handlers.node.NodeNICsHandler method) should_assign_public_to_all_nodes() (nailgun.objects.cluster.Cluster class method) pi.v1.handlers.notifications.NotificationHandler method) pi.v1.handlers.release.ReleaseHandler method) pi.v1.handlers.release.ReleaseNetworksHandler method) pi.v1.handlers.tasks.TaskHandler method) (nailgun.api.v1.handlers.release.ReleaseHandler attribute) Release (class in nailgun.objects.release) (class in ReleaseCollectionHandler (class nailgun.api.v1.handlers.release) in ReleaseHandler (class nailgun.api.v1.handlers.release) in ReleaseNetworksHandler (class nailgun.api.v1.handlers.release) in ReleaseOrchestratorData nailgun.objects.release) in (class (nailgun.objects.node.Node S save() (nailgun.objects.base.NailgunObject class method) schema attribute) (nailgun.objects.base.NailgunObject (nailgun.objects.cluster.Cluster attribute) (nailgun.api.v1.handlers.release.ReleaseNetworksHandler attribute) (nailgun.objects.base.NailgunCollection attribute) (nailgun.objects.cluster.ClusterCollection attribute) (nailgun.objects.node.NodeCollection attribute) remove_from_cluster() (nailgun.objects.node.Node class method) reset_to_discover() class method) should_have_public() (nailgun.objects.node.Node class method) single (nailgun.api.v1.handlers.cluster.ClusterHandler attribute) R ReleaseCollection nailgun.objects.release) set_volumes() (nailgun.objects.node.Node class method) (nailgun.objects.release.ReleaseCollection attribute) T TaskCollectionHandler (class nailgun.api.v1.handlers.tasks) in TaskHandler (class nailgun.api.v1.handlers.tasks) in to_dict() (nailgun.objects.base.NailgunObject class method) to_json() (nailgun.objects.base.NailgunCollection class method) (nailgun.objects.base.NailgunObject method) class to_list() (nailgun.objects.base.NailgunCollection class method) U update() (nailgun.objects.base.NailgunObject class method) (nailgun.objects.cluster.Cluster method) class (nailgun.objects.node.Node class method) (nailgun.objects.release.Release method) class update_by_agent() class method) (nailgun.objects.node.Node update_interfaces() class method) (nailgun.objects.node.Node update_nodes() class method) (nailgun.objects.cluster.Cluster update_pending_roles() (nailgun.objects.node.Node class method) update_roles() (nailgun.objects.node.Node class method) (nailgun.objects.release.Release method) update_volumes() class method) class (nailgun.objects.node.Node V VersionHandler (class nailgun.api.v1.handlers.version) in Python Module Index n nailgun nailgun.api.v1.handlers.cluster nailgun.api.v1.handlers.disks nailgun.api.v1.handlers.logs nailgun.api.v1.handlers.network_configuration nailgun.api.v1.handlers.node nailgun.api.v1.handlers.notifications nailgun.api.v1.handlers.release nailgun.api.v1.handlers.tasks nailgun.api.v1.handlers.version nailgun.objects.base nailgun.objects.cluster nailgun.objects.node nailgun.objects.release
© Copyright 2024