めもめも

このブログに記載の内容は個人の見解であり、必ずしも所属組織の立場、戦略、意見を代表するものではありません。

Private notes on Packstack plugin development (3)

Using Puppet modules

In the previous example, I used the simple manifest(puppet/templates/motd.pp) which didn't need any additional modules. But in general, Puppet manifests are associated with modules, self-contained bundles of code and data which can be included in the top level manifest. With Packstack, you can put your favorite modules under the directory "puppet/modules", and facts files under "puppet/facts". The current list of modules are as follows:

# ls puppet/modules/
apache            firewall  keystone   openstack  ssh     vlan
cinder            glance    memcached  packstack  stdlib  xinetd
concat            horizon   mysql      qpid       swift
create_resources  inifile   nova       rsync      sysctl

They are copied to the target server and specified in the modulepath. But the problem is that not all of them are actually copied. The modules to be copied are hard coded in "plugins/puppet_950.py" as below:

plugins/puppet_950.py

def copyPuppetModules():
    os_modules = ' '.join(('apache', 'cinder', 'concat',
                           'create_resources', 'firewall',
                           'glance', 'horizon', 'inifile',
                           'keystone', 'memcached', 'mysql',
                           'nova', 'openstack', 'packstack',
                           'qpid', 'rsync', 'ssh', 'stdlib',
                           'swift', 'sysctl', 'vlan', 'xinetd'))

If you want to add your own module, you need to add its directory name to the list above.

Multi target option

In some cases, you may want to apply the same configuration to multiple servers. It's not that hard to modify the existing single target plugin to the multi targert one. Here's the modified version of motd_020.py.

plugin/motd_020.py

"""
plugin for configuring /etc/motd
"""

import os
import logging

from packstack.installer import validators
import packstack.installer.common_utils as utils
from packstack.installer.exceptions import ScriptRuntimeError

from packstack.modules.ospluginutils import getManifestTemplate, appendManifestFile, manifestfiles

# Controller object will be initialized from main flow
controller = None

PLUGIN_NAME = "OS-MOTD"

logging.debug("plugin %s loaded", __name__)

def initConfig(controllerObject):
    global controller
    controller = controllerObject

    paramsList = [
                  {"CMD_OPTION"      : "motd-host",
                   "USAGE"           : "A comma separated list of IP addresses to configure /etc/motd",
                   "PROMPT"          : "Enter a comma separated list of IP addresses to configure /etc/motd",
                   "OPTION_LIST"     : [],
                   "VALIDATORS"      : [validators.validate_multi_ssh],
                   "DEFAULT_VALUE"   : utils.getLocalhostIP(),
                   "MASK_INPUT"      : False,
                   "LOOSE_VALIDATION": True,
                   "CONF_NAME"       : "CONFIG_MOTD_HOSTS",
                   "USE_DEFAULT"     : False,
                   "NEED_CONFIRM"    : False,
                   "CONDITION"       : False },
                  {"CMD_OPTION"      : "motd-message",
                   "USAGE"           : "message text in /etc/motd",
                   "PROMPT"          : "Enter the message",
                   "OPTION_LIST"     : [],
                   "VALIDATORS"      : [validators.validate_not_empty],
                   "DEFAULT_VALUE"   : "Hello, World!",
                   "MASK_INPUT"      : False,
                   "LOOSE_VALIDATION": True,
                   "CONF_NAME"       : "CONFIG_MOTD_MESSAGE",
                   "USE_DEFAULT"     : False,
                   "NEED_CONFIRM"    : False,
                   "CONDITION"       : False },
                 ]
    groupDict = { "GROUP_NAME"            : "MOTD",
                  "DESCRIPTION"           : "MOTD Options",
                  "PRE_CONDITION"         : "CONFIG_MOTD",
                  "PRE_CONDITION_MATCH"   : "y",
                  "POST_CONDITION"        : False,
                  "POST_CONDITION_MATCH"  : True}
    controller.addGroup(groupDict, paramsList)

def initSequences(controller):
    if controller.CONF['CONFIG_MOTD'] != 'y':
        return

    configmotdsteps = [
             {'title': 'Configuring /etd/motd message', 'functions':[createtestmanifest]},
    ]
    controller.addSequence("Installing config-motd Component", [], [], configmotdsteps)

def createtestmanifest():
    for host in controller.CONF['CONFIG_MOTD_HOSTS'].split(','):
        manifestfile = "%s_motd.pp" % host
        manifestdata = getManifestTemplate("motd.pp")
        appendManifestFile(manifestfile, manifestdata)

The new option CONFIG_MOTD_HOSTS accepts the comma separated list as target servers. As the convention, the option key should end with "_HOSTS". In createmanifest(), the same manifest file is added for each server in the list.

Here is the result of the test run.

# packstack --debug
Welcome to Installer setup utility
Enter the path to your ssh Public key to install on servers  [/root/.ssh/id_rsa.pub] : 
Enter a comma separated list of NTP server(s). Leave plain if Packstack should not install ntpd on instances.: 
Should Packstack configure motd [y|n]  [y] : 
Enter a comma separated list of IP addresses to configure /etc/motd  [192.168.122.191] : 192.168.122.191,192.168.122.192
Enter the message  [Hello, World!] : Hello, Worlds!      
To subscribe each server to EPEL enter "y" [y|n]  [y] : 
Enter a comma separated list of URLs to any additional yum repositories to install: 
To subscribe each server to Red Hat enter a username here: 
To subscribe each server to Red Hat enter your password here :
To subscribe each server to Red Hat Enterprise Linux 6 Server Beta channel (only needed for Preview versions of RHOS) enter "y" [y|n]  [n] : 
To subscribe each server with RHN Satellite enter RHN Satellite server URL: 

