diff options
author | Tyler Baker <tyler.baker@linaro.org> | 2014-04-17 14:14:39 -0700 |
---|---|---|
committer | Tyler Baker <tyler.baker@linaro.org> | 2014-04-17 14:14:39 -0700 |
commit | bc8b057384ebc289f6f708818c78690b4c4d36d4 (patch) | |
tree | 2ec621a6a28ddeebc8f7581e3edcdfd5878c4824 |
Initial commit
-rw-r--r-- | .gitignore | 2 | ||||
-rw-r--r-- | .gitreview | 4 | ||||
-rw-r--r-- | MANIFEST.in | 4 | ||||
-rw-r--r-- | etc/lavapdu-listen.init | 128 | ||||
-rw-r--r-- | etc/lavapdu-runner.init | 128 | ||||
-rw-r--r-- | etc/lavapdu.conf | 8 | ||||
-rw-r--r-- | etc/lavapdulogrotate | 19 | ||||
-rwxr-xr-x | lavapdu-listen | 127 | ||||
-rwxr-xr-x | lavapdu-runner | 120 | ||||
-rw-r--r-- | lavapdu/__init__.py | 22 | ||||
-rw-r--r-- | lavapdu/apcdrivers.py | 168 | ||||
-rw-r--r-- | lavapdu/driver.py | 27 | ||||
-rw-r--r-- | lavapdu/engine.py | 111 | ||||
-rw-r--r-- | lavapdu/pdurunner.py | 85 | ||||
-rw-r--r-- | lavapdu/socketserver.py | 143 | ||||
-rwxr-xr-x | pduclient | 40 | ||||
-rw-r--r-- | setup.py | 50 | ||||
-rw-r--r-- | var/lib/lavapdu/pdu.db | bin | 0 -> 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 Binary files differnew file mode 100644 index 0000000..902f2e1 --- /dev/null +++ b/var/lib/lavapdu/pdu.db |