image block
2792 Views 13 Comments
Sooner or later is appearing the need to write a daemon. And this is pretty easy to do. But let's start from the defining and why it needed.

What is this Daemon?

Daemon this is a program which is running on multitasking computers in the background. This helps to avoid endless control of program by an interactive user. We can just start the service and it will make any data processing or perform some tasks housekeeping in the background etc.

Python command line arguments

Before running the script, we need to give some arguments for input. Native library of Python named "argparse" can help easily to resolve it. There is a simple example :
import argparse

def arguments_reader():
    parser = argparse.ArgumentParser(description='cmdb_agent runner')
    parser.add_argument('operation',
        metavar='OPERATION',
        type=str,
        help='Operation with cmdb agent. Accepts any of these values: start, stop, restart, status',
        choices=['start', 'stop', 'restart', 'status'])
    args = parser.parse_args()
    operation = args.operation
    return operation

if __name__ == "__main__":
    action = arguments_reader()
    print(action)
From this we see that we can just import the library and set needed options. It will check them if they are not valid (not from our list of options) it will raise the error. In this case vaild options are start, stop, restart and status.

A simple unix/linux daemon in Python

Ok, let's go to practice. At the first, have a look to example :
import argparse
import os, sys
import time, datetime
import atexit
import signal

pid_file = '/var/run/cmdb_agent'

class Daemon(object):

    def __init__(self, pidfile, stdin='/dev/null',
                 stdout='/dev/null', stderr='/dev/null'):

        self.stdin = stdin
        self.stdout = stdout
        self.stderr = stderr
        self.pidfile = pidfile


    def daemonize(self):

        self.fork()
        self.dettach_env()
        self.fork()
        sys.stdout.flush()
        sys.stderr.flush()
        self.attach_stream('stdin', mode='r')
        self.attach_stream('stdout', mode='a+')
        self.attach_stream('stderr', mode='a+')
        self.create_pidfile()


    def attach_stream(self, name, mode):

        stream = open(getattr(self, name), mode)
        os.dup2(stream.fileno(), getattr(sys, name).fileno())


    def dettach_env(self):

        os.chdir("/")
        os.setsid()
        os.umask(0)


    def fork(self):

        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError as e:
            sys.stderr.write("Fork failed: %d (%s)\n" % (e.errno, e.strerror))
            sys.exit(1)


    def create_pidfile(self):

        atexit.register(self.delpid)
        pid = str(os.getpid())
        open(self.pidfile,'w+').write("%s\n" % pid)


    def delpid(self):

        os.remove(self.pidfile)


    def start(self):
        pid = self.get_pid()

        if pid:
            message = "CMDB agent already running?"
            sys.stderr.write(message % self.pidfile)
            sys.exit(1)

        self.daemonize()
        self.run()


    def get_pid(self):

        try:
            pf = open(self.pidfile,'r')
            pid = int(pf.read().strip())
            pf.close()
        except (IOError, TypeError):
            pid = None
        return pid


    def stop(self, silent=False):

        pid = self.get_pid()

        if not pid:
            if not silent:
                message = "CMDB agent not running?"
                sys.stderr.write(message % self.pidfile)
            return
        try:
            while True:
                os.kill(pid, signal.SIGTERM)
                time.sleep(0.1)
        except OSError as err:
            err = str(err)
            if err.find("No such process") > 0:
                if os.path.exists(self.pidfile):
                    os.remove(self.pidfile)
            else:
                sys.stdout.write(str(err))
                sys.exit(1)


    def restart(self):

        self.stop(silent=True)
        self.start()


    def run(self):

        raise NotImplementedError



def arguments_reader():

    parser = argparse.ArgumentParser(description='cmdb_agent runner')
    parser.add_argument('operation',
        metavar='OPERATION',
        type=str,
        help='Operation with CMDB agent. Accepts any of these values: start, stop, restart, status',
        choices=['start', 'stop', 'restart', 'status'])
    args = parser.parse_args()
    operation = args.operation
    return operation



class CMDBAgent(Daemon):


    def run(self):
        while True:
            self.some_actions()
            time.sleep(1)


    def some_actions(self):

        test_file = "/tmp/test.txt"
        with open(test_file, "a") as myfile:
            myfile.write("appended text\n")

if __name__ == "__main__":

    action = arguments_reader()
    daemon = CMDBAgent(pid_file,)

    if action == 'start':
        print("Starting CMDB agent")
        daemon.start()
        pid = daemon.get_pid()

        if not pid:
            print("Unable run CMDB agent")
        else:
            print("CMDB agent is running")

    elif action == 'stop':
        print("Stoping CMDB agent")
        daemon.stop()

    elif action == 'restart':
        print("Restarting CMDB agent")
        daemon.restart()

    elif action == 'status':
        print("Viewing CMDB agent status")
        pid = daemon.get_pid()

        if not pid:
            print("CMDB agent isn't running")
        else:
            print("CMDB agent is running")

    sys.exit(0)
