Sunday Jan 06, 2013

oracle vm template config script example

The programmatic way to extend Oracle VM Template Configure is to build your own module.

To write your own module, you have to build an RPM that contains a configure script in a specific format, let's go through the steps to do this.

Oracle VM template configure works very similar to the init.d and chkconfig script model. For template config we have the /etc/template.d directory, all the scripts go into /etc/template.d/scripts. Then symlinks are made to other subdirectories based on the type of target the scripts provide. At this point we handle configure and cleanup. When a script/module gets added using ovm-chkconfig, the header of the script is read to verify the name, priority and targets and then a symlink is made to the corresponding subdirectories under /etc/template.d.

As an example, you have /etc/init.d/sshd which is the main sshd initscript and when sshd is enabled you will find a symlink in /etc/rc3.d/S55sshd to /etc/init.d/sshd. These symlinks are created by chkconfig when you enable or disable a service. The same thing goes for Oracle VM template config and the content of /etc/template.d/scripts. You will see /etc/template.d/scripts/ssh and since ssh (on my system) is enabled for the configure target, I have a symlink to /etc/template.d/configure.d/70ssh.

Like init.d, the digit in front of the script name specifies the priority at which it should be run.

The most important and complex part is writing your own script for your own application. Our scripts are in python, theoretically you could write it in a different language, as long as the input, output and argument handling remains the same. The examples here will all be in python. Each script has 2 main part : (1) the script header which contains information like script name, targets, priorities and description and (2) the actual script which has to handle a small set of parameters. You can take a look at the existing scripts for examples.

(1) script header
Aside from a copyright header that suits your needs, the script headers require a very specific comment block, here is an example :

# name: network
# configure: 50
# cleanup: 50
# description: Script to configure template network.

You have to use the exact same format. Provide your own script name, which will be used when calling ovm-chkconfig, the targets (right now we implement configure and cleanup) and the priority for your script. The priority will specify in what order the scripts get executed. You do not have to implement all targets, if you have a configure target but not cleanup, that is OK, same goes for cleanup versus configure. It is up to you. The configure target gets called when a first boot/initial start of the VM happens, cleanup happens when you manually initiate a cleanup in your VM or when you want to restore the VM to its original state.

# name: [script name]
# [target]: [priority]
# [target]: [priority]
# description: a description and can
#   cross multiple lines.

Now for the body of the script. Basically the main requirement is that it accepts a [target] parameter. Let's say we have script called foo that needs to be run at configure time, then the script (/etc/template.d/scripts) will have to accept and understand handling the parameter configure. If you also want to call it for cleanup, then it has to handle cleanup. You can have your script handle any other arguments, this is totally up to you, they are optional for our purposes. There is one optional parameter which is useful to implement and this is -e or --enumerate. ovm-template-config uses this to be able to enumerate the parameters for a target for your script.

Here is the firewall example:

# ovm-template-config --human-readable --enumerate configure --script firewall
  [{u'description': u'Whether to enable network firewall: True or False.',
    u'hidden': True,
    u'key': u''}])]
and if you run the script manually :

# ./firewall configure -e
[{"hidden": true, "description": "Whether to enable network firewall: True or False.", "key": ""}]

In other words, the firewall script lists the parameters it expects when run as a configure target.

Now here is an example of the script body, in python. It implements the configure and cleanup target and handles the enumerate argument. Part of the magic is handled in templateconfig.cli.

    import json
except ImportError:
    import simplejson as json
from templateconfig.cli import main

def do_enumerate(target):
    param = []
    if target == 'configure':
        param += []
    elif target == 'cleanup':
        param += []
    return json.dumps(param)

def do_configure(param):
    param = json.loads(param)
    return json.dumps(param)

def do_unconfigure(param):
    param = json.loads(param)
    return json.dumps(param)

def do_cleanup(param):
    param = json.loads(param)
    return json.dumps(param)

if __name__ == '__main__':
    main(do_enumerate, {'configure': do_configure, 'cleanup': do_cleanup})

So now you can fill this out with your own parameters and code. Again taking the firewall script as an example, to add expected keys :

