+++ /dev/null
-%%
-%%
-
-\chapter{Python Scripting}
-\label{PythonChapter}
-\index[general]{Python Scripting}
-\index[general]{Scripting!Python}
-
-You may be asking what Python is and why a scripting language is
-needed in Bacula. The answer to the first question is that Python
-is an Object Oriented scripting language with features similar
-to those found in Perl, but the syntax of the language is much
-cleaner and simpler. The answer to why have scripting in Bacula is to
-give the user more control over the whole backup process. Probably
-the simplest example is when Bacula needs a new Volume name, with
-a scripting language such as Python, you can generate any name
-you want, based on the current state of Bacula.
-
-\section{Python Configuration}
-\index[general]{Python Configuration}
-\index[general]{Configuration!Python}
-
-Python must be enabled during the configuration process by adding
-a \verb:--:with-python, and possibly specifying an alternate
-directory if your Python is not installed in a standard system
-location. If you are using RPMs you will need the python-devel package
-installed.
-
-When Python is configured, it becomes an integral part of Bacula and
-runs in Bacula's address space, so even though it is an interpreted
-language, it is very efficient.
-
-When the Director starts, it looks to see if you have a {\bf
-Scripts Directory} Directive defined (normal default {\bf
-/etc/bacula/scripts}, if so, it looks in that directory for a file named
-{\bf DirStartUp.py}. If it is found, Bacula will pass this file to Python
-for execution. The {\bf Scripts Directory} is a new directive that you add
-to the Director resource of your bacula-dir.conf file.
-
-Note: Bacula does not install Python scripts by default because these
-scripts are for you to program. This means that with a default
-installation with Python enabled, Bacula will print the following error
-message:
-
-\begin{verbatim}
-09-Jun 15:14 bacula-dir: ERROR in pythonlib.c:131 Could not import
-Python script /etc/bacula/scripts/DirStartUp. Python disabled.
-\end{verbatim}
-
-The source code directory {\bf examples/python} contains sample scripts
-for DirStartUp.py, SDStartUp.py, and FDStartUp.py that you might want
-to use as a starting point. Normally, your scripts directory (at least
-where you store the Python scripts) should be writable by Bacula, because
-Python will attempt to write a compiled version of the scripts (e.g.
-DirStartUp.pyc) back to that directory.
-
-When starting with the sample scripts, you can delete any part that
-you will not need, but you should keep all the Bacula Event and Job Event
-definitions. If you do not want a particular event, simply replace the
-existing code with a {\bf noop = 1}.
-
-\section{Bacula Events}
-\index[general]{Bacula Events}
-\index[general]{Events}
-A Bacula event is a point in the Bacula code where Bacula
-will call a subroutine (actually a method) that you have
-defined in the Python StartUp script. Events correspond
-to some significant event such as a Job Start, a Job End,
-Bacula needs a new Volume Name, ... When your script is
-called, it will have access to all the Bacula variables
-specific to the Job (attributes of the Job Object), and
-it can even call some of the Job methods (subroutines)
-or set new values in the Job attributes, such as the
-Priority. You will see below how the events are used.
-
-\section{Python Objects}
-\index[general]{Python Objects}
-\index[general]{Objects!Python}
-
-There are four Python objects that you will need to work with:
-\begin{description}
-\item [The Bacula Object]
- The Bacula object is created by the Bacula daemon (the Director
- in the present case) when the daemon starts. It is available to
- the Python startup script, {\bf DirStartup.py}, by importing the
- Bacula definitions with {\bf import bacula}. The methods
- available with this object are described below.
-
-\item [The Bacula Events Class]
- You create this class in the startup script, and you pass
- it to the Bacula Object's {\bf set\_events} method. The
- purpose of the Bacula Events Class is to define what global
- or daemon events you want to monitor. When one of those events
- occurs, your Bacula Events Class will be called at the method
- corresponding to the event. There are currently three events,
- JobStart, JobEnd, and Exit, which are described in detail below.
-
-\item [The Job Object]
- When a Job starts, and assuming you have defined a JobStart method
- in your Bacula Events Class, Bacula will create a Job Object. This
- object will be passed to the JobStart event. The Job Object has a
- has good number of read-only members or attributes providing many
- details of the Job, and it also has a number of writable attributes
- that allow you to pass information into the Job. These attributes
- are described below.
-
-\item [The Job Events Class]
- You create this class in the JobStart method of your Bacula Events
- class, and it allows you to define which of the possible Job Object
- events you want to see. You must pass an instance of your Job Events
- class to the Job Object set\_events() method.
- Normally, you will probably only have one
- Job Events Class, which will be instantiated for each Job. However,
- if you wish to see different events in different Jobs, you may have
- as many Job Events classes as you wish.
-\end{description}
-
-
-The first thing the startup script must do is to define what global Bacula
-events (daemon events), it wants to see. This is done by creating a
-Bacula Events class, instantiating it, then passing it to the
-{\bf set\_events} method. There are three possible
-events.
-
-\begin{description}
-\item [JobStart]
- \index[general]{JobStart}
- This Python method, if defined, will be called each time a Job is started.
- The method is passed the class instantiation object as the first argument,
- and the Bacula Job object as the second argument. The Bacula Job object
- has several built-in methods, and you can define which ones you
- want called. If you do not define this method, you will not be able
- to interact with Bacula jobs.
-
-\item [JobEnd]
- This Python method, if defined, will be called each time a Job terminates.
- The method is passed the class instantiation object as the first argument,
- and the Bacula Job object as the second argument.
-
-\item [Exit]
- This Python method, if defined, will be called when the Director terminates.
- The method is passed the class instantiation object as the first argument.
-\end{description}
-
-Access to the Bacula variables and methods is done with:
-
- import bacula
-
-The following are the read-only attributes provided by the bacula object.
-\begin{description}
-\item [Name]
-\item [ConfigFile]
-\item [WorkingDir]
-\item [Version] string consisting of "Version Build-date"
-\end{description}
-
-
-A simple definition of the Bacula Events Class might be the following:
-
-\footnotesize
-\begin{verbatim}
-import sys, bacula
-class BaculaEvents:
- def JobStart(self, job):
- ...
-\end{verbatim}
-\normalsize
-
-Then to instantiate the class and pass it to Bacula, you
-would do:
-
-\footnotesize
-\begin{verbatim}
-bacula.set_events(BaculaEvents()) # register Bacula Events wanted
-\end{verbatim}
-\normalsize
-
-And at that point, each time a Job is started, your BaculaEvents JobStart
-method will be called.
-
-Now to actually do anything with a Job, you must define which Job events
-you want to see, and this is done by defining a JobEvents class containing
-the methods you want called. Each method name corresponds to one of the
-Job Events that Bacula will generate.
-
-A simple Job Events class might look like the following:
-
-\footnotesize
-\begin{verbatim}
-class JobEvents:
- def NewVolume(self, job):
- ...
-\end{verbatim}
-\normalsize
-
-Here, your JobEvents class method NewVolume will be called each time
-the Job needs a new Volume name. To actually register the events defined
-in your class with the Job, you must instantiate the JobEvents class and
-set it in the Job {\bf set\_events} variable. Note, this is a bit different
-from how you registered the Bacula events. The registration process must
-be done in the Bacula JobStart event (your method). So, you would modify
-Bacula Events (not the Job events) as follows:
-
-\footnotesize
-\begin{verbatim}
-import sys, bacula
-class BaculaEvents:
- def JobStart(self, job):
- events = JobEvents() # create instance of Job class
- job.set_events(events) # register Job events desired
- ...
-\end{verbatim}
-\normalsize
-
-When a job event is triggered, the appropriate event definition is
-called in the JobEvents class. This is the means by which your Python
-script or code gets control. Once it has control, it may read job
-attributes, or set them. See below for a list of read-only attributes,
-and those that are writable.
-
-In addition, the Bacula {\bf job} object in the Director has
-a number of methods (subroutines) that can be called. They
-are:
-\begin{description}
-\item [set\_events] The set\_events method takes a single
- argument, which is the instantiation of the Job Events class
- that contains the methods that you want called. The method
- names that will be called must correspond to the Bacula
- defined events. You may define additional methods but Bacula
- will not use them.
-\item [run] The run method takes a single string
- argument, which is the run command (same as in the Console)
- that you want to submit to start a new Job. The value
- returned by the run method is the JobId of the job that
- started, or -1 if there was an error.
-\item [write] The write method is used to be able to send
- print output to the Job Report. This will be described later.
-\item[cancel] The cancel method takes a single integer argument,
- which is a JobId. If JobId is found, it will be canceled.
-\item [DoesVolumeExist] The DoesVolumeExist method takes a single
- string argument, which is the Volume name, and returns
- 1 if the volume exists in the Catalog and 0 if the volume
- does not exist.
-\end{description}
-
-The following attributes are read/write within the Director
-for the {\bf job} object.
-
-\begin{description}
-\item [Priority] Read or set the Job priority.
- Note, that setting a Job Priority is effective only before
- the Job actually starts.
-\item [Level] This attribute contains a string representing the Job
- level, e.g. Full, Differential, Incremental, ... if read.
- The level can also be set.
-\end{description}
-
-The following read-only attributes are available within the Director
-for the {\bf job} object.
-
-\begin{description}
-\item [Type] This attribute contains a string representing the Job
- type, e.g. Backup, Restore, Verify, ...
-\item [JobId] This attribute contains an integer representing the
- JobId.
-\item [Client] This attribute contains a string with the name of the
- Client for this job.
-\item [NumVols] This attribute contains an integer with the number of
- Volumes in the Pool being used by the Job.
-\item [Pool] This attribute contains a string with the name of the Pool
- being used by the Job.
-\item [Storage] This attribute contains a string with the name of the
- Storage resource being used by the Job.
-\item [Catalog] This attribute contains a string with the name of the
- Catalog resource being used by the Job.
-\item [MediaType] This attribute contains a string with the name of the
- Media Type associated with the Storage resource being used by the Job.
-\item [Job] This attribute contains a string containing the name of the
- Job resource used by this job (not unique).
-\item [JobName] This attribute contains a string representing the full
- unique Job name.
-\item [JobStatus] This attribute contains a single character string
- representing the current Job status. The status may change
- during execution of the job. It may take on the following
- values:
- \begin{description}
- \item [C] Created, not yet running
- \item [R] Running
- \item [B] Blocked
- \item [T] Completed successfully
- \item [E] Terminated with errors
- \item [e] Non-fatal error
- \item [f] Fatal error
- \item [D] Verify found differences
- \item [A] Canceled by user
- \item [F] Waiting for Client
- \item [S] Waiting for Storage daemon
- \item [m] Waiting for new media
- \item [M] Waiting for media mount
- \item [s] Waiting for storage resource
- \item [j] Waiting for job resource
- \item [c] Waiting for client resource
- \item [d] Waiting on maximum jobs
- \item [t] Waiting on start time
- \item [p] Waiting on higher priority jobs
- \end{description}
-
-\item [Priority] This attribute contains an integer with the priority
- assigned to the job.
-\item [CatalogRes] tuple consisting of (DBName, Address, User,
- Password, Socket, Port, Database Vendor) taken from the Catalog resource
- for the Job with the exception of Database Vendor, which is
- one of the following: MySQL, PostgreSQL, SQLite, Internal,
- depending on what database you configured.
-\item [VolumeName]
- After a Volume has been purged, this attribute will contain the
- name of that Volume. At other times, this value may have no meaning.
-\end{description}
-
-The following write-only attributes are available within the
-Director:
-
-\begin{description}
-\item [JobReport] Send line to the Job Report.
-\item [VolumeName] Set a new Volume name. Valid only during the
- NewVolume event.
-\end{description}
-
-\section{Python Console Command}
-\index[general]{Python Console Command}
-\index[general]{Console Command!Python}
-
-There is a new Console command named {\bf python}. It takes
-a single argument {\bf restart}. Example:
-\begin{verbatim}
- python restart
-\end{verbatim}
-
-This command restarts the Python interpreter in the Director.
-This can be useful when you are modifying the DirStartUp script,
-because normally Python will cache it, and thus the
-script will be read one time.
-
-\section{Debugging Python Scripts}
-\index[general]{Debugging Python Scripts}
-In general, you debug your Python scripts by using print statements.
-You can also develop your script or important parts of it as a
-separate file using the Python interpreter to run it. Once you
-have it working correctly, you can then call the script from
-within the Bacula Python script (DirStartUp.py).
-
-If you are having problems loading DirStartUp.py, you will probably
-not get any error messages because Bacula can only print Python
-error messages after the Python interpreter is started. However, you
-may be able to see the error messages by starting Bacula in
-a shell window with the {\bf -d1} option on the command line. That
-should cause the Python error messages to be printed in the shell
-window.
-
-If you are getting error messages such as the following when
-loading DirStartUp.py:
-
-\begin{verbatim}
- Traceback (most recent call last):
- File "/etc/bacula/scripts/DirStartUp.py", line 6, in ?
- import time, sys, bacula
- ImportError: /usr/lib/python2.3/lib-dynload/timemodule.so: undefined
- symbol: PyInt_FromLong
- bacula-dir: pythonlib.c:134 Python Import error.
-\end{verbatim}
-
-It is because the DirStartUp script is calling a dynamically loaded
-module (timemodule.so in the above case) that then tries to use
-Python functions exported from the Python interpreter (in this case
-PyInt\_FromLong). The way Bacula is currently linked with Python does
-not permit this. The solution to the problem is to put such functions
-(in this case the import of time into a separate Python script, which
-will do your calculations and return the values you want. Then call
-(not import) this script from the Bacula DirStartUp.py script, and
-it all should work as you expect.
-
-
-
-
-
-\section{Python Example}
-\index[general]{Python Example}
-\index[general]{Example!Python}
-
-An example script for the Director startup file is provided in
-{\bf examples/python/DirStartup.py} as follows:
-
-\footnotesize
-\begin{verbatim}
-#
-# Bacula Python interface script for the Director
-#
-
-# You must import both sys and bacula
-import sys, bacula
-
-# This is the list of Bacula daemon events that you
-# can receive.
-class BaculaEvents(object):
- def __init__(self):
- # Called here when a new Bacula Events class is
- # is created. Normally not used
- noop = 1
-
- def JobStart(self, job):
- """
- Called here when a new job is started. If you want
- to do anything with the Job, you must register
- events you want to receive.
- """
- events = JobEvents() # create instance of Job class
- events.job = job # save Bacula's job pointer
- job.set_events(events) # register events desired
- sys.stderr = events # send error output to Bacula
- sys.stdout = events # send stdout to Bacula
- jobid = job.JobId; client = job.Client
- numvols = job.NumVols
- job.JobReport="Python Dir JobStart: JobId=%d Client=%s NumVols=%d\n" % (jobid,client,numvols)
-
- # Bacula Job is going to terminate
- def JobEnd(self, job):
- jobid = job.JobId
- client = job.Client
- job.JobReport="Python Dir JobEnd output: JobId=%d Client=%s.\n" % (jobid, client)
-
- # Called here when the Bacula daemon is going to exit
- def Exit(self, job):
- print "Daemon exiting."
-
-bacula.set_events(BaculaEvents()) # register daemon events desired
-
-"""
- These are the Job events that you can receive.
-"""
-class JobEvents(object):
- def __init__(self):
- # Called here when you instantiate the Job. Not
- # normally used
- noop = 1
-
- def JobInit(self, job):
- # Called when the job is first scheduled
- noop = 1
-
- def JobRun(self, job):
- # Called just before running the job after initializing
- # This is the point to change most Job parameters.
- # It is equivalent to the JobRunBefore point.
- noop = 1
-
- def NewVolume(self, job):
- # Called when Bacula wants a new Volume name. The Volume
- # name returned, if any, must be stored in job.VolumeName
- jobid = job.JobId
- client = job.Client
- numvol = job.NumVols;
- print job.CatalogRes
- job.JobReport = "JobId=%d Client=%s NumVols=%d" % (jobid, client, numvol)
- job.JobReport="Python before New Volume set for Job.\n"
- Vol = "TestA-%d" % numvol
- job.JobReport = "Exists=%d TestA-%d" % (job.DoesVolumeExist(Vol), numvol)
- job.VolumeName="TestA-%d" % numvol
- job.JobReport="Python after New Volume set for Job.\n"
- return 1
-
- def VolumePurged(self, job):
- # Called when a Volume is purged. The Volume name can be referenced
- # with job.VolumeName
- noop = 1
-
-
-
-\end{verbatim}
-\normalsize