Solaris Features: Service Management Facility - Part 4: Developing for SMF
Okay, now you know how to do basic tasks. But how to use SMF for your your own applications. I will use openvpn as an example. A good source for this program is blastwave. Please install the package tun
and openvpn
I want to show a running example, thus we have to do some work. It will be just a simple static shared key configuration, as this is a SMF tutorial, not one for OpenVPN. We will use theoden and gandalf again. gandalf will be the server. theoden the client.
Preparing the server
Okay … this is just a short configuration for an working OpenVPN server.
Now just leave this terminal this way.
Preparing the client
Okay, we need some basic configurations to get the client side of OpenVPN working, even when it´s unter control of the SMF ;)
# mkdir /etc/openvpn # mv /tmp/static.key /etc/openvpn # cd /etc/openvpn/ # ls -l total 2 -rw------- 1 jmoekamp other 636 Feb 27 16:11 static.key # chown -R root:root /etc/openvpn # chmod 600 static.key
Before working with SMF itself
At first i remove this stinking init.d links. We don´t need them anymore:
Okay, and now let´s hack the startup script….? Wrong! SMF can do many task for you, but this needs careful planing. You should answer yourself some questions:
- What variables make a generic description of a service to a specific server?
- How do i start the process? How do i stop them? How can i force the process to reload it´s config?
- Which services are my dependencies? Which services depend on my new service?
- How should the service react in the case of a failed dependency?
- What should happen in the case of a failure in the new service. Okay, let´s answer this questions for our OpenVPN service. The variables for our OpenVPN client are the hostname of the remote hosts and the local and remote IP of the VPN tunnel . Besides of this the filename of the secret key and the tunnel device may differ, thus it would be nice to keep them configurable. Starting openvpn is easy. We have just to start the openvpn daemon with some command line parameters. We stop the service by killing the process. And a refresh is done via stopping and starting the service. We clearly need the networking to use a VPN service. But networking isn´t just bringing up the networking cards. You need the name services for example. So make things easier, the service don´t check for every networking service to be up and running. We just define an dependency for the “network” milestone. As it make no sense to connect to a server without a network it looks like a sensible choice to stop the service in case of a failed networking. Furthermore it seems a good choice to restart the service when the networking configuration has changed. Perhaps we modified the configuration of the name services and the name of the OpenVPN server resolves to a different IP. What should happen in the case of a exiting OpenVPN daemon? Of course it should started again. Okay, now we can start with coding the scripts and xml files.
The Manifest
Okay, as i wrote before, the manifest is the source of the configuration of a service. Thus we have to write such a manifest. The manifest is a XML file. Okay, at first we obey the gods of XML and do some definitions:
Okay, at first we habve to name the service:
In this example, we define some simple dependencies. As i wrote before: Without networking a VPN is quite useless, thus the OpenVPN service depends on the reached milestone
.
In this part of the manifest we define the exec method to start the service. We use a script to start the service. The %m
is a variable. It will be substituted with the name of the called action. In this example it would be expanded to /lib/svc/method/openvpn start
.
<exec_method type='method' name='start' exec='/lib/svc/method/openvpn %m' timeout_seconds='2' />
Okay, we can stop OpenVPN simply by sending a SIGTERM signal to it. Thus we can use a automagical exec method. In case you use the :kill
SMF will kill all processes in the actual contract of the service.
<exec_method type='method' name='stop' exec=':kill' timeout_seconds='2'> </exec_method>
Okay, thus far we´ve only define the service. Let´s define a service. We call the instance theoden2gandalf
for obvious names. The service should run with root privileges. After this we define the properties of this service instance like the remote host or the file with the secret keys.
<instance name='theoden2gandalf' enabled='false'> <method_context> <method_credential user='root' group='root' /> </method_context> <property_group name='openvpn' type='application'> <propval name='remotehost' type='astring' value='gandalf' /> <propval name='secret' type='astring' value='/etc/openvpn/static.key' /> <propval name='tunnel_local_ip' type='astring' value='172.16.1.2' /> <propval name='tunnel_remote_ip' type='astring' value='172.16.1.1' /> <propval name='tunneldevice' type='astring' value='tun' /> </property_group> </instance>
At the end we add some further metadata to this service:
<stability value='Evolving' /> <template> <common_name> <loctext xml:lang='C'>OpenVPN</loctext> </common_name> <documentation> <manpage title='openvpn' section='1' /> <doc_link name='openvpn.org' uri='http://openvpn.org' /> </documentation> </template> </service> </service_bundle>
I saved this xml file to my homedirectory under /export/home/jmoekamp/openvpn.xml
.
The exec method´s script
General considerations
Okay, we referenced a script in the exec method. This script is really similar to a normal init.d script. But there are some important differences. As there´s no parallel startup of services in init.d most scripts for this system bringup method tend to return as quickly as possible. We have to change this behaviour. Scripts for transient or standalone services should only return in the case of the successful execution of the complete script or when we´ve terminated the process. For services under control of the contract mechanism the script should at least wait until the processes of the service generate some meaningful error messages, but they have to exit, as SMF would consider the service startup as failed, when the script doesn´t return after There are some general tips:
- When you write your own
stop
method don´t implement it in a way that simply kills all processes with a certain name (e.g. pkill "openvpn") this was and is really bad style, as there may be several instances of service. Just using the name to stop the processes would cause unneeded collateral damage. -
It´s a good practice to include the
/lib/svc/share/smf_include.sh
. It defines some variables for errorcodes to ease the development of the method scripts.
Implementing a exec method script
We store configuration properties in the Service Component repository. It would be nice to use them for configuration. Thus we have to access them.
Here comes the svcprop command to help:
# svcprop -p openvpn/remotehost svc:/application/network/openvpn:theoden2gandalf gandalf
With a little bit of shell scripting we can use this properties to use them for starting our processes.
I saved this script to my homedirectory under /export/home/jmoekamp/openvpn
.
Installation of the new Service
Okay, copy the script to /lib/svc/method/
:
# cp openvpn /lib/svc/method # chmod +x /lib/svc/method/openvpn
After this step you have to import the manifest into the Service Configuration Repository:
# svccfg validate /export/home/jmoekamp/openvpn.xml # svccfg import /home/jmoekamp/openvpn.xml
Testing it
Let´s test our brand new service:
The OpenVPN service isn´t enabled. Thus there is no tunnel. The ping doesn´t get through.
Now we enable the service and test it again.
Voila … SMF has started our brand new service. When we look into the list of services, we will find it:
When we look into the process table, we will finde the according process:
Okay, we doesn´t need the tunnel any longer after a few day,thus we disable it:
No process left.