summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTyler Baker <tyler.baker@linaro.org>2014-04-17 14:14:39 -0700
committerTyler Baker <tyler.baker@linaro.org>2014-04-17 14:14:39 -0700
commitbc8b057384ebc289f6f708818c78690b4c4d36d4 (patch)
tree2ec621a6a28ddeebc8f7581e3edcdfd5878c4824
Initial commit
-rw-r--r--.gitignore2
-rw-r--r--.gitreview4
-rw-r--r--MANIFEST.in4
-rw-r--r--etc/lavapdu-listen.init128
-rw-r--r--etc/lavapdu-runner.init128
-rw-r--r--etc/lavapdu.conf8
-rw-r--r--etc/lavapdulogrotate19
-rwxr-xr-xlavapdu-listen127
-rwxr-xr-xlavapdu-runner120
-rw-r--r--lavapdu/__init__.py22
-rw-r--r--lavapdu/apcdrivers.py168
-rw-r--r--lavapdu/driver.py27
-rw-r--r--lavapdu/engine.py111
-rw-r--r--lavapdu/pdurunner.py85
-rw-r--r--lavapdu/socketserver.py143
-rwxr-xr-xpduclient40
-rw-r--r--setup.py50
-rw-r--r--var/lib/lavapdu/pdu.dbbin0 -> 2048 bytes
18 files changed, 1186 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..44dbcd6
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+*.pyc
+.idea/
diff --git a/.gitreview b/.gitreview
new file mode 100644
index 0000000..248d8cc
--- /dev/null
+++ b/.gitreview
@@ -0,0 +1,4 @@
+[gerrit]
+host=git.linaro.org
+port=29418
+project=lava/lavaproxy
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..9f951a3
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,4 @@
+include etc/lavapdu.conf
+include etc/lavapdu-runner.init
+include etc/lavapdu-listen.init
+include etc/lavapdulogrotate \ No newline at end of file
diff --git a/etc/lavapdu-listen.init b/etc/lavapdu-listen.init
new file mode 100644
index 0000000..5a0e06e
--- /dev/null
+++ b/etc/lavapdu-listen.init
@@ -0,0 +1,128 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: lavapdu-listen
+# Required-Start: $remote_fs $network
+# Required-Stop: $remote_fs $network
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: LAVA PDU Listener
+# Description: TCP Listening daemon to accept PDU requests
+### END INIT INFO
+
+# Author: Matthew Hart <matthew.hart@linaro.org>
+
+LOGFILE="--logfile /var/log/lavapdu-listener.log"
+LOGLEVEL="--loglevel=INFO"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="lavapdu-listen" # short description
+NAME=lavapdu-listen # short server's name (truncated for 15 chars)
+DAEMON=/usr/bin/lavapdu-listen # server's location
+DAEMON_ARGS="$LOGLEVEL" # Arguments to run the daemon with
+PIDFILE=/var/run/$DESC.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x $DAEMON ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+#. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
diff --git a/etc/lavapdu-runner.init b/etc/lavapdu-runner.init
new file mode 100644
index 0000000..a11b8f3
--- /dev/null
+++ b/etc/lavapdu-runner.init
@@ -0,0 +1,128 @@
+#!/bin/sh
+### BEGIN INIT INFO
+# Provides: lavapdu-runner
+# Required-Start: $remote_fs $network
+# Required-Stop: $remote_fs $network
+# Default-Start: 2 3 4 5
+# Default-Stop: 0 1 6
+# Short-Description: LAVA PDU Runner
+# Description: Runner daemon to process PDU requests
+### END INIT INFO
+
+# Author: Matthew Hart <matthew.hart@linaro.org>
+
+LOGFILE="--logfile /var/log/lavapdu-runner.log"
+LOGLEVEL="--loglevel=INFO"
+
+# PATH should only include /usr/* if it runs after the mountnfs.sh script
+PATH=/sbin:/usr/sbin:/bin:/usr/bin
+DESC="lavapdu-runner" # short description
+NAME=lavapdu-runner # short server's name (truncated for 15 chars)
+DAEMON=/usr/bin/lavapdu-runner # server's location
+DAEMON_ARGS="$LOGLEVEL" # Arguments to run the daemon with
+PIDFILE=/var/run/$DESC.pid
+SCRIPTNAME=/etc/init.d/$NAME
+
+# Exit if the package is not installed
+[ -x $DAEMON ] || exit 0
+
+# Read configuration variable file if it is present
+[ -r /etc/default/$NAME ] && . /etc/default/$NAME
+
+# Load the VERBOSE setting and other rcS variables
+#. /lib/init/vars.sh
+
+# Define LSB log_* functions.
+# Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
+. /lib/lsb/init-functions
+
+#
+# Function that starts the daemon/service
+#
+do_start()
+{
+ # Return
+ # 0 if daemon has been started
+ # 1 if daemon was already running
+ # 2 if daemon could not be started
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON --test > /dev/null \
+ || return 1
+ start-stop-daemon --start --quiet --pidfile $PIDFILE --exec $DAEMON -- \
+ $DAEMON_ARGS \
+ || return 2
+ # Add code here, if necessary, that waits for the process to be ready
+ # to handle requests from services started subsequently which depend
+ # on this one. As a last resort, sleep for some time.
+}
+
+#
+# Function that stops the daemon/service
+#
+do_stop()
+{
+ # Return
+ # 0 if daemon has been stopped
+ # 1 if daemon was already stopped
+ # 2 if daemon could not be stopped
+ # other if a failure occurred
+ start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE --name $NAME
+ RETVAL="$?"
+ [ "$RETVAL" = 2 ] && return 2
+ # Wait for children to finish too if this is a daemon that forks
+ # and if the daemon is only ever run from this initscript.
+ # If the above conditions are not satisfied then add some other code
+ # that waits for the process to drop all resources that could be
+ # needed by services started subsequently. A last resort is to
+ # sleep for some time.
+ # Many daemons don't delete their pidfiles when they exit.
+ rm -f $PIDFILE
+ return "$RETVAL"
+}
+
+case "$1" in
+ start)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC " "$NAME"
+ do_start
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ stop)
+ [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
+ 2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
+ esac
+ ;;
+ status)
+ status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
+ ;;
+ restart|force-reload)
+ #
+ # If the "reload" option is implemented then remove the
+ # 'force-reload' alias
+ #
+ log_daemon_msg "Restarting $DESC" "$NAME"
+ do_stop
+ case "$?" in
+ 0|1)
+ do_start
+ case "$?" in
+ 0) log_end_msg 0 ;;
+ 1) log_end_msg 1 ;; # Old process is still running
+ *) log_end_msg 1 ;; # Failed to start
+ esac
+ ;;
+ *)
+ # Failed to stop
+ log_end_msg 1
+ ;;
+ esac
+ ;;
+ *)
+ echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
+ exit 3
+ ;;
+esac
diff --git a/etc/lavapdu.conf b/etc/lavapdu.conf
new file mode 100644
index 0000000..fba2a26
--- /dev/null
+++ b/etc/lavapdu.conf
@@ -0,0 +1,8 @@
+{
+ "hostname": "0.0.0.0",
+ "port": 16421,
+ "dbhost": "127.0.0.1",
+ "dbuser": "pdudaemon",
+ "dbpass": "pdudaemon",
+ "dbname": "lavapdu"
+} \ No newline at end of file
diff --git a/etc/lavapdulogrotate b/etc/lavapdulogrotate
new file mode 100644
index 0000000..f113324
--- /dev/null
+++ b/etc/lavapdulogrotate
@@ -0,0 +1,19 @@
+/var/log/lavapdu-listener.log {
+ weekly
+ rotate 12
+ compress
+ delaycompress
+ missingok
+ notifempty
+ create 644 root root
+}
+
+/var/log/lavapdu-runner.log {
+ weekly
+ rotate 12
+ compress
+ delaycompress
+ missingok
+ notifempty
+ create 644 root root
+} \ No newline at end of file
diff --git a/lavapdu-listen b/lavapdu-listen
new file mode 100755
index 0000000..c1e7852
--- /dev/null
+++ b/lavapdu-listen
@@ -0,0 +1,127 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import logging
+import json
+import os
+import sys
+import optparse
+from logging.handlers import WatchedFileHandler
+
+import daemon
+import daemon.pidlockfile
+
+from lavapdu.socketserver import ListenerServer
+
+
+def getDaemonLogger(filePath, log_format=None, loglevel=logging.INFO):
+ logger = logging.getLogger()
+ logger.setLevel(loglevel)
+ try:
+ watchedHandler = WatchedFileHandler(filePath)
+ except Exception as e:
+ return e
+
+ watchedHandler.setFormatter(logging.Formatter(log_format or '%(asctime)s %(msg)s'))
+ logger.addHandler(watchedHandler)
+ return logger, watchedHandler
+
+
+def readSettings(filename):
+ """
+ Read settings from config file, to listen to all hosts, hostname should be 0.0.0.0
+ """
+ settings = {"port": 16421, "hostname": "0.0.0.0", "dbuser": "pdudaemon",
+ "dbpass": "pdudaemon", "dbname": "pdu_queue", "dbhost": "127.0.0.1"}
+ with open(filename) as stream:
+ jobdata = stream.read()
+ json_default = json.loads(jobdata)
+ if "port" in json_default:
+ settings['port'] = json_default['port']
+ if "hostname" in json_default:
+ settings['hostname'] = json_default['hostname']
+ if "dbuser" in json_default:
+ settings['dbuser'] = json_default['dbuser']
+ if "dbpass" in json_default:
+ settings['dbpass'] = json_default['dbpass']
+ if "dbname" in json_default:
+ settings['dbname'] = json_default['dbname']
+ if "dbhost" in json_default:
+ settings['dbhost'] = json_default['dbhost']
+ return settings
+
+if __name__ == '__main__':
+ # instance settings come from django - the coordinator doesn't use django and is
+ # not necessarily per-instance, so use the command line and a default conf file.
+ pidfile = "/var/run/lavapdu-listen.pid"
+ logfile = "/var/log/lavapdu-listener.log"
+ conffile = "/etc/lavapdu.conf"
+ settings = readSettings(conffile)
+ usage = "Usage: %prog [--logfile] --[loglevel]"
+ description = "LAVA PDU request listener server, host and port are handled in %s" % conffile
+ parser = optparse.OptionParser(usage=usage, description=description)
+ parser.add_option("--logfile", dest="logfile", action="store",
+ type="string", help="log file [%s]" % logfile)
+ parser.add_option("--loglevel", dest="loglevel", action="store",
+ type="string", help="logging level [INFO]")
+ (options, args) = parser.parse_args()
+ if options.logfile:
+ if os.path.exists(os.path.dirname(options.logfile)):
+ logfile = options.logfile
+ else:
+ print "No such directory for specified logfile '%s'" % logfile
+ open(logfile, 'w').close()
+ level = logging.DEBUG
+ if options.loglevel == "DEBUG":
+ level = logging.DEBUG
+ if options.loglevel == "WARNING":
+ level = logging.WARNING
+ if options.loglevel == "ERROR":
+ level = logging.ERROR
+ if options.loglevel == "INFO":
+ level = logging.INFO
+ client_logger, watched_file_handler = getDaemonLogger(logfile, loglevel=level,
+ log_format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')
+ if isinstance(client_logger, Exception):
+ print("Fatal error creating client_logger: " + str(client_logger))
+ sys.exit(os.EX_OSERR)
+ # noinspection PyArgumentList
+ lockfile = daemon.pidlockfile.PIDLockFile(pidfile)
+ if lockfile.is_locked():
+ logging.error("PIDFile %s already locked" % pidfile)
+ sys.exit(os.EX_OSERR)
+ context = daemon.DaemonContext(
+ working_directory=os.getcwd(),
+ pidfile=lockfile,
+ files_preserve=[watched_file_handler.stream],
+ stderr=watched_file_handler.stream,
+ stdout=watched_file_handler.stream)
+ starter = {"logging_level": level,
+ "hostname": settings['hostname'],
+ "port": settings['port'],
+ "dbhost": settings["dbhost"],
+ "dbuser": settings["dbuser"],
+ "dbpass": settings["dbpass"],
+ "dbname": settings["dbname"]}
+ with context:
+ logging.info("Running LAVA PDU Listener %s %s %d."
+ % (logfile, settings['hostname'], settings['port']))
+ #logging.getLogger().setLevel(options.loglevel)
+ ListenerServer(starter).start() \ No newline at end of file
diff --git a/lavapdu-runner b/lavapdu-runner
new file mode 100755
index 0000000..9ad9d62
--- /dev/null
+++ b/lavapdu-runner
@@ -0,0 +1,120 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import logging
+import json
+import os
+import sys
+import optparse
+from logging.handlers import WatchedFileHandler
+
+import daemon
+import daemon.pidlockfile
+
+from lavapdu.pdurunner import PDURunner
+
+
+def getDaemonLogger(filePath, log_format=None, loglevel=logging.INFO):
+ logger = logging.getLogger()
+ logger.setLevel(loglevel)
+ try:
+ watchedHandler = WatchedFileHandler(filePath)
+ except Exception as e:
+ return e
+
+ watchedHandler.setFormatter(logging.Formatter(log_format or '%(asctime)s %(msg)s'))
+ logger.addHandler(watchedHandler)
+ return logger, watchedHandler
+
+
+def readSettings(filename):
+ """
+ Read settings from config file, to listen to all hosts, hostname should be 0.0.0.0
+ """
+ settings = {"dbuser": "pdudaemon", "dbpass": "pdudaemon", "dbname": "pdu_queue", "dbhost": "127.0.0.1"}
+ with open(filename) as stream:
+ jobdata = stream.read()
+ json_default = json.loads(jobdata)
+ if "dbuser" in json_default:
+ settings['dbuser'] = json_default['dbuser']
+ if "dbpass" in json_default:
+ settings['dbpass'] = json_default['dbpass']
+ if "dbname" in json_default:
+ settings['dbname'] = json_default['dbname']
+ if "dbhost" in json_default:
+ settings['dbhost'] = json_default['dbhost']
+ return settings
+
+if __name__ == '__main__':
+ # instance settings come from django - the coordinator doesn't use django and is
+ # not necessarily per-instance, so use the command line and a default conf file.
+ pidfile = "/var/run/lavapdu-runner.pid"
+ logfile = "/var/log/lavapdu-runner.log"
+ conffile = "/etc/lavapdu.conf"
+ settings = readSettings(conffile)
+ usage = "Usage: %prog [--logfile] --[loglevel]"
+ description = "LAVA PDU request listener server, host and port are handled in %s" % conffile
+ parser = optparse.OptionParser(usage=usage, description=description)
+ parser.add_option("--logfile", dest="logfile", action="store",
+ type="string", help="log file [%s]" % logfile)
+ parser.add_option("--loglevel", dest="loglevel", action="store",
+ type="string", help="logging level [INFO]")
+ (options, args) = parser.parse_args()
+ if options.logfile:
+ if os.path.exists(os.path.dirname(options.logfile)):
+ logfile = options.logfile
+ else:
+ print "No such directory for specified logfile '%s'" % logfile
+ open(logfile, 'w').close()
+ level = logging.DEBUG
+ if options.loglevel == "DEBUG":
+ level = logging.DEBUG
+ if options.loglevel == "WARNING":
+ level = logging.WARNING
+ if options.loglevel == "ERROR":
+ level = logging.ERROR
+ if options.loglevel == "INFO":
+ level = logging.INFO
+ client_logger, watched_file_handler = getDaemonLogger(logfile, loglevel=level,
+ log_format='%(asctime)s:%(levelname)s:%(name)s:%(message)s')
+ if isinstance(client_logger, Exception):
+ print("Fatal error creating client_logger: " + str(client_logger))
+ sys.exit(os.EX_OSERR)
+ # noinspection PyArgumentList
+ lockfile = daemon.pidlockfile.PIDLockFile(pidfile)
+ if lockfile.is_locked():
+ logging.error("PIDFile %s already locked" % pidfile)
+ sys.exit(os.EX_OSERR)
+ context = daemon.DaemonContext(
+ working_directory=os.getcwd(),
+ pidfile=lockfile,
+ files_preserve=[watched_file_handler.stream],
+ stderr=watched_file_handler.stream,
+ stdout=watched_file_handler.stream)
+ starter = {"logging_level": level,
+ "dbhost": settings["dbhost"],
+ "dbuser": settings["dbuser"],
+ "dbpass": settings["dbpass"],
+ "dbname": settings["dbname"]}
+ with context:
+ logging.info("Running LAVA PDU Runner %s dbhost: %s"
+ % (logfile, settings["dbhost"]))
+ p = PDURunner(starter)
+ p.run_me()
diff --git a/lavapdu/__init__.py b/lavapdu/__init__.py
new file mode 100644
index 0000000..9b73b54
--- /dev/null
+++ b/lavapdu/__init__.py
@@ -0,0 +1,22 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import pdurunner
+import socketserver
diff --git a/lavapdu/apcdrivers.py b/lavapdu/apcdrivers.py
new file mode 100644
index 0000000..0dad9b6
--- /dev/null
+++ b/lavapdu/apcdrivers.py
@@ -0,0 +1,168 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import logging
+
+from driver import PDUDriver
+
+
+class apc7952(PDUDriver):
+ def _pdu_logout(self):
+ self._back_to_main()
+ logging.debug("Logging out")
+ self.connection.send("4\r")
+
+ def _back_to_main(self):
+ logging.debug("Returning to main menu")
+ self.connection.expect('>')
+ for i in range(1, 20):
+ #print("Sending escape character")
+ self.connection.send("\x1B")
+ self.connection.send("\r")
+ res = self.connection.expect(["4- Logout", "> "])
+ if res == 0:
+ logging.debug("Back at main menu")
+ break
+ #self.connection.send("\r")
+ #self.connection.expect("4- Logout")
+ #self.connection.expect("> ")
+
+ def _enter_outlet(self, outlet, enter_needed=True):
+ outlet = "%s" % outlet
+ logging.debug("Attempting to enter outlet %s", outlet)
+ if (enter_needed):
+ self.connection.expect("Press <ENTER> to continue...")
+ logging.debug("Sending enter")
+ self.connection.send("\r")
+ self.connection.expect("> ")
+ logging.debug("Sending outlet number")
+ self.connection.send(outlet)
+ self.connection.send("\r")
+ logging.debug("Finished entering outlet")
+
+ def _port_interaction(self, command, port_number):
+ print("Attempting command: %s port: %i" % (command, port_number))
+ ### make sure in main menu here
+ self._back_to_main()
+ self.connection.send("\r")
+ self.connection.expect("1- Device Manager")
+ self.connection.expect("> ")
+ logging.debug("Entering Device Manager")
+ self.connection.send("1\r")
+ res = self.connection.expect(["3- Outlet Control/Configuration", "2- Outlet Control", "2- Outlet Management", "------- Device Manager"])
+ logging.debug("Matched pattern %s", res)
+ if res == 0:
+ self.connection.send("3\r")
+ self._enter_outlet(port_number)
+ elif res == 1:
+ self.connection.send("2\r")
+ self._enter_outlet(port_number)
+ elif res == 2:
+ self.connection.send("2\r")
+ elif res == 3:
+ logging.debug("Matched ------- Device Manager")
+ self._enter_outlet(port_number, False)
+ res = self.connection.expect(["1- Control Outlet", "1- Outlet Control/Configuration"])
+ self.connection.expect("> ")
+ self.connection.send("1\r")
+ res = self.connection.expect(["> ", "Press <ENTER> to continue..."])
+ if res == 1:
+ logging.debug("Stupid paging thingmy detected, pressing enter")
+ self.connection.send("\r")
+ self.connection.send("\r")
+ res = self.connection.expect(["Control Outlet %s" % port_number, "Control Outlet"])
+ if res == 0:
+ logging.debug("Already at the right port")
+ else:
+ self.connection.send("%s\r" % port_number)
+ self.connection.send("1\r")
+ self.connection.expect("3- Immediate Reboot")
+ self.connection.expect("> ")
+ if command == "reboot":
+ self.connection.send("3\r")
+ self.connection.expect("Immediate Reboot")
+ self._do_it()
+ elif command == "delayed":
+ self.connection.send("6\r")
+ self.connection.expect("Delayed Reboot")
+ self._do_it()
+ elif command == "on":
+ self.connection.send("1\r")
+ self.connection.expect("Immediate On")
+ self._do_it()
+ elif command == "off":
+ self.connection.send("2\r")
+ self.connection.expect("Immediate Off")
+ self._do_it()
+ else:
+ logging.debug("Unknown command!")
+
+ def _do_it(self):
+ self.connection.expect("Enter 'YES' to continue or <ENTER> to cancel :")
+ self.connection.send("YES\r")
+ self.connection.expect("Press <ENTER> to continue...")
+ self.connection.send("\r")
+
+ def port_delayed(self, port_number):
+ self._port_interaction("delayed", port_number)
+
+ def port_on(self, port_number):
+ self._port_interaction("on", port_number)
+
+ def port_off(self, port_number):
+ self._port_interaction("off", port_number)
+
+ def port_reboot(self, port_number):
+ self._port_interaction("reboot", port_number)
+
+
+class apc8959(PDUDriver):
+ connection = None
+ pdu_commands = {"off": "olOff", "on": "olOn", "reboot": "olReboot", "delayed": "olDlyReboot"}
+
+ def _pdu_logout(self):
+ logging.debug("logging out")
+ self.connection.send("\r")
+ self.connection.send("exit")
+ self.connection.send("\r")
+ logging.debug("done")
+
+ def _pdu_get_to_prompt(self):
+ self.connection.send("\r")
+ self.connection.expect('apc>')
+
+ def _port_interaction(self, command, port_number):
+ logging.debug("Attempting %s on port %i" % (command, port_number))
+ self._pdu_get_to_prompt()
+ self.connection.sendline(self.pdu_commands[command] + (" %i" % port_number))
+ self.connection.expect("E000: Success")
+ logging.debug("done")
+
+ def port_delayed(self, port_number):
+ self._port_interaction("delayed", port_number)
+
+ def port_on(self, port_number):
+ self._port_interaction("on", port_number)
+
+ def port_off(self, port_number):
+ self._port_interaction("off", port_number)
+
+ def port_reboot(self, port_number):
+ self._port_interaction("reboot", port_number)
diff --git a/lavapdu/driver.py b/lavapdu/driver.py
new file mode 100644
index 0000000..bd36c18
--- /dev/null
+++ b/lavapdu/driver.py
@@ -0,0 +1,27 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+
+class PDUDriver():
+ connection = None
+ pdu_commands = {"off": "olOff", "on": "olOn", "reboot": "olReboot", "delayed": "olDlyReboot"}
+
+ def __init__(self, connection):
+ self.connection = connection
diff --git a/lavapdu/engine.py b/lavapdu/engine.py
new file mode 100644
index 0000000..beb954f
--- /dev/null
+++ b/lavapdu/engine.py
@@ -0,0 +1,111 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import pexpect
+import os
+import logging
+import sys
+
+from apcdrivers import apc8959
+from apcdrivers import apc7952
+
+
+class PDUEngine():
+ connection = None
+ prompt = 0
+ driver = None
+
+ def __init__(self, pdu_hostname, pdu_telnetport=23):
+ self.exec_string = "/usr/bin/telnet %s %d" % (pdu_hostname, pdu_telnetport)
+ logging.debug("Created new PDUEngine: %s" % self.exec_string)
+ #self.connection.logfile_read = sys.stdout
+ prompt = self._pdu_login("apc", "apc")
+ if prompt == 0:
+ logging.debug("Found a v5 prompt")
+ self.driver = apc8959(self.connection)
+ elif prompt == 1:
+ logging.debug("Found a v3 prompt")
+ self.driver = apc7952(self.connection)
+ else:
+ logging.debug("Unknown prompt!")
+
+ def pduconnect(self):
+ self.connection = self.get_connection(self.exec_string)
+
+ def pduclose(self):
+ self.connection.close(True)
+
+ def pdureconnect(self):
+ self.pduclose()
+ self.pduconnect()
+
+ def get_connection(self, exec_string):
+ connection = pexpect.spawn(exec_string)
+ connection.logfile = sys.stdout
+ return connection
+
+ def is_busy(self):
+ if os.path.exists("/proc/%i" % self.connection.pid):
+ return True
+ return False
+
+ def close(self):
+ self.driver._pdu_logout()
+ self.connection.close(True)
+
+ def _pdu_login(self, username, password):
+ logging.debug("attempting login with username %s, password %s" % (username, password))
+ self.pduconnect()
+ self.connection.send("\r")
+ self.connection.expect("User Name :")
+ self.connection.send("apc\r")
+ self.connection.expect("Password :")
+ self.connection.send("apc\r")
+ return self.connection.expect(["apc>", ">"])
+
+
+if __name__ == "__main__":
+ #pe1 = PDUEngine("pdu15")
+ #pe1.driver.port_off(22)
+ #pe1.driver.port_on(22)
+ #pe1.close()
+ #pe2 = PDUEngine("pdu14")
+ #pe2.driver.port_off(6)
+ #pe2.driver.port_on(6)
+ #pe2.close()
+ #pe3 = PDUEngine("pdu01")
+ #pe3.driver.port_reboot(1)
+ #pe3.driver.port_off(1)
+ #pe3.driver.port_on(1)
+ #pe3.close()
+ logging.basicConfig(level=logging.DEBUG)
+ logging.getLogger().setLevel(logging.DEBUG)
+ pe4 = PDUEngine("192.168.1.153")
+ pe4.driver.port_reboot(1)
+ pe4.driver.port_reboot(2)
+ pe4.driver.port_reboot(3)
+ pe4.driver.port_reboot(4)
+ pe4.driver.port_reboot(5)
+ pe4.driver.port_reboot(6)
+ pe4.driver.port_reboot(7)
+ pe4.driver.port_reboot(8)
+ #pe4.driver.port_off(8)
+ #pe4.driver.port_on(8)
+ pe4.close()
diff --git a/lavapdu/pdurunner.py b/lavapdu/pdurunner.py
new file mode 100644
index 0000000..cc2b19d
--- /dev/null
+++ b/lavapdu/pdurunner.py
@@ -0,0 +1,85 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import logging
+import time
+from engine import PDUEngine
+from socketserver import DBHandler
+
+
+class PDURunner():
+
+ def __init__(self, config):
+ logging.basicConfig(level=config["logging_level"])
+ logging.getLogger().setLevel(config["logging_level"])
+ logging.getLogger().name = "PDURunner"
+ self.config = config
+
+ def get_one(self, db):
+ job = db.get_next_job()
+ if job:
+ job_id, hostname, port, request = job
+ logging.info("Processing queue item: (%s %s) on hostname: %s" % (request, port, hostname))
+ #logging.debug(id, hostname, request, port)
+ res = self.do_job(hostname, port, request)
+ db.delete_row(job_id)
+ else:
+ logging.debug("Found nothing to do in database")
+
+ def do_job(self, hostname, port, request):
+ retries = 5
+ while retries > 0:
+ try:
+ pe = PDUEngine(hostname, 23)
+ if request == "reboot":
+ pe.driver.port_reboot(port)
+ elif request == "on":
+ pe.driver.port_on(port)
+ elif request == "off":
+ pe.driver.port_off(port)
+ elif request == "delayed":
+ pe.driver.port_delayed(port)
+ else:
+ logging.debug("Unknown request type: %s" % request)
+ pe.pduclose()
+ retries = 0
+ except:
+ logging.warn("Failed to execute job: %s %s %s (attempts left %i)" % (hostname, port, request, retries))
+ #logging.warn(e)
+ time.sleep(5)
+ retries -= 1
+
+ def run_me(self):
+ logging.info("Starting up the PDURunner")
+ while 1:
+ db = DBHandler(self.config)
+ self.get_one(db)
+ db.close()
+ del(db)
+ time.sleep(2)
+
+if __name__ == "__main__":
+ starter = {"dbhost": "127.0.0.1",
+ "dbuser": "pdudaemon",
+ "dbpass": "pdudaemon",
+ "dbname": "lavapdu",
+ "logging_level": logging.DEBUG}
+ p = PDURunner(starter)
+ p.run_me()
diff --git a/lavapdu/socketserver.py b/lavapdu/socketserver.py
new file mode 100644
index 0000000..5a6011c
--- /dev/null
+++ b/lavapdu/socketserver.py
@@ -0,0 +1,143 @@
+#! /usr/bin/python
+
+# Copyright 2013 Linaro Limited
+# Author Matt Hart <matthew.hart@linaro.org>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+# MA 02110-1301, USA.
+
+import SocketServer
+import psycopg2
+import logging
+import socket
+
+
+class DBHandler(object):
+ def __init__(self, config):
+ logging.debug("Creating new DBHandler: %s" % config["dbhost"])
+ logging.getLogger().name = "DBHandler"
+ self.conn = psycopg2.connect(database=config["dbname"], user=config["dbuser"],
+ password=config["dbpass"], host=config["dbhost"])
+ self.cursor = self.conn.cursor()
+
+ def do_sql(self, sql):
+ logging.debug("executing sql: %s" % sql)
+ self.cursor.execute(sql)
+ self.conn.commit()
+
+ def do_sql_with_fetch(self, sql):
+ logging.debug("executing sql: %s" % sql)
+ self.cursor.execute(sql)
+ row = self.cursor.fetchone()
+ self.conn.commit()
+ return row
+
+ def delete_row(self, row_id):
+ logging.debug("deleting row %i" % row_id)
+ self.do_sql("delete from pdu_queue where id=%i" % row_id)
+
+ def get_res(self, sql):
+ return self.cursor.execute(sql)
+
+ def get_next_job(self):
+ row = self.do_sql_with_fetch("select * from pdu_queue order by id asc limit 1")
+ return row
+
+ def close(self):
+ logging.debug("Closing DBHandler")
+ self.cursor.close()
+ self.conn.close()
+
+
+class ListenerServer(object):
+
+ def __init__(self, config):
+ self.server = TCPServer((config["hostname"], config["port"]), TCPRequestHandler)
+ logging.getLogger().name = "ListenerServer"
+ logging.getLogger().setLevel(config["logging_level"])
+ logging.info("listening on %s:%s" % (config["hostname"], config["port"]))
+ self.server.config = config
+ self.db = DBHandler(config)
+ self.create_db()
+ self.db.close()
+ del(self.db)
+
+ def create_db(self):
+ sql = "create table if not exists pdu_queue (id serial, hostname text, port int, request text)"
+ self.db.do_sql(sql)
+
+ def start(self):
+ logging.info("Starting the ListenerServer")
+ self.server.serve_forever()
+
+
+class TCPRequestHandler(SocketServer.BaseRequestHandler):
+ #"One instance per connection. Override handle(self) to customize action."
+ def insert_request(self, data):
+ logging.getLogger().name = "TCPRequestHandler"
+ array = data.split(" ")
+ if len(array) != 3:
+ logging.info("Wrong data size")
+ raise Exception("Unexpected data")
+ hostname = array[0]
+ port = int(array[1])
+ request = array[2]
+ if not (request in ["reboot", "on", "off", "delayed"]):
+ logging.info("Unknown request: %s" % request)
+ raise Exception("Unknown request: %s" % request)
+ db = DBHandler(self.server.config)
+ sql = "insert into pdu_queue (hostname,port,request) values ('%s',%i,'%s')" % (hostname, port, request)
+ db.do_sql(sql)
+ db.close()
+ del(db)
+
+ def handle(self):
+ logging.getLogger().name = "TCPRequestHandler"
+ ip = self.client_address[0]
+ try:
+ data = self.request.recv(4096).strip()
+ socket.setdefaulttimeout(2)
+ try:
+ request_host = socket.gethostbyaddr(ip)[0]
+ except socket.herror as e:
+ logging.debug("Unable to resolve: %s error: %s" % (ip, e))
+ request_host = ip
+ logging.info("Received a request from %s: '%s'" % (request_host, data))
+ self.insert_request(data)
+ self.request.sendall("ack\n")
+ except Exception as e:
+ logging.debug(e)
+ self.request.sendall("nack\n")
+ self.request.close()
+
+
+class TCPServer(SocketServer.ThreadingMixIn, SocketServer.TCPServer):
+ allow_reuse_address = True
+ daemon_threads = True
+ pass
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.DEBUG)
+ logging.getLogger().setLevel(logging.DEBUG)
+ logging.debug("Executing from __main__")
+ starter = {"hostname": "0.0.0.0",
+ "port": 16421,
+ "dbhost": "127.0.0.1",
+ "dbuser": "pdudaemon",
+ "dbpass": "pdudaemon",
+ "dbname": "lavapdu",
+ "logging_level": logging.DEBUG}
+ ss = ListenerServer(starter)
+ ss.start()
diff --git a/pduclient b/pduclient
new file mode 100755
index 0000000..fdcdcb3
--- /dev/null
+++ b/pduclient
@@ -0,0 +1,40 @@
+#!/usr/bin/python
+
+import socket
+import optparse
+
+if __name__ == '__main__':
+ usage = "Usage: %prog [-d] deamonhostname [-h] pduhostname -[p] pduportnum [-c] pducommand"
+ description = "LAVA PDU daemon client"
+ commands = ["reboot", "on", "off", "delayed"]
+ parser = optparse.OptionParser(usage=usage, description=description)
+ parser.add_option("--daemon", dest="pdudaemonhostname", action="store", type="string", help="LAVAPDU Daemon (ex: control)")
+ parser.add_option("--hostname", dest="pduhostname", action="store", type="string", help="PDU Hostname (ex: pdu05)")
+ parser.add_option("--port", dest="pduportnum", action="store", type="string", help="PDU Portnumber (ex: 04)")
+ parser.add_option("--command", dest="pducommand", action="store", type="string", help="PDU command (ex: reboot|on|off|delayed)")
+ (options, args) = parser.parse_args()
+ if (not (options.pdudaemonhostname) or not(options.pduhostname) or not (options.pduportnum) or not (options.pducommand)):
+ print("Missing option, try -h for help")
+ exit(1)
+ if not (options.pducommand in commands):
+ print("Unknown pdu command: %s" % options.pducommand)
+ exit(1)
+ #print(options)
+ string = ("%s %s %s" % (options.pduhostname, options.pduportnum, options.pducommand))
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ #sock.setblocking(0) # optional non-blocking
+ reply = ""
+ try:
+ sock.connect((options.pdudaemonhostname, 16421))
+ sock.send(string)
+ reply = sock.recv(16384).strip() # limit reply to 16K
+ sock.close()
+ except Exception:
+ print ("Error sending command, wrong hostname?")
+ exit(1)
+ if reply == "ack":
+ print("Command sent successfully.")
+ exit(0)
+ else:
+ print("Unknown error sending command! %s replied: %s" % (options.pdudaemonhostname, reply))
+ exit(127)
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..f74a3eb
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,50 @@
+#! /usr/bin/env python
+#
+# Copyright (C) 2013 Linaro Limited
+#
+# Author: Matthew Hart <matthew.hart@linaro.org>
+#
+# This file is part of LAVA-PDU.
+#
+# LAVA Coordinator is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# LAVA Coordinator is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, see <http://www.gnu.org/licenses>.
+
+from setuptools import setup, find_packages
+
+setup(
+ name='lavapdu',
+ version="0.0.2",
+ author="Matthew Hart",
+ author_email="matthew.hart@linaro.org",
+ license="GPL2+",
+ description="LAVA PDU Deamon for APC PDU's",
+ packages=find_packages(),
+ install_requires=[
+ "daemon",
+ "lockfile",
+ "pexpect",
+ "psycopg2"
+ ],
+ data_files=[
+ ("/etc/init.d/", ["etc/lavapdu-runner.init"]),
+ ("/etc/init.d/", ["etc/lavapdu-listen.init"]),
+ ("/etc/", ["etc/lavapdu.conf"]),
+ ("/etc/logrotate.d/", ["etc/lavapdulogrotate"]),
+ ],
+ scripts=[
+ 'lavapdu-runner',
+ 'lavapdu-listen',
+ 'pduclient'
+ ],
+ zip_safe=False,
+ include_package_data=True)
diff --git a/var/lib/lavapdu/pdu.db b/var/lib/lavapdu/pdu.db
new file mode 100644
index 0000000..902f2e1
--- /dev/null
+++ b/var/lib/lavapdu/pdu.db
Binary files differ