2015-03-10

As OpenStack matures as a solution, there is a growing need to effectively deploy OpenStack in a prescriptive manner.

To achieve this, numerous deployers have adopted Puppet to be their configuration tool of choice and it has grown to be the most widely used tool when deploying OpenStack[1].

The community effort to automate the deployment of OpenStack using Puppet has been open-sourced and is captured in Stackforge[2], where modules to configure various services can be found. Additionally, Puppetlabs officially supports the modules and releases them on their own repository[3].

People contributing to them usually come from varying backgrounds – both development and operations. That means:

They understand how OpenStack actually works.

They know how to deploy OpenStack.

They have feedback from the field.

Most of them are using OpenStack and very often at scale.

Taking the above into consideration, you can understand why OpenStack Puppet modules are able to deploy OpenStack at scale, in a very flexible way: the OpenStack Puppet modules benefit from the feedback of people deploying on real use-cases.

OpenStack is growing very fast; new projects are joining the OpenStack domain frequently. To easily deploy them in your OpenStack environment, you should consider to use Puppet. This article aims to help people wanting to create an OpenStack Puppet module that could be used by the whole community.

As a reference, the following will describe the process of contributing a Puppet module using an existing project: puppet-gnocchi.

Announcing your project

A good idea before creating a new module is to announce your project on the OpenStack Puppet mailing-list [4] to see if someone is already working on it. In my experience, it happens quite often that people make their own modules if they don’t find it on Stackforge but are too shy to publish them upstream.

Typically, It is simpler to start the project in a personal repository — either an individual Github namespace or one tied to your employer — to have the basic structure and then migrate it to Stackforge once it is tested and the code is clean enough accept contributions from the community.

Hosting the code

OpenStack provides a dedicated namespace for external projects: Stackforge [5].

StackForge is the method that OpenStack-related projects can be consumed by and make use of the OpenStack management infrastructure. If you want to allow the community to contribute to your module, hosting it on Stackforge is required. Please read this official documentation to manage new projects in Stackforge: http://docs.openstack.org/infra/manual/creators.html

The following offers a quick overview of the steps required to add your module to the OpenStack infrastructure environment:

Add the module to gerrit/projects.yaml (following other Puppet modules). Note that Gerrit ACL should be “/home/gerrit2/acls/stackforge/puppet-modules.config” to allow reviews from the existing OpenStack Puppet core team.

add the module to Gerritbot in gerritbot/channels.yaml so #puppet-openstack IRC channel (hosted on freenode) will have notifications on each patchset.

create Jenkins jobs in patching jenkins/jobs/projects.yaml

configure Zuul to gate the modules, in zuul/layout.yaml