def do_enumerate(target):
    param = []
    if target == 'configure':
        param += [{'key': '',
                   'description': 'Whether to enable network firewall: True or False.',
                   'hidden': True}]
    return json.dumps(param)

The above shows that this script expect the key to be set and what the default is, along with a description. Add this for each key/value pair that you expect for your script and then afterwards it is easy to understand what the input to your script needs to be, again by running ovm-template-config.

To execute actions at configure time, based on values set, here's a do_configure() example:

def do_configure(param):
    param = json.loads(param)
    firewall = param.get('')
    if firewall == 'True':
        shell_cmd('service iptables start')
        shell_cmd('service ip6tables start')
        shell_cmd('chkconfig --level 2345 iptables on')
        shell_cmd('chkconfig --level 2345 ip6tables on')
    elif firewall == 'False':
        shell_cmd('service iptables stop')
        shell_cmd('service ip6tables stop')
        shell_cmd('chkconfig --level 2345 iptables off')
        shell_cmd('chkconfig --level 2345 ip6tables off')
    return json.dumps(param)

When the script is called, you can use param.get() to retrieve key/value variables and then just make use of it. Just like in the firewall example, you can do whatever you want, call out other commands, add more python code, it's up to you...

It is also possible to alter keys or add new keys which then get sent back. So if you want your script to communicate values back which can be retrieved later through the manager API, for instance with ovm_vmmessage -q, you can simply to this :

param['key'] = 'some value'

Key can be an existing key, or a new one.

And that's really it... for the script. Next up is packaging.

In order to install and configure these template configure scripts, they have to be packaged in an RPM, with a specific naming convention. Package the script(s), there can be more than one, as ovm-template-config-[scriptname]. Ideally in the post install of the RPM you want to add the script automatically. Execute # /usr/sbin/ovm-chkconfig --add [scriptname]. When de-installing a script/RPM, remove it at un-install time, # /usr/sbin/ovm-chkconfig --del [scriptname].

Here is an example of an RPM spec file that can be used:

Name: ovm-template-config-example
Version: 3.0
Release: 1%{?dist}
Summary: Oracle VM template example configuration script.
Group: Applications/System
License: GPL
Source0: %{name}-%{version}.tar.gz
BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX)
BuildArch: noarch
Requires: ovm-template-config

Oracle VM template example configuration script.

%setup -q



if [ $1 = 1 ]; then
    /usr/sbin/ovm-chkconfig --add example

if [ $1 = 0 ]; then
    /usr/sbin/ovm-chkconfig --del example


* Tue Mar 22 2011 Zhigang Wang  - 3.0-1
- Initial build.

Modify the content to your liking, change the name example to your script name, and add whatever else dependencies you might have or whatever files need to be bundled along with this. If you want to bundle executables or scripts that live in other locations, that's allowed. As you can see from the spec file, it automatically called ovm-chkconfig --add and --del at post-install and pre-uninstall time of the RPM.

In order to create RPMs, you have to install rpmbuild, # yum install rpm-build.

To make it easy, here's a Makefile you can use and help automate all of this :


	@echo 'Commonly used make targets:'
	@echo '  install    - install program'
	@echo '  dist       - create a source tarball'
	@echo '  rpm        - build RPM packages'
	@echo '  clean      - remove files created by other targets'

dist: clean
	mkdir $(PACKAGE)-$(VERSION)
	tar -cSp --to-stdout --exclude .svn --exclude .hg --exclude .hgignore \
	    --exclude $(PACKAGE)-$(VERSION) * | tar -x -C $(PACKAGE)-$(VERSION)
	tar -czSpf $(PACKAGE)-$(VERSION).tar.gz $(PACKAGE)-$(VERSION)
	rm -rf $(PACKAGE)-$(VERSION)

	install -D example $(DESTDIR)/etc/template.d/scripts/example

rpm: dist
	rpmbuild -ta $(PACKAGE)-$(VERSION).tar.gz

	rm -fr $(PACKAGE)-$(VERSION)
	find . -name '*.py[cdo]' -exec rm -f '{}' ';'
	rm -f *.tar.gz

.PHONY: dist install rpm clean