Installer will be installed using the following configuration:
==============================================================
ssh-public-key:                /root/.ssh/id_rsa.pub
ntp-severs:                    
config-motd:                   y
motd-host:                     192.168.122.191,192.168.122.192
motd-message:                  Hello, Worlds!
use-epel:                      y
additional-repo:               
rh-username:                   
rh-password:                   
rh-beta-repo:                  n
rhn-satellite-server:          
Proceed with the configuration listed above? (yes|no): yes

Installing:
Clean Up...                                              [ DONE ]
Setting up ssh keys...                                   [ DONE ]
Adding pre install manifest entries...                   [ DONE ]
Configuring /etd/motd message...                         [ DONE ]
Preparing servers...                                     [ DONE ]
Adding post install manifest entries...                  [ DONE ]
Installing Dependencies...                               [ DONE ]
Copying Puppet modules and manifests...                  [ DONE ]
Applying Puppet manifests...
Applying 192.168.122.191_prescript.pp
Applying 192.168.122.192_prescript.pp
Applying 192.168.122.191_motd.pp
Applying 192.168.122.192_motd.pp
192.168.122.192_prescript.pp :                                       [ DONE ]
192.168.122.192_motd.pp :                                            [ DONE ]
192.168.122.191_prescript.pp :                                       [ DONE ]
192.168.122.191_motd.pp :                                            [ DONE ]
Applying 192.168.122.191_postscript.pp
Applying 192.168.122.192_postscript.pp
192.168.122.192_postscript.pp :                                      [ DONE ]
192.168.122.191_postscript.pp :                                      [ DONE ]
                            [ DONE ]

 **** Installation completed successfully ******

Additional information:
 * A new answerfile was created in: /root/packstack-answers-20130521-154526.txt
 * Time synchronization installation was skipped. Please note that unsynchronized time on server instances might be problem for some OpenStack components.
 * The installation log file is available at: /var/tmp/packstack/20130521-154457-z4LFcK/openstack-setup.log

One thing you need to know from this output is the following fact...

At first, the following four manifests are applied in parallel.

Applying 192.168.122.191_prescript.pp
Applying 192.168.122.192_prescript.pp
Applying 192.168.122.191_motd.pp
Applying 192.168.122.192_motd.pp
192.168.122.192_prescript.pp :                                       [ DONE ]
192.168.122.192_motd.pp :                                            [ DONE ]
192.168.122.191_prescript.pp :                                       [ DONE ]
192.168.122.191_motd.pp :                                            [ DONE ]

After waiting until they finish, the following two manifests are applied in parallel.

Applying 192.168.122.191_postscript.pp
Applying 192.168.122.192_postscript.pp
192.168.122.192_postscript.pp :                                      [ DONE ]
192.168.122.191_postscript.pp :                                      [ DONE ]

You may suspect that there is an underlying mechanism to control the parallel manifest application. Yep, this is the very next topic.

Parallel Puppet configuration

Puppet manifests are applied when applyPuppetManifest() in the worker queue is executed (See the drawing in the previous part.) It takes manifests from the staging area with FIFO manner and apply it. By default, it proceeds to the next manifest without waiting the previous manifest to finish.

If you want to mark some waiting point as in the case above, you need to attach a "marker" to the manifest. When applyPuppetManifest() find the marked manifest, it waits until all the previous manifests are applied, and proceeds with the marked one. But one exception is that if the manifest has the same marker with the previous one, it doesn't wait.

In the case above, the markers were attached as below:

192.168.122.191_prescript.pp : No marker
192.168.122.192_prescript.pp : No marker
192.168.122.191_motd.pp      : No marker
192.168.122.192_motd.pp      : No marker
....Wait here....
192.168.122.191_postscript.pp : Marker "postscript"
192.168.122.192_postscript.pp : Marker "postscript"

The marker can be attached as a third argument of appendManifestFile().

plugins/postscript_949.py

def createmanifest():
    for hostname in gethostlist(controller.CONF):
        manifestfile = "%s_postscript.pp" % hostname
        manifestdata = getManifestTemplate("postscript.pp")
        appendManifestFile(manifestfile, manifestdata, 'postscript')

The general rule for attaching markers is:

1) If you are sure that the manifest can be applied in parallel with any previous manifests, you don't attach a marker.

2) If you are not sure that the manifest can be applied in parallel with previous manifests, you should attach a unique marker to it.

3) If you are sure that the manifest can be applied in parallel with the previous one, you would attach the same marker with the previous one. This is generally the case when you apply the same manifest to multiple servers.

You can see how this marker mechanism is realised from the source code of applyPuppetManifest().

plugins/puppet_950.py

def applyPuppetManifest():
    print
    currently_running = []
    lastmarker = None
    for manifest, marker in manifestfiles.getFiles():
        # if the marker has changed then we don't want to proceed until
        # all of the previous puppet runs have finished
        if lastmarker != None and lastmarker != marker:
            waitforpuppet(currently_running)
        lastmarker = marker
...

Continue to the next part.