This example has been taken from site : wookr.com/pydaemon
As we can see, we are using 'argparse' library to get options from input and to convey them you our daemon. And due selected option we are calling needed function.
In the example mentioned some definitions, which I want to describe :
  • stdin, stdout, stderr - there are standard UNIX channels. Which are give communication between program and environment. The three I/O connections are called standard input (stdin), standard output (stdout) and standard error (stderr)
  • PID - process ID
  • PID file - it's file which contain PID. Them are using in UNIX systems for easy way to know ID of process and check status of process.
  • fork - this is the operation which creates a new process by duplicating of calling process. It's mean new process, referred to as the child, is an exact duplicate of the calling process, referred to as the parent, but with separate PID.
This script is opening I/O channels for communication with the script and forking process. That what we need, the function "run" of class "CMDBAgent". It will process daemon and will do action what we wrote in the background. For example, I wrote additional function, named "some_actions", which will add some text to a file every second. And exactly instead of this function, you can put any action what you want. You can manage this service via input commands. Example is follow :
# ./simple_daemon.py start
Starting CMDB agent
# ./simple_daemon.py status
Viewing CMDB agent status
CMDB agent is running
# ./simple_daemon.py stop
Stoping CMDB agent
And also you can check this process via ps command.
# ps -aux | grep simple
root     32052  0.0  0.8 145080  4328 ?        S    14:07   0:00 /bin/python ./simple_daemon.py start

Sipmle loggining of Daemon

Great! We have a working daemon. Let's just add a few details to our script, that he will start writing to log file their actions, instead of printing it on stdout.
For this, we can write separate script which will be imported to our daemon.
class LogginSystem(object):

    def __init__(self, file_location):
        self.log_file = open(file_location, 'a+')

    def write(self, message):
        self.log_file.write(message)

    def __del__(self):
        self.log_file.close()
This class opens the file when calling, write the message via separate function and close log file when it is going to finish. So now we need only import it, give location and just use the function of write instead of "print". To import script from current directory :
from logsystem import LogginSystem as logsys
Example of usage (you can change format like you want) :
logginsystem.write("[%s] Starting CMDB agent\n" % datetime.datetime.now().strftime('%B %d %H:%M:%S'))
or with PID :
logginsystem.write("[%s] CMDB agent is running [PID=%d]\n" % datetime.datetime.now().strftime('%B %d %H:%M:%S'), pid)

Conclusion

This is all I wanted to tell about daemons. Hope I have convinced you, that it's easy to create services on UNIX systems via Python and we can manage them like we want, using standard input channel and UNIX command.

Resources

All examples and full scripts you can find on my GitHub.

Hope this information was useful for you and thank you for the attention.
And of course, if you'll have any questions - don't hesitate to contact me :)
- Kostia

13 Comments

  1. Commander Du Cialis Pas Cher Farmacia Online Uk <a href=http://buycialonline.com>cheapest cialis</a> Rezeptfrei Viagra Ausland Cheap Amoxicillin Without Prescription Amoxicillin Merck

  2. Cialis Zollprobleme Buy Azithromycin Or Ceftriaxone Now <a href=http://cial40mg.com>cheapest cialis 20mg</a> Viagra Et Cialis Pas Cher Cialis Alkohol

  3. Cialis 5 Mg Le Prix <a href=http://brandcial.com>cialis overnight shipping from usa</a> Keflex Sinus Viagra Y Alcohol Buy Propecia No Prescription Online

  4. Buy Lexapro Online Pharmacy Propecia Canadian Cialis Soft <a href=http://buycial.com>generic cialis canada</a> Sky Pharmacy Wellbutrin Trazadone Without Rx

  5. Combivent Inhaler Order On Line No Rx <a href=http://cialtadalaf.com>where to buy cialis online safely</a> Kamagra Online Sales Purchase Amoxicillin No Prescription Overnight Delivery

  6. Levitra 5mg Rezeptfrei Viagra Efectos En Mujeres Comment Utiliser Cytotec Pour Avorter <a href=http://orderciali.com>cialis price</a> Doxycyclin 200mg

  7. <a href="http://canadianpharmaciesoffer.com/">canadian online pharmacy</a> http://canadianpharmaciesoffer.com/ <a href=http://canadianpharmaciesoffer.com/>legitimate online pharmacies</a>

  8. <a href="http://canadianpharmaciesoffer.com/">no 1 canadian pharcharmy online</a> http://canadianpharmaciesoffer.com/ <a href=http://canadianpharmaciesoffer.com/>best 10 online pharmacies</a>

  9. <a href="http://canadianpharmaciesoffer.com/">safe online pharmacies in canada</a> http://canadianpharmaciesoffer.com/ <a href=http://canadianpharmaciesoffer.com/>best mail order pharmacies</a>

  10. <a href="http://canadianpharmaciesprofmeds.com/">top 10 mail order pharmacies</a> http://canadianpharmaciesprofmeds.com/ <a href=http://canadianpharmaciesprofmeds.com/>canadian pharmacies</a>

  11. international pharmacies that ship to the usa http://canadianpharmaciesprofmeds.com/ [url=http://canadianpharmaciesprofmeds.com/]canadian pharmacies shipping to usa[/url]

  12. canadian neighbor pharmacy <a href=" http://canadianpharmacystorm.com/# ">canada pharmacy reviews</a> reliable canadian pharmacy

  13. legit online pharmacy http://canadianpharmacystorm.com/# best canadian online pharmacy reviews


  14. Leave a Comment