Khaleesi Best Practices Guide¶
The purpose of this guide is to lay out the coding standards and best practices to be applied when working with Khaleesi. These best practices are specific to Khlaeesi but should be in line with general Ansible guidelines.
- Each section includes:
- A ‘Rule’ which states the best practice to apply
- Explanations and notable exceptions
- Examples of code applying the rule and, if applicable, examples of where the exceptions would hold
General Best Practices¶
Rule: Whitespace and indentation - Use 4 spaces.¶
Ensure that you use 4 spaces, not tabs, to separate each level of indentation.
Examples:
# BEST_PRACTICES_APPLIED
- name: set plan values for plan based ceph deployments
shell: >
source {{ instack_user_home }}/stackrc;
openstack management plan set {{ overcloud_uuid }}
-P Controller-1::CinderEnableIscsiBackend=false;
when: installer.deploy.type == 'plan'
Rule: Parameter Format - Use the YAML dictionary format when 3 or more parameters are being passed.¶
When several parameters are being passed in a module, it is hard to see exactly what value each parameter is getting. It is preferable to use the Ansible YAML syntax to pass in parameters so that it is clear what values are being passed for each parameter.
Examples:
# Step with all arguments passed in one line
- name: create .ssh dir
file: path=/home/{{ provisioner.remote_user }}/.ssh mode=0700 owner=stack group=stack state=directory
# BEST_PRACTICE_APPLIED
- name: create .ssh dir
file:
path: /home/{{ provisioner.remote_user }}/.ssh
mode: 0700
owner: stack
group: stack
state: directory
Rule: Line Length - Keep text under 100 characters per line.¶
For ease of readability, keep text to a uniform length of 100 characters or less. Some modules are known to have issues with multi-line formatting and should be commented on if it is an issue within your change.
Examples:
# BEST_PRACTICE_APPLIED
- name: set plan values for plan based ceph deployments
shell: >
source {{ instack_user_home }}/stackrc;
source {{ instack_user_home }}/deploy-nodesrc;
openstack management plan set {{ overcloud_uuid }}
-P Controller-1::CinderEnableIscsiBackend=false
-P Controller-1::CinderEnableRbdBackend=true
-P Controller-1::GlanceBackend=rbd
-P Compute-1::NovaEnableRbdBackend=true;
when: installer.deploy.type == 'plan'
# EXCEPTION: - When a module breaks from multi-line use, add a comment to indicate it
# The long line in this task fails when broken down
- name: copy over common environment file (virt)
local_action: >
shell pushd {{ base_dir }}/khaleesi; rsync --delay-updates -F --compress --archive --rsh \
"ssh -F ssh.config.ansible -S none -o StrictHostKeyChecking=no" \
{{base_dir}}/khaleesi-settings/hardware_environments/common/plan-parameter-neutron-bridge.yaml undercloud:{{ instack_user_home }}/plan-parameter-neutron-bridge.yaml
Rule: Using Quotes - Use single quotes.¶
Use single quotes throughout playbooks except when double quotes are required
for shell
commands or enclosing when
statements.
Examples:
# BEST_PRACTICE_APPLIED
- name: get floating ip address
register: floating_ip_result
shell: >
source {{ instack_user_home }}/overcloudrc;
neutron floatingip-show '{{ floating_ip.stdout }}' | grep 'ip_address' | sed -e 's/|//g';
# EXCEPTION - shell command uses both single and double quotes
- name: copy instackenv.json to root dir
shell: >
'ssh -t -o "StrictHostKeyChecking=no" {{ provisioner.host_cloud_user }}@{{ floating_ip.stdout }} \
"sudo cp /home/{{ provisioner.host_cloud_user }}/instackenv.json /root/instackenv.json"'
when: provisioner.host_cloud_user != 'root'
# EXCEPTION - enclosing a ``when`` statement
- name: copy instackenv.json to root dir
shell: >
'ssh -t -o "StrictHostKeyChecking=no" {{ provisioner.host_cloud_user }}@{{ floating_ip.stdout }} \
"sudo cp /home/{{ provisioner.host_cloud_user }}/instackenv.json /root/instackenv.json"'
when: "provisioner.host_cloud_user != {{ user }}"
Rule: Order of Arguments - Keep argument order consistent within a playbook.¶
The order of arguments is:
tasks:
- name:
hosts:
sudo:
module:
register:
retries:
delay:
until:
ignore_errors:
with_items:
when:
Warning
While name
is not required, it is an Ansible best practice, and a Khaleesi best
practice, to name all tasks.
Examples:
# BEST_PRACTICE_APPLIED - polling
- name: poll for heat stack-list to go to COMPLETE
shell: >
source {{ instack_user_home }}/stackrc;
heat stack-list;
register: heat_stack_list_result
retries: 10
delay: 180
until: heat_stack_list_result.stdout.find("COMPLETE") != -1
when: node_to_scale is defined
# BEST_PRACTICE_APPLIED - looping through items
- name: remove any yum repos not owned by rpm
sudo: yes
shell: rm -Rf /etc/yum.repos.d/{{ item }}
ignore_errors: true
with_items:
- beaker-*
Rule: Adding Workarounds - Create bug reports and flags for all workarounds.¶
More detailed information and examples on working with workarounds in Khaleesi can be found in the documentation on Handling Workarounds.
Rule: Ansible Modules - Use Ansible modules over shell
where available.¶
The generic shell
module should be used only when there is not a suitable Ansible module
available to do the required steps. Use the command
module when a step requires a single
bash command.
Examples:
# BEST_PRACTICE_APPLIED - using Ansible 'git' module rather than 'shell: git clone'
- name: clone openstack-virtual-baremetal repo
git:
repo=https://github.com/cybertron/openstack-virtual-baremetal/
dest={{instack_user_home}}/openstack-virtual-baremetal
# BEST_PRACTICE_APPLIED - using Openstack modules that have checks for redundancy or
# existing elements
- name: setup neutron network for floating ips
register: public_network_uuid_result
quantum_network:
state: present
auth_url: '{{ get_auth_url_result.stdout }}'
login_username: admin
login_password: '{{ get_admin_password_result.stdout }}'
login_tenant_name: admin
name: '{{ installer.network.name }}'
provider_network_type: '{{ hw_env.network_type }}'
provider_physical_network: '{{ hw_env.physical_network }}'
provider_segmentation_id: '{{ hw_env.ExternalNetworkVlanID }}'
router_external: yes
shared: no
# EXCEPTION - using shell as there are no Ansible modules yet for updating nova quotas
- name: set neutron subnet quota to unlimited
ignore_errors: true
shell: >
source {{ instack_user_home }}/overcloudrc;
neutron quota-update --subnet -1;
neutron quota-update --network -1;
Rule: Scripts - Use scripts rather than shell for lengthy or complex bash operations.¶
Scripts can hide output details and debugging scripts requires the user to look in multiple
directories for the code involved. Consider using scripts over shell
if the step in Ansible
requires multiple lines (more than ten), involves complex logic, or is called more than once.
Examples:
# BEST_PRACTICE_APPLIED - calling Beaker checkout script,
# keeps the complexity of Beaker provisioning in a standalone script
- name: provision beaker machine with kerberos auth
register: beaker_job_status
shell: >
chdir={{base_dir}}/khaleesi-settings
{{base_dir}}/khaleesi-settings/beakerCheckOut.sh
--arch={{ provisioner.beaker_arch }}
--family={{ provisioner.beaker_family }}
--distro={{ provisioner.beaker_distro }}
--variant={{ provisioner.beaker_variant }}
--hostrequire=hostlabcontroller={{ provisioner.host_lab_controller }}
--task=/CoreOS/rhsm/Install/automatjon-keys
--keyvalue=HVM=1
--ks_meta=ksdevice=link
--whiteboard={{ provisioner.whiteboard_message }}
--job-group={{ provisioner.beaker_group }}
--machine={{ lookup('env', 'BEAKER_MACHINE') }}
--timeout=720;
async: 7200
poll: 180
when: provisioner.beaker_password is not defined
Rule - Roles - Use roles for generic tasks which are applied across installers, provisioners, or testers.¶
Roles should be used to avoid code duplication. When using roles, take care to use debug steps and print appropriate code output to allow users to trace the source of errors since the exact steps are not visible directly in the playbook run. Please review the Ansibles official best practices documentation for more information regarding role structure.
Examples:
# BEST_PRACTICE_APPLIED - validate role that can be used with multiple installers
https://github.com/redhat-openstack/khaleesi/tree/master/roles/validate_openstack
RDO-Manager Specific Best Practices¶
The following rules apply to RDO-Manager specific playbooks and roles.
Rule: Step Placement - Place a step under the playbook directory named for where it will be executed.¶
The RDO-Manager related playbooks have the following directory structure:
|-- installer
|-- rdo-manager
|-- overcloud
|-- undercloud
| -- post-deploy
|-- rdo-manager
These guidelines are used when deciding where to place new steps:
undercloud
- any step that can be executed without the overcloudovercloud
- any step that is used to deploy the overcloudpost-deploy
- always a standalone playbook - steps executed once the overcloud is deployed
Rule: Idempotency - Any step executed post setup should be idempotent.¶
RDO-Manager has some set up steps that cannot be run multiple times without cleaning up the environment. Any step added after setup should be able to rerun without causing damage. Defensive programming conditions, that check for existence or availability etc. and modify when or how a step is run, can be added to ensure playbooks remain idempotent.
Examples:
# BEST_PRACTICE_APPLIED - using Ansible modules that check for existing elements
- name: create provisioning network
register: provision_network_uuid_result
quantum_network:
state: present
auth_url: "{{ get_auth_url_result.stdout }}"
login_username: admin
login_password: "{{ get_admin_password_result.stdout }}"
login_tenant_name: admin
name: "{{ tmp.node_prefix }}provision"
# BEST_PRACTICE_APPLIED - defensive programming,
# ignoring errors from creating a flavor that already exists
- name: create baremetal flavor
shell: >
source {{ instack_user_home }}/overcloudrc;
nova flavor-create baremetal auto 6144 50 2;
ignore_errors: true
Applying these Best Practices and Guidelines¶
Before submitting a review for Khaleesi please review your changes to ensure they follow with the best practices outlined above.
Contributing to this Guide¶
Additional best practices and suggestions for improvements to the coding standards are welcome. To contribute to this guide, please review contribution documentation and submit a review to GerritHub.