Create a directory, copy over your script, the spec file and this Makefile. Run # make dist, to create a src tarball of your code and then # make rpm. This will generate an RPM in the RPMS/noarch directory. For instance: /root/rpmbuild/RPMS/noarch/ovm-template-config-test-3.0-1.el6.noarch.rpm

Next you can take this RPM and install it on a target system.

# rpm -ivh  /root/rpmbuild/RPMS/noarch/ovm-template-config-test-3.0-1.el6.noarch.rpm
Preparing...                ########################################### [100%]
   1:ovm-template-config-tes########################################### [100%]

And as you can see, it's added to the ovm-chkconfig list :

# ovm-chkconfig --list|grep testtest                 on:75       
off         off         on:25       off         off         off         off        

One point of caution : the configure scripts get executed very early on in the bootstage. ovmd is executed as S00ovmd. This is well before many other services are (1) configured, (2) running. So if your product requires services like network connectivity or others to be up and running, then you have to split up the configuration into two parts. First, use the above to gather configuration data remotely, store it in a way that you can use it, and then add your own /etc/init.d scripts which can take this data afterwards. So you can have your own init scripts executed at a late stage when the services you depend on are available.

That's really all there is to it. Thanks to Zhigang for example code I have used here.

oracle vm messages

Using the Oracle VM Message API for your own applications...

There are two ways to communicate through the APIs, a quick and easy one and a more comprehensive one.

The quick and easy method of just sending and receiving messages.

  • Sending messages using ovm_utils or using the Oracle VM CLI to the VM
  • # ssh admin@localhost -p 10000
    admin@localhost's password: 
    OVM> sendVmMessage Vm name=ol6u3apitest key=foo message=bar log=no
    Command: sendVmMessage Vm name=ol6u3apitest key=foo message=bar log=no
    Status: Success
    Time: 2012-12-27 09:04:29,890 PST


    # ./ovm_vmmessage -u admin -p ######## -h localhost -v ol6u3apitest -k foo -V bar
    Oracle VM VM Message utility 0.5.2.
    VM : 'ol6u3apitest' has status :  Running.
    Sending message.
    Message sent successfully.

    so both of the examples send a key/value pair of foo=bar to the VM.

  • Receiving messages on the VM side using ovmd
  • Inside the VM, you can use the ovmd executable to retrieve and send messages back.

    # ovmd -l 

    lists all currently set key/value pairs

    # ovmd -g key 
    get a value from inside the VM

    # ovmd -r key
    delete a key out of the current cache

    # ovmd -x
    delete the key/value values currently set in the cache

    so in the case of the message sent through the manager above, you can see it in the VM :

    # ovmd -l
    # ovmd -g mykey
    # ovmd -r mykey
    # ovmd -l

  • Setting key/value pairs inside the VM and retrieve through the manager
  • # ovmd -p key=value
    set a key/value pair inside the VM

    # ovmd -p newkey=newvalue
    # ovmd -l

    use ovm_vmmessage to query the value of a key, use the -q option with the key to query to retrieve a set value.

    ovm_vmmessage will also return when this key was set inside the VM.

    # ./ovm_vmmessage -u admin -p Manager1 -h localhost -v ol6u3apitest -q newkey
    Oracle VM VM Message utility 0.5.2.
    VM : 'ol6u3apitest' has status :  Running.
    Querying for key 'newkey'.
    Query successful.
    Query for Key : 'newkey' returned value 'newvalue'.
    Key set 2 minutes ago.

    So with just these simple tools it's possible to set up a model where you send message from your application outside of a VM to the VM through the OVMAPI and also send messages from an application inside a VM back. You can write your own daemon process that runs and queries for values or just do it manually. A recommendation would be to create a naming convention for your product. For instance, for the Oracle VM template configuration we use[values]. You could consider something similar or just have [application].[key] or whatever you want. The maximum size of the total message is 8Kb.

    Next up will be extending template config for your own application.


    Wim Coekaerts is the Senior Vice President of Linux and Virtualization Engineering for Oracle. He is responsible for Oracle's complete desktop to data center virtualization product line and the Oracle Linux support program.

    You can follow him on Twitter at @wimcoekaerts


    • Oracle
    « January 2013 »