pax_global_header00006660000000000000000000000064133644024030014511gustar00rootroot0000000000000052 comment=704b6219ecc4ea80184617ea1a9e1d3ca51bd2ef pdudaemon-0.0.7/000077500000000000000000000000001336440240300134715ustar00rootroot00000000000000pdudaemon-0.0.7/.gitignore000066400000000000000000000000371336440240300154610ustar00rootroot00000000000000dist/ *.pyc .idea/ *.egg-info/ pdudaemon-0.0.7/.travis.yml000066400000000000000000000007501336440240300156040ustar00rootroot00000000000000notifications: irc: "chat.freenode.net#pdudaemon" on_success: always on_failure: always use_notice: false skip_join: false services: - docker before_install: - tar -c requirements.txt share/Dockerfile.travis | docker build -t pdudaemon -f share/Dockerfile.travis - script: - docker run -v $(pwd):/p -w /p pdudaemon pycodestyle --ignore=E501 . - docker run -v $(pwd):/p -w /p pdudaemon sh -c "python3 ./setup.py install && ./share/pdudaemon-test.sh" pdudaemon-0.0.7/Dockerfile.dockerhub000066400000000000000000000006751336440240300174400ustar00rootroot00000000000000FROM debian:stretch RUN apt-get update && apt-get install -y pep8 \ python3-setuptools \ python3-pip \ libsystemd-dev \ curl \ psmisc \ pkg-config \ libffi-dev \ libhidapi-dev \ libudev-dev \ libusb-1.0-0-dev \ cython3 \ supervisor ADD share/pdudaemon.conf /config/ WORKDIR /pdudaemon COPY . . RUN pip3 install --user -r ./requirements.txt RUN python3 ./setup.py install CMD ["/usr/bin/supervisord", "-c", "/pdudaemon/share/supervisord.conf"] pdudaemon-0.0.7/MANIFEST.in000066400000000000000000000000201336440240300152170ustar00rootroot00000000000000include share/* pdudaemon-0.0.7/README.md000066400000000000000000000052731336440240300147570ustar00rootroot00000000000000# PDUDaemon Python daemon for controlling/sequentially executing commands to PDUs (Power Distribution Units) ## Why is this needed? #### Queueing Most PDUs have a very low power microprocessor, or low quality software, which cannot handle multiple requests at the same time. This quickly becomes an issue in environments that use power control frequently, such as board farms, and gets worse on PDUs that have a large number of controllable ports. #### Standardising Every PDU manufacturer has a different way of controlling their PDUs. Though many support SNMP, there's still no single simple way to communicate with all PDUs if you have a mix of brands. ## Supported devices list APC, Devantech and ACME are well supported, however there is no official list yet. The [strategies.py](https://github.com/pdudaemon/pdudaemon/blob/master/pdudaemon/drivers/strategies.py) file is a good place to see all the current drivers. ## Installing Debian packages are on the way, hopefully. For now, make sure the requirements are met and then: ```python3 setup.py install``` ## Config file To be added. ## Making a power control request - **HTTP** The daemon can accept requests over plain HTTP. The port is configurable, but defaults to 16421 There is no encryption or authentication, consider yourself warned. To enable, change the 'listener' setting in the 'daemon' section of the config file to 'http'. This will break 'pduclient' requests. An example request would be ``` curl http://pdudaemonhostname:16421/power/control/on?hostname=pdu01&port=1``` ***Return Codes*** - HTTP 200 - Request Accepted - HTTP 503 - Invalid Request, Request not accepted - **pduclient** The bundled client is used when PDUDaemon is configured to listen to 'tcp' requests. ``` Usage: pduclient --daemon deamonhostname --hostname pduhostname --port pduportnum --command pducommand PDUDaemon client Options: -h, --help show this help message and exit --daemon=PDUDAEMONHOSTNAME PDUDaemon listener hostname (ex: localhost) --hostname=PDUHOSTNAME PDU Hostname (ex: pdu05) --port=PDUPORTNUM PDU Portnumber (ex: 04) --command=PDUCOMMAND PDU command (ex: reboot|on|off) --delay=PDUDELAY Delay before command runs, or between off/on when rebooting (ex: 5) ``` ## Adding drivers PDUDaemon was written to accept "plugin" style driver files. There's no official example yet, so take a look in the [drivers](https://github.com/pdudaemon/pdudaemon/tree/master/pdudaemon/drivers) directory and see if you can adapt one. ## Why can't PDUDaemon do $REQUIREMENT? Patches welcome, as long as it keeps the system simple and lightweight. ## Contact #pdudaemon on Freenode pdudaemon-0.0.7/pduclient000077500000000000000000000102231336440240300154040ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 optparse def do_tcp_request(options): import socket if options.pdudelay: string = ("%s %s %s %s" % (options.pduhostname, options.pduportnum, options.pducommand, options.pdudelay)) else: string = ("%s %s %s" % (options.pduhostname, options.pduportnum, options.pducommand)) sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) reply = "" try: sock.connect((options.pdudaemonhostname, 16421)) sock.send(string.encode('utf-8')) reply = sock.recv(16384).strip() # limit reply to 16K reply = reply.decode('utf-8') sock.close() except Exception: print ("Error sending command, wrong daemon hostname?") exit(1) if reply == "ack": print("Command sent successfully.") exit(0) else: print("Error sending command! %s replied: %s" % (options.pdudaemonhostname, reply)) exit(127) def do_http_request(options): import requests request = "http://%s:16421/power/control/%s?hostname=%s&port=%s" % ( options.pdudaemonhostname, options.pducommand, options.pduhostname, options.pduportnum) if options.pdudelay: request += "&delay=%s" % options.pdudelay try: reply = requests.get(request) except Exception: print ("Error sending command, wrong daemon hostname?") exit(1) if reply.ok: print("Command sent successfully.") exit(0) else: print("Error sending command! %s replied: %s" % (options.pdudaemonhostname, reply)) exit(127) if __name__ == '__main__': usage = "Usage: %prog --daemon deamonhostname --hostname pduhostname " \ "--port pduportnum --command pducommand" description = "PDUDaemon client" commands = ["reboot", "on", "off"] parser = optparse.OptionParser(usage=usage, description=description) parser.add_option("-H", "--http", dest="use_http", action="store_true", help="Use HTTP protocol") parser.add_option("--daemon", dest="pdudaemonhostname", action="store", type="string", help="PDUDaemon listener hostname (ex: localhost)") 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)") parser.add_option("--delay", dest="pdudelay", action="store", type="int", help="Delay before command runs, or between off/on " "when rebooting (ex: 5)") (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) if options.use_http: do_http_request (options) else: do_tcp_request (options) pdudaemon-0.0.7/pdudaemon/000077500000000000000000000000001336440240300154455ustar00rootroot00000000000000pdudaemon-0.0.7/pdudaemon/__about__.py000066400000000000000000000026051336440240300177300ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright 2018 Remi Duraffort # Matt Hart # # 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. __all__ = ["__author__", "__description__", "__license__", "__url__", "__version__"] def git_hash(): import subprocess try: out = subprocess.check_output(["git", "rev-parse", "--short", "HEAD"], stderr=subprocess.STDOUT) return out.decode("utf-8").rstrip("\n") except Exception: return "git" __author__ = "Matt Hart" __description__ = 'Control and Queueing daemon for PDUs' __license__ = 'GPLv2+' __url__ = 'https://github.com/pdudaemon/pdudaemon.git' __version__ = "0.1" pdudaemon-0.0.7/pdudaemon/__init__.py000066400000000000000000000170331336440240300175620ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright 2018 Remi Duraffort # Matt Hart # # 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 argparse import contextlib import json import logging from logging.handlers import WatchedFileHandler from queue import Empty, Queue import signal import sqlite3 import sys import time from pdudaemon.tcplistener import TCPListener from pdudaemon.httplistener import HTTPListener from pdudaemon.pdurunner import PDURunner from pdudaemon.drivers.driver import PDUDriver ########### # Constants ########### CONFIGURATION_FILE = "/etc/pdudaemon/pdudaemon.conf" logging_FORMAT = "%(asctime)s - %(name)-30s - %(levelname)s %(message)s" logging_FORMAT_JOURNAL = "%(name)s.%(levelname)s %(message)s" logging_FILE = "/var/log/pdudaemon.log" ################## # Global logger ################## logger = logging.getLogger('pdud') def setup_logging(options): logger = logging.getLogger("pdud") """ Setup the log handler and the log level """ if options.journal: from systemd.journal import JournalHandler handler = JournalHandler(SYSLOG_IDENTIFIER="pdudaemon") handler.setFormatter(logging.Formatter(logging_FORMAT_JOURNAL)) elif options.logfile == "-" or not options.logfile: handler = logging.StreamHandler(sys.stdout) handler.setFormatter(logging.Formatter(logging_FORMAT)) else: handler = WatchedFileHandler(options.logfile) handler.setFormatter(logging.Formatter(logging_FORMAT)) logger.addHandler(handler) options.loglevel = options.loglevel.upper() if options.loglevel == "DEBUG": logger.setLevel(logging.DEBUG) elif options.loglevel == "INFO": logger.setLevel(logging.INFO) elif options.loglevel == "WARNING": logger.setLevel(logging.WARNING) else: logger.setLevel(logging.ERROR) class TasksDB(object): def __init__(self, dbname): self.conn = sqlite3.connect(dbname) self.conn.row_factory = sqlite3.Row self.conn.execute("CREATE TABLE IF NOT EXISTS tasks(id INTEGER PRIMARY KEY AUTOINCREMENT, " "hostname TEXT, port INTEGER, request TEXT, exectime INTEGER)") self.conn.commit() def create(self, hostname, port, request, exectime): try: self.conn.execute("INSERT INTO tasks (hostname, port, request, exectime) VALUES(?, ?, ?, ?)", (hostname, port, request, int(exectime))) self.conn.commit() return 0 except sqlite3.Error: return 1 def delete(self, task_id): with contextlib.suppress(sqlite3.Error): self.conn.execute("DELETE FROM tasks WHERE id=?", (task_id, )) self.conn.commit() def next(self, hostname): row = self.conn.execute("SELECT * FROM tasks WHERE hostname=? AND exectime < ? ORDER BY id ASC LIMIT 1", (hostname, int(time.time()))).fetchone() return row def main(): # Setup the parser parser = argparse.ArgumentParser() log = parser.add_argument_group("logging") log.add_argument("--journal", "-j", action="store_true", default=False, help="Log to the journal") log.add_argument("--logfile", dest="logfile", action="store", type=str, default="-", help="log file [%s]" % logging_FILE) log.add_argument("--loglevel", dest="loglevel", default="INFO", choices=["DEBUG", "ERROR", "INFO", "WARN"], type=str, help="logging level [INFO]") parser.add_argument("--conf", "-c", type=argparse.FileType("r"), default=CONFIGURATION_FILE, help="configuration file [%s]" % CONFIGURATION_FILE) parser.add_argument("--dbfile", "-d", type=str, help="SQLite3 db file") # Parse the command line options = parser.parse_args() # Setup logging setup_logging(options) logger.info('PDUDaemon starting up') # Read the configuration file try: settings = json.loads(options.conf.read()) except Exception as exc: logging.error("Unable to read configuration file '%s': %s", options.conf.name, exc) return 1 dbfile = options.dbfile if options.dbfile else settings['daemon']['dbname'] # Context workers = {} db_queue = Queue() dbhandler = TasksDB(dbfile) # Starting the workers logger.info("Starting the Workers") for hostname in settings["pdus"]: config = settings["pdus"][hostname] retries = config.get("retries", 5) task_queue = Queue() workers[hostname] = {"thread": PDURunner(config, hostname, task_queue, db_queue, retries), "queue": task_queue} workers[hostname]["thread"].start() # Start the listener logger.info("Starting the listener") listener = settings['daemon'].get('listener', 'tcp') if listener == 'tcp': listener = TCPListener(settings, db_queue) elif listener == 'http': listener = HTTPListener(settings, db_queue) else: logging.error("Unknown listener configured") listener.start() # Setup signal handling def signal_handler(signum, frame): logger.info("Signal received, shutting down listener") listener.shutdown() raise KeyboardInterrupt signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGTERM, signal_handler) # Main loop try: while True: with contextlib.suppress(Empty): while True: task = db_queue.get_nowait() action = task[0] logger.debug("db actions %s", action) if action == "CREATE": ret = dbhandler.create(task[1], task[2], task[3], task[4]) logger.debug("ret=%d", ret) elif action == "DELETE": dbhandler.delete(task[1]) for worker in workers: # Is the last task done if not workers[worker]["queue"].unfinished_tasks: task = dbhandler.next(worker) if task is not None: task_id = task["id"] port = task["port"] request = task["request"] logger.debug("put for %s: '%s' to port %s [id:%s]", worker, request, port, task_id) workers[worker]["queue"].put((task["id"], task["port"], task["request"])) # TODO: compute the timeout correctly time.sleep(1) except KeyboardInterrupt: pass finally: logger.info("Waiting for workers to finish...") for worker in workers: workers[worker]["queue"].put(None) workers[worker]["thread"].join() sys.exit() return 0 if __name__ == "__main__": # execute only if run as a script main() pdudaemon-0.0.7/pdudaemon/drivers/000077500000000000000000000000001336440240300171235ustar00rootroot00000000000000pdudaemon-0.0.7/pdudaemon/drivers/__init__.py000066400000000000000000000014641336440240300212410ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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. pdudaemon-0.0.7/pdudaemon/drivers/acme.py000066400000000000000000000041111336440240300203770ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2015 BayLibre SAS # Author Marc Titinger # # Based on apcxxx: # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.acmebase import ACMEBase import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class ACME(ACMEBase): cmd = {'on': 'dut-switch-on', 'off': 'dut-switch-off', 'reboot': 'dut-hard-reset'} @classmethod def accepts(cls, drivername): if drivername == "acme": return True return False def _pdu_logout(self): self._back_to_main() log.debug("Logging out") self.connection.send("exit\r") def _back_to_main(self): self.connection.send("\r") self.connection.expect('#') def _enter_outlet(self, outlet, enter_needed=True): log.debug("Finished entering outlet (nop)") def _port_interaction(self, command, port_number): if command not in self.cmd: acme_command = 'echo "unknown command {}"'.format(command) else: acme_command = '{} {}'.format(self.cmd[command], port_number) log.debug("Attempting command: %s", acme_command) self.connection.send(acme_command) self._do_it() self.connection.expect("#") def _do_it(self): self.connection.send("\r") pdudaemon-0.0.7/pdudaemon/drivers/acmebase.py000066400000000000000000000056051336440240300212430ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2015 BayLibre SAS # Author Marc Titinger # # Based on ACPBase: # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pexpect from pdudaemon.drivers.driver import PDUDriver import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) # SSH connection, assuming the id_rsa.pub key for the owner of pdudaemon-runner # i.e. root, was added to the authorized_keys on the ACME device. # This may be changed to a simple telnet connection in the future. class ACMEBase(PDUDriver): connection = None def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.username = settings.get("username", "root") self.exec_string = "/usr/bin/ssh %s@%s" % (self.username, hostname) super(ACMEBase, self).__init__() @classmethod def accepts(cls, drivername): # pylint: disable=unused-argument return False def port_interaction(self, command, port_number): log.debug("Running port_interaction from ACMEBase") self.get_connection() self._port_interaction(command, # pylint: disable=no-member port_number) def get_connection(self): log.debug("Connecting to Baylibre ACME with: %s", self.exec_string) # only uncomment this line for FULL debug when developing # self.connection = pexpect.spawn(self.exec_string, logfile=sys.stdout) self.connection = pexpect.spawn(self.exec_string) self._pdu_login(self.username, "") def _cleanup(self): self._pdu_logout() # pylint: disable=no-member def _bombout(self): log.debug("Bombing out of driver: %s", self.connection) self.connection.close(force=True) del self def _pdu_login(self, username, password): log.debug("attempting login with username %s, password %s", username, password) index = self.connection.expect(['#', 'password', 'yes/no']) if index == 1: self.connection.send("%s\r" % password) elif index == 2: self.connection.send("yes\r") pdudaemon-0.0.7/pdudaemon/drivers/apc7921.py000066400000000000000000000043311336440240300205640ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2017 Matt Hart # # 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 pdudaemon.drivers.apc7952 import APC7952 import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class APC7921(APC7952): @classmethod def accepts(cls, drivername): if drivername == "apc7921": return True return False def _port_interaction(self, command, port_number): log.debug("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("> ") log.debug("Entering Device Manager") self.connection.send("1\r") self.connection.expect("------- Device Manager") self.connection.send("2\r") self.connection.expect("1- Outlet Control/Configuration") self.connection.expect("> ") self.connection.send("1\r") self._enter_outlet(port_number, False) self.connection.expect("1- Control Outlet") self.connection.send("1\r") self.connection.expect("> ") if 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: log.debug("Unknown command!") pdudaemon-0.0.7/pdudaemon/drivers/apc7932.py000066400000000000000000000042651336440240300205740ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2016 EfficiOS # Author Jonathan Rajotte-Julien # # 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 pdudaemon.drivers.apc7952 import APC7952 import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class APC7932(APC7952): @classmethod def accepts(cls, drivername): if drivername == "apc7932": return True return False def _port_interaction(self, command, port_number): log.debug("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("> ") log.debug("Entering Device Manager") self.connection.send("1\r") self.connection.expect("------- Device Manager") self.connection.send("2\r") self.connection.expect("1- Outlet Control/Configuration") self.connection.expect("> ") self.connection.send("1\r") self._enter_outlet(port_number, False) self.connection.expect("> ") if 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: log.debug("Unknown command!") pdudaemon-0.0.7/pdudaemon/drivers/apc7952.py000066400000000000000000000073371336440240300206010ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.apcbase import APCBase import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class APC7952(APCBase): @classmethod def accepts(cls, drivername): if drivername == "apc7952": return True return False def _pdu_logout(self): self._back_to_main() log.debug("Logging out") self.connection.send("4\r") def _back_to_main(self): log.debug("Returning to main menu") self.connection.send("\r") self.connection.expect('>') for _ in range(1, 20): self.connection.send("\x1B") self.connection.send("\r") res = self.connection.expect(["4- Logout", "> "]) if res == 0: log.debug("Back at main menu") break def _enter_outlet(self, outlet, enter_needed=True): outlet = "%s" % outlet log.debug("Attempting to enter outlet %s", outlet) if enter_needed: self.connection.expect("Press to continue...") log.debug("Sending enter") self.connection.send("\r") self.connection.expect("> ") log.debug("Sending outlet number") self.connection.send(outlet) self.connection.send("\r") log.debug("Finished entering outlet") def _port_interaction(self, command, port_number): log.debug("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("> ") log.debug("Entering Device Manager") self.connection.send("1\r") self.connection.expect("------- Device Manager") self.connection.send("2\r") self.connection.expect("1- Outlet Control/Configuration") self.connection.expect("> ") self.connection.send("1\r") self._enter_outlet(port_number, False) self.connection.expect("> ") self.connection.send("1\r") res = self.connection.expect(["> ", "Press to continue..."]) if res == 1: log.debug("Stupid paging thingmy detected, pressing enter") self.connection.send("\r") self.connection.send("\r") if 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: log.debug("Unknown command!") def _do_it(self): self.connection.expect("Enter 'YES' to continue or " " to cancel :") self.connection.send("YES\r") self.connection.expect("Press to continue...") self.connection.send("\r") pdudaemon-0.0.7/pdudaemon/drivers/apc8959.py000066400000000000000000000034411336440240300206010ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.apcbase import APCBase import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class APC8959(APCBase): pdu_commands = {"off": "olOff", "on": "olOn"} @classmethod def accepts(cls, drivername): if drivername == "apc8959": return True return False def _pdu_logout(self): log.debug("logging out") self.connection.send("\r") self.connection.send("exit") self.connection.send("\r") log.debug("done") def _pdu_get_to_prompt(self): self.connection.send("\r") self.connection.expect('apc>') def _port_interaction(self, command, port_number): log.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") self._pdu_get_to_prompt() log.debug("done") pdudaemon-0.0.7/pdudaemon/drivers/apc9210.py000066400000000000000000000044041336440240300205560ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.apc7952 import APC7952 import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class APC9210(APC7952): @classmethod def accepts(cls, drivername): if drivername == "apc9210": return True return False def _port_interaction(self, command, port_number): log.debug("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- Outlet Manager") self.connection.expect("> ") log.debug("Entering Outlet Manager") self.connection.send("1\r") self.connection.expect("------- Outlet Manager") log.debug("Got to Device Manager") self._enter_outlet(port_number, False) self.connection.expect(["1- Control of Outlet", "1- Outlet Control/Configuration"]) self.connection.expect("> ") self.connection.send("1\r") self.connection.expect("Turn Outlet On") self.connection.expect("> ") if command == "on": self.connection.send("1\r") self.connection.expect("Turn Outlet On") self._do_it() elif command == "off": self.connection.send("2\r") self.connection.expect("Turn Outlet Off") self._do_it() else: log.debug("Unknown command!") pdudaemon-0.0.7/pdudaemon/drivers/apc9218.py000066400000000000000000000051421336440240300205660ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.apc7952 import APC7952 import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class APC9218(APC7952): @classmethod def accepts(cls, drivername): models = ["ap9606", "apc9606", "ap9218", "apc9218"] if drivername.lower() in models: return True return False def _port_interaction(self, 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("> ") log.debug("Entering Device Manager") self.connection.send("1\r") self.connection.expect("------- Device Manager") log.debug("Got to Device Manager") self._enter_outlet(port_number, False) self.connection.expect(["1- Control Outlet", "1- Outlet Control/Configuration"]) self.connection.expect("> ") self.connection.send("1\r") res = self.connection.expect(["> ", "Press to continue..."]) if res == 1: log.debug("Stupid paging thingmy detected, pressing enter") self.connection.send("\r") self.connection.send("\r") self.connection.expect(["Control Outlet %s" % port_number, "Control Outlet"]) self.connection.expect("3- Immediate Reboot") self.connection.expect("> ") if 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: log.debug("Unknown command!") pdudaemon-0.0.7/pdudaemon/drivers/apcbase.py000066400000000000000000000052551336440240300211020ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pexpect from pdudaemon.drivers.driver import PDUDriver import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class APCBase(PDUDriver): connection = None def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.username = settings.get("username", "apc") self.password = settings.get("password", "apc") telnetport = settings.get('telnetport', 23) self.exec_string = "/usr/bin/telnet %s %d" % (hostname, telnetport) super(APCBase, self).__init__() @classmethod def accepts(cls, drivername): return False def port_interaction(self, command, port_number): log.debug("Running port_interaction from APCBase") self.get_connection() self._port_interaction(command, # pylint: disable=no-member port_number) def get_connection(self): log.debug("Connecting to APC PDU with: %s", self.exec_string) # only uncomment this line for FULL debug when developing # self.connection = pexpect.spawn(self.exec_string, logfile=sys.stdout) self.connection = pexpect.spawn(self.exec_string) self._pdu_login(self.username, self.password) def _cleanup(self): if self.connection: self._pdu_logout() # pylint: disable=no-member self.connection.close() def _bombout(self): if self.connection: self.connection.close(force=True) def _pdu_login(self, username, password): log.debug("attempting login with username %s, password %s", username, password) self.connection.send("\r") self.connection.expect("User Name :") self.connection.send("%s\r" % username) self.connection.expect("Password :") self.connection.send("%s\r" % password) pdudaemon-0.0.7/pdudaemon/drivers/devantech.py000066400000000000000000000120161336440240300214360ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2016 Quentin Schulz # # Based on PDUDriver: # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.driver import PDUDriver import socket import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class DevantechBase(PDUDriver): connection = None port_count = 0 def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.ip = settings["ip"] self.port = settings.get("port", 17494) self.password = settings.get("password") super(DevantechBase, self).__init__() def connect(self): self.connection = socket.create_connection((self.ip, self.port)) if self.password: log.debug("Attempting connection to %s:%s with provided password.", self.hostname, self.port) msg = b'\x79' + self.password.encode("utf-8") ret = self.connection.sendall(msg) if ret: log.error("Failed to send message.") raise RuntimeError("Failed to send message.") ret = self.connection.recv(1) if ret != b'\x01': log.error("Authentication failed.") raise RuntimeError("Failed to authenticate. Verify your password.") def port_interaction(self, command, port_number): self.connect() if port_number > self.port_count: log.error("There are only %d ports. Provide a port number lesser than %d." % (self.port_count, self.port_count)) raise RuntimeError("There are only %d ports. Provide a port number lesser than %d." % (self.port_count, self.port_count)) if command == "on": msg = b'\x20' elif command == "off": msg = b'\x21' else: log.error("Unknown command %s." % (command)) return msg += port_number.to_bytes(1, 'big') msg += b'\x00' log.debug("Attempting control: %s port: %d hostname: %s." % (command, port_number, self.hostname)) ret = self.connection.sendall(msg) if ret: log.error("Failed to send message.") raise RuntimeError("Failed to send message.") ret = self.connection.recv(1) if ret != b'\x00': log.error("Failed to send %s command on port %d of %s." % (command, port_number, self.hostname)) raise RuntimeError("Failed to send %s command on port %d of %s." % (command, port_number, self.hostname)) def _close_connection(self): # Logout log.debug("Closing connection.") if self.password: log.debug("Attempting to logout.") ret = self.connection.sendall(b'\x7B') if ret: log.error("Failed to send message.") raise RuntimeError("Failed to send message.") ret = self.connection.recv(1) if ret != b'\x00': log.error("Failed to logout of %s." % self.hostname) raise RuntimeError("Failed to logout of %s." % self.hostname) self.connection.close() def _cleanup(self): self._close_connection() def _bombout(self): self._close_connection() @classmethod def accepts(cls, drivername): return False class DevantechETH002(DevantechBase): port_count = 2 @classmethod def accepts(cls, drivername): if drivername == "devantech_eth002": return True return False class DevantechETH0621(DevantechBase): port_count = 2 @classmethod def accepts(cls, drivername): if drivername == "devantech_eth0621": return True return False class DevantechETH484(DevantechBase): port_count = 4 @classmethod def accepts(cls, drivername): if drivername == "devantech_eth484": return True return False class DevantechETH008(DevantechBase): port_count = 8 @classmethod def accepts(cls, drivername): if drivername == "devantech_eth008": return True return False class DevantechETH8020(DevantechBase): port_count = 20 @classmethod def accepts(cls, drivername): if drivername == "devantech_eth8020": return True return False pdudaemon-0.0.7/pdudaemon/drivers/devantechusb.py000066400000000000000000000047541336440240300221620ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2017 Sjoerd Simons # # Based on PDUDriver: # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.driver import PDUDriver import serial import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class DevantechusbBase(PDUDriver): connection = None port_count = 0 supported = [] def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.device = settings.get("device", "/dev/ttyACM0") log.debug("device: %s" % self.device) super(DevantechusbBase, self).__init__() def port_interaction(self, command, port_number): if port_number > self.port_count or port_number < 1: err = "Port should be in the range 1 - %d" % (self.port_count) log.error(err) raise RuntimeError(err) if command == "on": byte = 0x64 + port_number elif command == "off": byte = 0x6e + port_number else: log.error("Unknown command %s." % (command)) return s = serial.Serial(self.device, 9600) s.write([byte]) s.close() @classmethod def accepts(cls, drivername): return drivername in cls.supported class DevantechUSB2(DevantechusbBase): port_count = 2 supported = ["devantech_USB-RLY02", "devantech_USB-RLY82"] # Various 8 relay devices class DevantechUSB8(DevantechusbBase): port_count = 8 supported = ["devantech_USB-RLY08B", "devantech_USB-RLY16", "devantech_USB-RLY16L", "devantech_USB-OPTO-RLY88", "devantech_USB-OPTO-RLY816"] pdudaemon-0.0.7/pdudaemon/drivers/driver.py000066400000000000000000000047371336440240300210030ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 os log = logging.getLogger("pdud.drivers") class PDUDriver(object): connection = None hostname = "" def __init__(self): super(PDUDriver, self).__init__() @classmethod def select(cls, drivername): candidates = cls.__subclasses__() # pylint: disable=no-member for subc in cls.__subclasses__(): # pylint: disable=no-member candidates = candidates + (subc.__subclasses__()) for subsubc in subc.__subclasses__(): candidates = candidates + (subsubc.__subclasses__()) willing = [c for c in candidates if c.accepts(drivername)] if len(willing) == 0: log.error("No driver accepted the configuration '%s'", drivername) os._exit(1) log.debug("%s accepted the request", willing[0]) return willing[0] def handle(self, request, port_number): log.debug("Driving PDU hostname: %s " "PORT: %s REQUEST: %s", self.hostname, port_number, request) if request == "on": self.port_on(port_number) elif request == "off": self.port_off(port_number) else: log.debug("Unknown request to handle - oops") raise NotImplementedError( "Driver doesn't know how to %s " % request ) self._cleanup() 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_interaction(self, command, port_number): pass def _bombout(self): pass def _cleanup(self): pass pdudaemon-0.0.7/pdudaemon/drivers/egpms.py000066400000000000000000000110421336440240300206060ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2017 Sjoerd Simons Schulz # # Based on PDUDriver: # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.driver import PDUDriver import socket import sys from array import array import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class EgPMS(PDUDriver): port_count = 4 def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.ip = settings["ip"] self.password = array('B', settings["password"].encode("utf-8") + 8 * b' ')[:8] self.challenge = None def authresponse(self, part): response = self.challenge[part] ^ self.password[part + 2] response *= self.password[part] response ^= self.password[part + 4] << 8 | self.password[part + 6] response ^= self.challenge[part + 2] r = array('B', [response & 0xff, (response >> 8) & 0xff]) return r def encode_state(self, status): state = status ^ self.challenge[2] state += self.challenge[3] state ^= self.password[0] state += self.password[1] return state & 0xff def decode_state(self, state): state = state - self.password[1] state ^= self.password[0] state -= self.challenge[3] state ^= self.challenge[2] return state & 0xff def dump_status(self, status): status.reverse() for b in status: s = self.decode_state(b) log.debug("on: %d (raw: %x)" % (s >> 4 == 1, s)) def connect(self): self.socket = socket.create_connection((self.ip, 5000)) self.socket.send(b'\x11') self.challenge = array('B', self.socket.recv(4)) self.socket.send(self.authresponse(0) + self.authresponse(1)) status = array('B', self.socket.recv(self.port_count)) log.debug("Connected") self.dump_status(status) def disconnect(self): # From egctl: # Empirically found way to close session w/o 4 second timeout on # the device side is to send some invalid sequence. This helps # to avoid a hiccup on subsequent run of the utility. # # Other protocol documents explain that after the current settings the # device waits for a schedule update for a while (if any) but will go # back to the start state if it doesn't make sense. self.socket.send(b'\x11') self.socket.close() def port_interaction(self, command, port_number): SWITCH_ON = 0x1 SWITCH_OFF = 0x2 DONT_SWITCH = 0x4 if port_number > self.port_count or port_number < 1: err = "Port should be in the range 1 - %d" % (self.port_count) log.error(err) raise RuntimeError(err) if command == "on": on = True elif command == "off": on = False else: log.error("Unknown command %s." % (command)) return self.connect() log.debug("Attempting control: %s port: %d hostname: %s." % (command, port_number, self.hostname)) update = array('B') for s in range(1, self.port_count + 1): if s != port_number: update.append(self.encode_state(DONT_SWITCH)) else: update.append( self.encode_state(SWITCH_ON if on else SWITCH_OFF)) # States get send in reverse port order bytewise update.reverse() self.socket.send(update) status = array('B', self.socket.recv(self.port_count)) self.disconnect() log.debug("Updated") self.dump_status(status) @classmethod def accepts(cls, drivername): if drivername == 'egpms': return True return False pdudaemon-0.0.7/pdudaemon/drivers/ip9258.py000066400000000000000000000040261336440240300204370ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2016 BayLibre, Inc. # Author Kevin Hilman # # TODO: # - use pysnmp instead of snmpset command-line tool # # Based on localcmdline.py # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 subprocess from pdudaemon.drivers.localbase import LocalBase import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class IP9258(LocalBase): def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings @classmethod def accepts(cls, drivername): if drivername == "ip9258": return True return False def _port_interaction(self, command, port_number): power_oid = '1.3.6.1.4.1.92.58.2.%d.0' % (port_number) cmd_base = '/usr/bin/snmpset -v 1 -c public %s %s integer' \ % (self.hostname, power_oid) cmd = None log.debug("Attempting control: %s port: %i" % (command, port_number)) if command == "on": cmd = cmd_base + ' %d > /dev/null' % (1) elif command == "off": cmd = cmd_base + ' %d > /dev/null' % (0) else: logging.debug("Unknown command!") if cmd: log.debug("running %s" % cmd) subprocess.call(cmd, shell=True) pdudaemon-0.0.7/pdudaemon/drivers/localbase.py000066400000000000000000000031371336440240300214260ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pexpect from pdudaemon.drivers.driver import PDUDriver import sys import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class LocalBase(PDUDriver): connection = None def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings super(LocalBase, self).__init__() @classmethod def accepts(cls, drivername): return False def port_interaction(self, command, port_number): log.debug("Running port_interaction from LocalBase") self._port_interaction(command, port_number) def _bombout(self): log.debug("Bombing out of driver: %s" % self.connection) del(self) def _cleanup(self): log.debug("Cleaning up driver: %s" % self.connection) pdudaemon-0.0.7/pdudaemon/drivers/localcmdline.py000066400000000000000000000035071336440240300221300ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 subprocess import call from pdudaemon.drivers.localbase import LocalBase import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class LocalCmdline(LocalBase): def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.cmd_on = settings.get("cmd_on", None) self.cmd_off = settings.get("cmd_off", None) @classmethod def accepts(cls, drivername): if drivername == "localcmdline": return True return False def _port_interaction(self, command, port_number): cmd = None log.debug("Attempting control: %s port: %i" % (command, port_number)) if command == "on" and self.cmd_on: cmd = self.cmd_on % port_number elif command == "off" and self.cmd_off: cmd = self.cmd_off % port_number else: log.debug("Unknown command!") if cmd: log.debug("running %s" % cmd) call(cmd, shell=True) pdudaemon-0.0.7/pdudaemon/drivers/sainsmart.py000066400000000000000000000040541336440240300215010ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright 2016 BayLibre, Inc. # Author Kevin Hilman # # Based on localcmdline.py # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.localbase import LocalBase import requests import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class Sainsmart(LocalBase): def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.ip = settings.get("ip", self.hostname) self.url_base = "http://%s/30000/" % self.ip log.debug(self.url_base) @classmethod def accepts(cls, drivername): if drivername == "sainsmart": return True return False def _port_interaction(self, command, port_number): val = -1 if command == "on": log.debug("Attempting control: %s port: %i" % (command, port_number)) val = (port_number - 1) * 2 + 1 elif command == "off": log.debug("Attempting control: %s port: %i" % (command, port_number)) val = (port_number - 1) * 2 else: log.debug("Unknown command!") if (val >= 0): url = self.url_base + "%02d" % val log.debug("HTTP GET at %s" % url) requests.get(url) pdudaemon-0.0.7/pdudaemon/drivers/strategies.py000066400000000000000000000042671336440240300216600ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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. from pdudaemon.drivers.acme import ACME # pylint: disable=W0611 from pdudaemon.drivers.apc7932 import APC7932 # pylint: disable=W0611 from pdudaemon.drivers.apc7952 import APC7952 # pylint: disable=W0611 from pdudaemon.drivers.apc9218 import APC9218 # pylint: disable=W0611 from pdudaemon.drivers.apc8959 import APC8959 # pylint: disable=W0611 from pdudaemon.drivers.apc9210 import APC9210 # pylint: disable=W0611 from pdudaemon.drivers.apc7921 import APC7921 # pylint: disable=W0611 from pdudaemon.drivers.ubiquity import Ubiquity3Port # pylint: disable=W0611 from pdudaemon.drivers.ubiquity import Ubiquity6Port # pylint: disable=W0611 from pdudaemon.drivers.ubiquity import Ubiquity8Port # pylint: disable=W0611 from pdudaemon.drivers.localcmdline import LocalCmdline from pdudaemon.drivers.ip9258 import IP9258 from pdudaemon.drivers.sainsmart import Sainsmart from pdudaemon.drivers.devantech import DevantechETH002 from pdudaemon.drivers.devantech import DevantechETH0621 from pdudaemon.drivers.devantech import DevantechETH484 from pdudaemon.drivers.devantech import DevantechETH008 from pdudaemon.drivers.devantech import DevantechETH8020 from pdudaemon.drivers.devantechusb import DevantechUSB2 from pdudaemon.drivers.devantechusb import DevantechUSB8 from pdudaemon.drivers.synaccess import SynNetBooter from pdudaemon.drivers.egpms import EgPMS from pdudaemon.drivers.ykush import YkushXS pdudaemon-0.0.7/pdudaemon/drivers/synaccess.py000066400000000000000000000102131336440240300214650ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2016 Broadcom # Author Christian Daudt # Based on apcbase+apc8959 by: # Author Matt Hart # # 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 sys import logging import pexpect from pdudaemon.drivers.driver import PDUDriver import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class SynBase(PDUDriver): connection = None def __init__(self, hostname, settings): self.hostname = hostname log.debug(settings) self.settings = settings self.username = "admin" self.password = "admin" telnetport = 23 if "telnetport" in settings: telnetport = settings["telnetport"] if "username" in settings: self.username = settings["username"] if "password" in settings: self.password = settings["password"] self.exec_string = "/usr/bin/telnet %s %d" % (hostname, telnetport) log.debug("Telnet command: [%s]" % self.exec_string) super(SynBase, self).__init__() @classmethod def accepts(cls, drivername): return False def port_interaction(self, command, port_number): log.debug("Running port_interaction from SynBase") self.get_connection() self._port_interaction(command, # pylint: disable=no-member port_number) def get_connection(self): log.debug("Connecting to Syn PDU with: %s", self.exec_string) # only uncomment this line for FULL debug when developing # self.connection = pexpect.spawn(self.exec_string, logfile=sys.stdout) self.connection = pexpect.spawn(self.exec_string) self._pdu_login(self.username, self.password) def _cleanup(self): self._pdu_logout() # pylint: disable=no-member def _bombout(self): log.debug("Bombing out of driver: %s", self.connection) self.connection.close(force=True) del self def _pdu_login(self, username, password): # Expected sequence: # >login # User ID: admin # Password:****** # > log.debug("attempting login with username %s, password %s", username, password) self.connection.expect(">") self.connection.send("login\r") self.connection.expect("User ID:") self.connection.send("%s\r" % username) self.connection.expect("Password:") self.connection.send("%s\r" % password) self.connection.expect(">") # # Only Synaccess product support at this point # Synaccess Networks netBooter Series B # Login identifies as: System Model: NP-08B class SynNetBooter(SynBase): pdu_commands = {"off": "pset %s 0", "on": "pset %s 1"} @classmethod def accepts(cls, drivername): if drivername == "synnetbooter": return True return False def _pdu_logout(self): log.debug("logging out") self.connection.send("\r") self.connection.send("exit") self.connection.send("\r") log.debug("done") def _pdu_get_to_prompt(self): self.connection.send("\r") self.connection.expect('>') def _port_interaction(self, command, port_number): log.debug("Attempting %s on port %i", command, port_number) self._pdu_get_to_prompt() self.connection.sendline(self.pdu_commands[command] % (port_number)) self._pdu_get_to_prompt() log.debug("done") pdudaemon-0.0.7/pdudaemon/drivers/ubiquity.py000066400000000000000000000077011336440240300213550ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2015 Alexander Couzens # # 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 paramiko import SSHClient from paramiko.ssh_exception import SSHException from paramiko import RejectPolicy, WarningPolicy from pdudaemon.drivers.driver import PDUDriver import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) class UbiquityBase(PDUDriver): client = None # overwrite power_count port_count = 0 def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.sshport = 22 self.username = "ubnt" self.password = "ubnt" # verify ssh hostkey? unknown hostkey will make this job fail self.verify_hostkey = True if "sshport" in settings: self.sshport = settings["sshport"] if "username" in settings: self.username = settings["username"] if "password" in settings: self.password = settings["password"] if "verify_hostkey" in settings: self.verify_hostkey = settings["verify_hostkey"] super(UbiquityBase, self).__init__() def connect(self): log.info("Connecting to Ubiquity mfi %s@%s:%d", self.username, self.hostname, self.sshport) self.client = SSHClient() self.client.load_system_host_keys() if self.verify_hostkey: self.client.set_missing_host_key_policy(RejectPolicy()) else: self.client.set_missing_host_key_policy(WarningPolicy()) self.client.connect(hostname=self.hostname, port=self.sshport, username=self.username, password=self.password) def port_interaction(self, command, port_number): log.debug("Running port_interaction from UbiquityBase") self.connect() if port_number > self.port_count: raise RuntimeError("We only have ports 1 - %d. %d > maxPorts (%d)" % self.port_count, port_number, self.port_count) if command == "on": command = "sh -c 'echo 1 > /proc/power/relay%d'" % port_number elif command == "off": command = "sh -c 'echo 0 > /proc/power/relay%d'" % port_number try: stdin, stdout, stderr = self.client.exec_command(command, bufsize=-1, timeout=3) stdin.close() except SSHException: pass def _cleanup(self): self.client.close() def _bombout(self): self.client.close() @classmethod def accepts(cls, drivername): return False class Ubiquity3Port(UbiquityBase): port_count = 3 @classmethod def accepts(cls, drivername): if drivername == "ubntmfi3port": return True return False class Ubiquity6Port(UbiquityBase): port_count = 6 @classmethod def accepts(cls, drivername): if drivername == "ubntmfi6port": return True return False class Ubiquity8Port(UbiquityBase): port_count = 8 @classmethod def accepts(cls, drivername): if drivername == "ubntmfi8port": return True return False pdudaemon-0.0.7/pdudaemon/drivers/ykush.py000066400000000000000000000041641336440240300206450ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2018 Sjoerd Simons # # Based on PDUDriver: # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 pdudaemon.drivers.driver import PDUDriver import hid import os log = logging.getLogger("pdud.drivers." + os.path.basename(__file__)) YKUSH_VID = 0x04d8 YKUSH_XS_PID = 0xf0cd class YkushXS(PDUDriver): connection = None port_count = 2 supported = ["YKUSHXS"] def __init__(self, hostname, settings): self.hostname = hostname self.settings = settings self.serial = settings.get("serial", u"") log.debug("serial: %s" % self.serial) super(YkushXS, self).__init__() def port_interaction(self, command, port_number): if port_number > self.port_count or port_number < 1: err = "Port should be in the range 1 - %d" % (self.port_count) log.error(err) raise RuntimeError(err) if command == "on": byte = 0x11 elif command == "off": byte = 0x01 else: log.error("Unknown command %s." % (command)) return d = hid.device() d.open(YKUSH_VID, YKUSH_XS_PID, serial_number=self.serial) d.write([byte, byte]) d.read(64) d.close() @classmethod def accepts(cls, drivername): return drivername in cls.supported pdudaemon-0.0.7/pdudaemon/httplistener.py000066400000000000000000000077061336440240300205560ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2018 Matt Hart # # 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. from http.server import BaseHTTPRequestHandler, HTTPServer import urllib.parse as urlparse import logging import time import threading logger = logging.getLogger('pdud.http') class PDUHTTPHandler(BaseHTTPRequestHandler): def _set_headers(self, code): self.send_response(code) self.send_header('Content-type', 'text/plain') self.end_headers() def log_message(self, format, *args): pass def do_GET(self): logger.info("Handling HTTP request from %s: %s", self.client_address, self.path) data = urlparse.parse_qs(urlparse.urlparse(self.path).query) path = urlparse.urlparse(self.path).path res = self.insert_request(data, path) if res: self._set_headers(200) self.wfile.write("OK - accepted request\n".encode('utf-8')) else: self._set_headers(500) self.wfile.write("Invalid request\n".encode('utf-8')) def insert_request(self, data, path): delay = 5 entry = path.lstrip('/').split('/') if len(entry) != 3: logger.info("Request path was invalid: %s", entry) return False if not (entry[0] == 'power' and entry[1] == 'control'): logger.info("Unknown request, path was %s", path) return False request = entry[2] custom_delay = False now = int(time.time()) if data.get('delay', None): delay = data.get('delay')[0] custom_delay = True hostname = data.get('hostname', [None])[0] port = data.get('port', [None])[0] if not hostname or not port or not request: logger.info("One of hostname,port,request was not set") return False db_queue = self.server.db_queue if not (request in ["reboot", "on", "off"]): logger.info("Unknown request: %s", request) return False if request == "reboot": logger.debug("reboot requested, submitting off/on") db_queue.put(("CREATE", hostname, port, "off", now)) db_queue.put(("CREATE", hostname, port, "on", now + int(delay))) return True else: if custom_delay: logger.debug("using delay as requested") db_queue.put(("CREATE", hostname, port, request, now + int(delay))) return True else: db_queue.put(("CREATE", hostname, port, request, now)) return True class HTTPListener(threading.Thread): def __init__(self, config, db_queue): super(HTTPListener, self).__init__(name="HTTP Listener") settings = config["daemon"] listen_host = settings["hostname"] listen_port = settings.get("port", 16421) logger.info("listening on %s:%s", listen_host, listen_port) self.server = HTTPServer((listen_host, listen_port), PDUHTTPHandler) self.server.settings = settings self.server.config = config self.server.db_queue = db_queue def run(self): logger.info("Starting the HTTP server") self.server.serve_forever() def shutdown(self): self.server.shutdown() self.server.server_close() pdudaemon-0.0.7/pdudaemon/pdurunner.py000066400000000000000000000054251336440240300200470ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 import traceback import threading import pexpect from pdudaemon.drivers.driver import PDUDriver import pdudaemon.drivers.strategies # pylint: disable=W0611 class PDURunner(threading.Thread): def __init__(self, config, hostname, task_queue, db_queue, retries): super(PDURunner, self).__init__(name=hostname) self.config = config self.hostname = hostname self.task_queue = task_queue self.db_queue = db_queue self.retries = retries self.logger = logging.getLogger("pdud.pdu.%s" % hostname) self.driver = self.driver_from_hostname(hostname) def driver_from_hostname(self, hostname): drivername = self.config['driver'] driver = PDUDriver.select(drivername)(hostname, self.config) return driver def do_job(self, port, request): retries = self.retries while retries > 0: try: return self.driver.handle(request, port) except (OSError, pexpect.exceptions.EOF, Exception): # pylint: disable=broad-except self.logger.warn(traceback.format_exc()) self.logger.warn("Failed to execute job: %s %s (attempts left %i)", port, request, retries - 1) if self.driver: self.driver._bombout() # pylint: disable=W0212,E1101 time.sleep(5) retries -= 1 continue return False def run(self): self.logger.info("Starting a PDURunner for PDU: %s", self.hostname) while 1: job = self.task_queue.get() if job is None: self.logger.info("leaving") self.task_queue.task_done() return 0 job_id, port, request = job self.logger.info("Processing task (%s %s)", request, port) self.do_job(port, request) self.task_queue.task_done() self.db_queue.put(("DELETE", job_id)) return 0 pdudaemon-0.0.7/pdudaemon/tcplistener.py000066400000000000000000000074131336440240300203600ustar00rootroot00000000000000#!/usr/bin/python3 # Copyright 2013 Linaro Limited # Author Matt Hart # # 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 threading import logging import socket import time logger = logging.getLogger('pdud.tcp') class TCPListener(threading.Thread): def __init__(self, config, db_queue): super(TCPListener, self).__init__(name="TCP Listener") settings = config["daemon"] listen_host = settings["hostname"] listen_port = settings.get("port", 16421) logger.info("listening on %s:%s", listen_host, listen_port) self.server = TCPServer((listen_host, listen_port), TCPRequestHandler) self.server.settings = settings self.server.config = config self.server.db_queue = db_queue def run(self): logger.info("Starting the TCPServer") self.server.serve_forever() def shutdown(self): self.server.shutdown() self.server.server_close() class TCPRequestHandler(socketserver.BaseRequestHandler): def insert_request(self, data): array = data.split(" ") delay = 10 custom_delay = False now = int(time.time()) if (len(array) < 3) or (len(array) > 4): logger.info("Wrong data size") raise Exception("Unexpected data") if len(array) == 4: delay = int(array[3]) custom_delay = True hostname = array[0] port = int(array[1]) request = array[2] db_queue = self.server.db_queue if not (request in ["reboot", "on", "off"]): logger.info("Unknown request: %s", request) raise Exception("Unknown request: %s", request) if request == "reboot": logger.debug("reboot requested, submitting off/on") db_queue.put(("CREATE", hostname, port, "off", now)) db_queue.put(("CREATE", hostname, port, "on", now + int(delay))) else: if custom_delay: logger.debug("using delay as requested") db_queue.put(("CREATE", hostname, port, request, now + int(delay))) else: db_queue.put(("CREATE", hostname, port, request, now)) def handle(self): request_ip = self.client_address[0] try: data = self.request.recv(16384) data = data.decode('utf-8') data = data.strip() socket.setdefaulttimeout(2) try: request_host = socket.gethostbyaddr(request_ip)[0] except socket.herror: request_host = request_ip logger.info("Received a request from %s: '%s'", request_host, data) self.insert_request(data) self.request.sendall("ack\n".encode('utf-8')) except Exception as global_error: # pylint: disable=broad-except logger.debug(global_error.__class__) logger.debug(global_error) self.request.sendall(global_error) self.request.close() class TCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer): allow_reuse_address = True daemon_threads = True pdudaemon-0.0.7/requirements.txt000066400000000000000000000001041336440240300167500ustar00rootroot00000000000000pexpect requests systemd_python paramiko pyserial setuptools hidapi pdudaemon-0.0.7/setup.py000077500000000000000000000043461336440240300152150ustar00rootroot00000000000000#!/usr/bin/python3 # # Copyright 2018 Remi Duraffort # Matt Hart # # 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. from setuptools import setup # grab metadata without importing the module metadata = {} with open("pdudaemon/__about__.py", encoding="utf-8") as fp: exec(fp.read(), metadata) # Setup the package setup( name='pdudaemon', version=metadata['__version__'], description=metadata['__description__'], author=metadata['__author__'], author_email='matt@mattface.org', license=metadata['__license__'], url=metadata['__url__'], python_requires=">=3.4", classifiers=[ "Development Status :: 5 - Production/Stable", "Environment :: Console", "Intended Audience :: Developers", "License :: OSI Approved :: GNU General Public License v2 or later (GPLv2+)", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3.4", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3 :: Only", "Topic :: Communications", "Topic :: Software Development :: Testing", "Topic :: System :: Networking", ], packages=['pdudaemon', 'pdudaemon.drivers'], entry_points={ 'console_scripts': [ 'pdudaemon = pdudaemon:main' ] }, install_requires=[ "requests", "pexpect", "systemd_python", "paramiko", "pyserial", "hidapi" ], zip_safe=True ) pdudaemon-0.0.7/share/000077500000000000000000000000001336440240300145735ustar00rootroot00000000000000pdudaemon-0.0.7/share/80-ykushxs.rules000066400000000000000000000002151336440240300176100ustar00rootroot00000000000000ACTION=="remove", GOTO="ykushxs_end" SUBSYSTEM!="usb", GOTO="ykushxs_end" ENV{ID_MODEL}=="YKUSH_XS", OWNER="pdudaemon" LABEL="ykushxs_end" pdudaemon-0.0.7/share/Dockerfile.travis000066400000000000000000000014101336440240300200700ustar00rootroot00000000000000FROM debian:stretch RUN apt-get update && apt-get install -y pep8 \ python3-setuptools \ python3-pip \ libsystemd-dev \ curl \ psmisc \ pkg-config \ libffi-dev \ libhidapi-dev \ libudev-dev \ libusb-1.0-0-dev \ cython3 ADD requirements.txt / RUN pip3 install --user -r /requirements.txt RUN pip3 install --user pycodestyle pdudaemon-0.0.7/share/pdudaemon-test.sh000077500000000000000000000006001336440240300200570ustar00rootroot00000000000000#!/bin/bash pdudaemon --loglevel=DEBUG --conf=share/pdudaemon.conf --dbfile=/tmp/dbfile & sleep 5 curl -q "http://localhost:16421/power/control/reboot?hostname=test&port=1&delay=5" &> /dev/null sleep 15 LINES=`cat /tmp/pdu | wc -l` if [[ $LINES -eq 2 ]] then echo "localcmdline executed ok" cat /tmp/pdu exit 0 else echo "localcmdline test failed" cat /tmp/pdu exit 1 fi pdudaemon-0.0.7/share/pdudaemon.conf000066400000000000000000000020411336440240300174130ustar00rootroot00000000000000{ "daemon": { "hostname": "0.0.0.0", "port": 16421, "dbname": "pdudaemon", "logging_level": "DEBUG", "listener": "http" }, "pdus": { "test": { "driver": "localcmdline", "cmd_on": "echo '%d on' >> /tmp/pdu", "cmd_off": "echo '%d off' >> /tmp/pdu" }, "baylibre-acme.local": { "driver": "acme" }, "192.168.10.2": { "driver": "apc9210" }, "192.168.10.3": { "driver": "apc7952", "telnetport": 5023 }, "192.168.10.5": { "driver": "apc8959" }, "pdutest": { "driver": "apc7952", "telnetport": 23, "retries": 2 }, "192.168.154.173": { "driver": "ubntmfi3port", "username": "ubnt", "password": "ubnt", "sshport": 22, "verify_hostkey": true }, "127.0.0.1": { "driver": "localcmdline" } } } pdudaemon-0.0.7/share/pdudaemon.service000066400000000000000000000004711336440240300201330ustar00rootroot00000000000000[Unit] Description=Control and Queueing daemon for PDUs [Service] ExecStart=/usr/sbin/pdudaemon --journal --dbfile=/var/lib/pdudaemon/pdudaemon.db --conf=/etc/pdudaemon/pdudaemon.conf Type=simple DynamicUser=yes StateDirectory=pdudaemon ProtectHome=true Restart=on-abnormal [Install] WantedBy=multi-user.target pdudaemon-0.0.7/share/supervisord.conf000066400000000000000000000004301336440240300200240ustar00rootroot00000000000000[supervisord] nodaemon=true [program:pdudaemon] command=/usr/local/bin/pdudaemon --dbfile=/config/pdudaemon.db --conf=/config/pdudaemon.conf autostart=true autorestart=true stdout_logfile=/dev/stdout stdout_logfile_maxbytes=0 stderr_logfile=/dev/stderr stderr_logfile_maxbytes=0