Once this is done, you can contact someone from OpenStack Puppet core team (#puppet-openstack on IRC) to validate the new module and the OpenStack infrastructure core team (#openstack-infra on IRC) to validate the new project configuration.

Licensing

Following OpenStack policy, all OpenStack Puppet modules are Apache 2.0 licensed [6].

Module structure

This is an example of the basic directory/file structure of any module:

examples

Maintainers usually create some examples of hieradata and manifests to deploy the services. It is often a good starting point for newcomers interested in deploying the module.

lib

Contains functions, providers, types, and custom facts.

Each module should contain at least one provider, which aims to configure the service itself with inifile.

This way, you could configure logging like this:

manifests

Contains all the classes and defines needed to configure the service.

A good practice is to split the Puppet code in different functions/roles.

Example:

init: everything that is common across all nodes (logging, basic packages, etc)

params: contains platform dependents variables, like service/package names or file paths.

db/

mysql/postgresql: configure the service database, user and permissions

sync: manage the service db-sync

keystone/

auth: manage Keystone resources (tenant, user, service, endpoint for the service)

api: configure API service, so everything specific to API service itself should land here.

storage/

ceph/swift/file: configure the selected backend storage for Gnocchi.

In this way, the module is easy to read and debug if any issues arise. By splitting the bits, we add more flexibility both for deployers and for developers wanting to add more features.

spec

This is where we put the tests (can be generated by rspec-puppet-init):

spec/

acceptance (optional) : beaker-rspec tests

classes/ : test the Puppet classes

defines/ : test the Puppet defines

unit/ : test functions, providers, types, custom facts (everything that is Ruby)

More details in the “Testing” section.

.fixtures.yml

Contains the modules list that rspec will need to pull before running the tests.

.gitignore

Contains everything you don’t want to commit:

It’s usually related to the files created when you run tests in the directory.

Gemfile

Contains Gem dependencies. For rspec, note we currently work on

Also, we add puppet-lint-param-docs to ensure to have full documentation coverage on Puppet parameters.

LICENSE

Just copy/paste the Apache 2.0 manifest.

README

This documentation is generally a first start when approaching a Puppet module (or any project in general).

Puppet modules often use this canevas:

More documentation you will provide, more contributions and users you will have.

Rakefile

It contains the tests that we will execute with Rake. It usually contains spec and lint.

metadata.json

This file is crucial because it describes the way of publishing this module

It contains:

name, version, author, summary, license, source, URL

requirements: Puppet 3.x is commonly used

Operating System support: Debian/Ubuntu & Redhat/Fedora are a good start

dependencies: modules can be supported by Stackforge or Puppetlabs. It’s very rare to need another source.

Coding style

To ensure the Puppet modules are easily consumable, a few rules were defined as best practices:

Default values for parameters should be the same as the OpenStack code suggests. There are some exceptions, for example, when a bug is reported in the official project. You can have a look at OpenStack documentation.

If you try to express something complex with Exec, you should consider writing a Puppet provider/type for it.

A lot of parameters are common across OpenStack modules. Don’t re-invent their name but rather follow the naming paradigm other modules use (database, rabbitmq, etc).

Classes usually don’t inherit from another class.

A manifest has 100% coverage on parameters documentation.

Same rule for testing (rspec + unit).

If we use “Define” to write a function, we may want to create a manifest to create the resources (with create_resources), so we can manage the actual resource with Hiera [7].

Validate parameters: if we expect an array, ensure the parameter is an array. Use Puppet fail if something should block the deployment (example: in Juno, Neutron L3 Agent could not work with both DVR & HA enabled, so we checked the parameters and fail if both modes were enabled).

Support deprecation: if you deprecate a parameter or a class, we have to support it for at least one release cycle. Use Puppet warnings to show the deprecated parameter or class and apply the old configuration appropriately.

If some code can be reused in another module, it’s probably something that could land in puppet-openstacklib. It can be a provider, a class or a define.

puppet-stdlib is your friend: use it to compute some values, to validate parameters and also to avoid resource duplication (with ensure_resource).

Avoid template usage, especially for configuration files. Using templates instead of providers reduces flexibility and increases the complexity in how we manage the configuration. Except in rare use-cases (puppet-horizon), don’t use it.

Don’t try to fix package dependencies issues. If a package is missing as a dependency, you should report the bug in upstream packaging bugtracker.

Don’t manage user/groups, it’s done by packaging already.

Add “secret => true;” to a password parameter, so it won’t appear in logs.

When required, put some parameter mandatory by not setting a default value. Be careful with it though.

I’m sure I missed some rules, but anyway, these are the most important in my mind.

Testing

I’m very surprised to see how many Puppet modules are written without tests.

Writing an OpenStack Puppet module without tests is not an option and each contribution without tests and documentation will be automatically rejected. This convention allows us to maintain proper code quality and usability.

Our modules support Puppet 3.3, 3.4, 3.6 and 3.7. To verify the Puppet manifests, we use puppet-rspec [8] against these Puppet versions.

To run tests locally, run the following commands:

A good rspec test should run against both Debian and RedHat distributions and support different contexts (ie. use-cases). Here is an example testing the Gnocchi API service:

To ensure that the Puppet modules respect the Puppet style guide, we use puppet-lint [9], which validates the module conforms to a common set of conventions and is consistent with existing modules. Here is an example of puppet-lint output:

I personally use VI with puppet plugin (syntastic) so I can see lint warnings & errors directly when I write Puppet code. Emacs also comes with similar modules.

Conclusion

The benefits of deploying OpenStack using Puppet are grand but requires some effort. Ultimately writing an OpenStack Puppet module requires:

a good understanding of how the OpenStack component actually works: what services are included and the configuration files and the options that can be set.

skills in Puppet, but not much. Our modules usually do the same thing, we rarely re-invent the wheel. The tricky thing is usually the Puppet providers that need to talk with OpenStack APIs (often used to manage OpenStack resources, like networks, images, etc).

testing… Before publishing the module, test it functionally in your environment to ensure at least it works

Feel free to join us on #puppet-openstack (freenode) for any question or feedback.

Links:

[1] OpenStack survey

[2] https://github.com/stackforge/?query=puppet

[3] https://puppetlabs.com/solutions/cloud-automation/compute/openstack

[4] Mailing-list

[5] https://github.com/stackforge

[6] http://www.apache.org/licenses/LICENSE-2.0

[7] https://docs.puppetlabs.com/references/latest/function.html#createresources

[8] http://rspec-puppet.com/

[9] http://puppet-lint.com/checks/

Thanks to Gordon for english review

Show more