Giter Club home page Giter Club logo

pdudaemon's Introduction

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 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

There is an official Docker container updated from tip:

$ docker pull pdudaemon/pdudaemon:latest
$ vi pdudaemon.conf

To create a config file, use share/pdudaemon.conf as a base, then mount your config file on top of the default:

$ docker run -v `pwd`/pdudaemon.conf:/config/pdudaemon.conf pdudaemon/pdudaemon:latest

Or you can build your own:

$ git clone https://github.com/pdudaemon/pdudaemon
$ cd pdudaemon
$ vi share/pdudaemon.conf
	- configure your PDUs
$ sudo docker build -t pdudaemon --build-arg HTTP_PROXY=$http_proxy -f Dockerfile.dockerhub .
$ docker run --rm -it -e http_proxy=$http_proxy -e https_proxy=$https_proxy -e NO_PROXY="$no_proxy" --net="host" pdudaemon:latest

Config file

An example configuration file can be found here. The section daemon is pretty self explanatory. The interesting part is the pdus section, where all managed PDUs are listed and configured. For example:

  "pdus": {
      "hostname_or_ip": {
          "driver": "driver_name",
          "additional_parameter": "42"
      },
      "test": {
          "driver": "localcmdline",
          "cmd_on": "echo '%s on' >> /tmp/pdu",
          "cmd_off": "echo '%s off' >> /tmp/pdu"
      },
      "energenie": {
          "driver": "EG-PMS",
          "device": "01:01:51:a4:c3"
      },
      "192.168.0.42": {
          "driver": "brennenstuhl_wspl01_tasmota"
      }
  }

It is important to mention, that hostname can be an arbitrary name for a locally connected device (like energenie in this example). For some (or most) network connected devices, it needs to be the actual hostname or IP address the PDU responds to (see query-string in next section). The correct value for driver is highly dependent on the used child class and the specific implementation. Check the imported Python module for that class and look for drivername to be sure. Some drivers require additional parameters (like a device ID). Which parameters are required can also be extracted from the associated python module and child class definition. It is also worth checking out the share folder for some driver specific example configuration files and helpful scripts that can help prevent major headaches!

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 HTTP request URL has the following syntax:

    http://<pdudaemon-hostname>:<pdudaemon-port>/power/control/<command>?<query-string>

    Where:

    • pdudaemon-hostname is the hostname or IP address where pdudaemon is running (e.g.: localhost)
    • pdudaemon-port is the port used by pdudaemon (e.g.: 16421)
    • command is an action for the PDU to execute:
      • on: power on
      • off: power off
      • reboot: reboot
    • query-string can have 3 parameters (same as pduclient, see below)
      • hostname: the PDU hostname or IP address used in the configuration file (e.g.: "192.168.10.2")
      • port: the PDU port number
      • delay: delay between power off and on during reboot (optional, by default 5 seconds)

    Some example requests would be:

    $ curl "http://localhost:16421/power/control/on?hostname=192.168.10.2&port=1"
    $ curl "http://localhost:16421/power/control/off?hostname=192.168.10.2&port=1"
    $ curl "http://localhost:16421/power/control/reboot?hostname=192.168.10.2&port=1&delay=10"
    

    Return Codes

    • HTTP 200 - Request Accepted
    • HTTP 503 - Invalid Request, Request not accepted
  • TCP (legacy pduclient) The bundled client is used when PDUDaemon is configured to listen to 'tcp' requests. TCP support is considered legacy but will remain functional.

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)
  • non-daemon (also called drive) If you would just like to use pdudaemon as an executable to drive a PDU without needing to run a daemon, you can use the --drive option. Configure the PDU in the config file as usual, then launch pdudaemon with the following options
$ pdudaemon --conf=share/pdudaemon.conf --drive --hostname pdu01 --port 1 --request reboot

If requesting reboot, the delay between turning the port off and on can be modified with --delay and is by default 5 seconds.

Adding drivers

Drivers are implemented children of the "PDUDriver" class and many example implementations can be found inside the drivers directory. Any new driver classes should be added to strategies.py.

External implementation of PDUDriver can also be registered using the python entry_points mechanism. For example add the following to your setup.cfg:

[options.entry_points]
pdudaemon.driver =
    mypdu = mypdumod:MyPDUClass

Why can't PDUDaemon do $REQUIREMENT?

Patches welcome, as long as it keeps the system simple and lightweight.

Contact

#pdudaemon on Freenode

pdudaemon's People

Contributors

actionhoscht avatar adehad avatar andrewshadura avatar bfletcher avatar cdleonard avatar dependabot[bot] avatar dougtargett avatar emantor avatar fooishbar avatar hallerjulian avatar iwamatsu avatar jluebbe avatar khilman avatar laura-nao avatar liuwenyi avatar malcolm-brooks avatar markferry avatar mattface avatar mwelchuk avatar obbardc avatar osozer-philips avatar psrcode avatar qschulz avatar sangorrin avatar silihubi avatar sjoerdsimons avatar sre avatar stefanrb avatar swvanbuuren avatar wuyuer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pdudaemon's Issues

pdudaemon always requires a port when devices with BMC don't need one for connection.

Some devices may not need to have a port when creating an alias for them in pdedaemon config, i.e. devices with BMC that can be directly controlled through the ipmitool.

So, I end up with something like this (notice I had to include %s in the commands themselves because the following block, but also specify a port that I don't need):

{
    "daemon": {
            . . . 
      },
      "pdus": {
          "device-name": {
              "driver": "localcmdline",
              "cmd_on": "ipmitool -I lanplus -H <IP address> -p %s -U lava -P <password> power on",
              "cmd_off": "ipmitool -I lanplus -H <IP address> -p %s -U lava -P <password> power off"
          }
      },
      "aliases": {
          "device-alias": {
              "hostname": "device-name",
              "port": "xxx"
        }
    }
}

pypi release?

Hi there!

Just was wondering if there are plans to release this package onto the pypi?

Blocking mode?

All current pdudaemon operations are asynchronous and delayed (#72). It's possible that for client sequence of off(); sleep(1); on(); pdudaemon delays will cause "off" to take effect after the on() call. This can be worked around (for example with longer sleep times) but it's nasty.

I think it would be reasonable to support a "block=true" parameter that makes "on/off" requests to only return after the driver does. This could also propagate driver errors back to the client.

pdudaemon doesn't return error on failure

https://github.com/pdudaemon/pdudaemon/blob/main/pdudaemon/listener.py#L105 seems to always return true, even if a request fails. This can mean downstream consumers may think that a port has changed state, even though it hasn't.

requests.exceptions.ConnectionError: HTTPConnectionPool(host='1', port=80): Max retries exceeded with url: /switch/1/turn_off (Caused by NameResolutionError("<urllib3.connection.HTTPConnection object at 0x7fca9a3a2010>: Failed to resolve 'pdu' ([Errno -2] Name or service not known)"))
2024-04-04 13:47:28,238 - pdud.pdu.1       - WARNING Failed to execute job: 1 off (attempts left 0)

Should also report failures in pduclient

APC7921: Different telnet interface version?

Hi,

we have 2 APC7921 devices that we bought 1-2 years ago. The APC7921 driver that is currently available here does not work as the telnet interface has changed its behavior. We had to rewrite the driver so it works with our devices. I assume that we have a newer FW in use on our devices.

How do you handle such cases, should the APC7921 driver do some sort of automagical detection and behave differently? Or would it be better to add another "flavor" of the APC7921 (something APC7921_v2) to keep the complexity and the test effort low?

Migration to pysnmp-lextudio

The fork https://github.com/lextudio/pysnmp continues the development of pysnmp, but the API has changed.

To use the new version pdudaemon/drivers/snmp.py should be updated to the new API. As there is now an async API, pdudaemon should probably use that.

A PR would be highly appreciated.

Requires that ports be numbered

Older versions of pdudaemon (the version in Debian oldstable at least) accepted string values as ports. This was useful for my system where I have devices controlled via a command line program which don't meaningfully have numbered ports, they have scripts that get run to power them on and off, so I assigned meaningful names to them to make the system more understandable. This really feels like a regression in functionality.

Stop using postgres

We dont need a massive database backend to keep a list of queued jobs, find something lighter.

Where to add the list of PDUs for monitoring?

Hi, I want to monitor multiple PDUs in my network. Let's say I have 5 different PDUs from 3 different make with multiple protocol:

  1. 2 PDUs supports SNMP
  2. 2 PDUs support HTTP
  3. 1 PDU support TCP
    Now where should I do the configuration or add the lists of PDUs to get the data from the PDUs?

delay parameter default value

There is some confusion about the intended semantics of the default value for the delay parameter. While it makes a lot of sense to put a delay in between the reboot command by default (i.e. to allow capacitors to discharge), it is not very intuitive why a on/off command is delayed before execution by default as well:

db_queue.put(("CREATE", args.hostname, args.port, args.request, now + int(args.delay)))

Right now, the Labgrid driver implementation is broken because it only explicitly sets the delay parameter for the shutdown command:

https://github.com/labgrid-project/labgrid/blob/27126daa04a382d228b0013efcb34e299689be46/labgrid/driver/powerdriver.py#L298-L299

Hence, it would like to ask if the behaviour in PDUDaemon is intended as it is or if the default delay should rather be zero for on/off commands?

ARM Docker Image

It would be nice to have a package to run on an arm processor

Document the configuration file parameters

The configuration file is confusing for new users, so explain each option in documentation.

This will probably be easier if the config file was in the format that supports comments :/

Can we get tagged releases please?

The last tag was from "0.0.8" in 2019 but there have been many changes since that time. Could we get updated tags created so we can use this reliably?

Question about operate multiple PDU.

Looks like just one queue in daemon?

Means if just set one PDU daemon for two pdu: pdu1 & pdu2.
Then, the request sent to PDU1 and the request sent to PDU2 will wait in the same queue?
So, suggest to have separate PDU deamon for different PDU? Not just use one PDU deamon for different PDUs?

Cannot distinguish between multiple Cleware USB-Switch 4 devices

Recently support for the Cleware devices USB-Switch 4 and USB-Switch 8 was added to PDUDaemon, which I appreciate very much.

Unfortunately, it is not possible to distinguish between multiple Cleware USB-Switch 4 devices:

  • PDUDaemon uses the serial number provided by hid to identify Cleware devices
  • Each Cleware USB-Switch 4 appears to have the same serial number
  • This might also occur for Cleware USB-Switch 8 devices (although I am not able test this, since I only possess 1 device of this kind)

Thus, we need another way to identify Cleware USB-Switch 4 (and possibly 8) devices.

Hints to do this might be hidden in the vendor's software, since this does support multiple device of the same kind afaik.

Plugin support

It would be interesting to support PDUDriver implementations in separate packages. This would reduce dependencies in pdudaemon core and make it easier to experiment with drivers.

The pkgresources iter_entry_points API would be a straight-forward fit.

listener: crash/exit after several hours: double free or corruption

I've been running a busy pdudaemon (several requests per minute constantly), and lavapdu-listener eventually stops working. The symptom is errors from pduclient reporting:

Error sending command, wrong daemon hostname?

But checking in the lavapdu-listener.log, the last line shows:

*** Error in `/usr/bin/python': double free or corruption (out): 0x00007fbabc0013d0 ***

Using HEAD from this repo
Server platform: x86, Debian jessie

pdudaemon.conf: passing variables using localcmdline

Is there a way to pass arguments to the commands that use the localcmdline tool?

For example, I'd like to set a variable like "serial_number" in the config file, but then pass it as a variable. Right now, I have a section like this:

        "phidget4-E6-1": {
            "driver": "localcmdline",
            "serial_number": "262305",
            "cmd_on": "/usr/local/bin/phidget-ctrl --serial 262305 --port %d --command on",
            "cmd_off": "/usr/local/bin/phidget-ctrl --serial 262305 --port %d --command off"
        },

but is there a way to replace the option passed to --serial with the variable?

Merge the listener and runner

I used to think that having separate listening and dispatching processes was a good idea, but it meant a database had to sit in between them. If possible, make a single daemon that handles accepting tasks and also forks runners to execute them.

Scrap the deamon support and let systemd handle it

There's no need for all the logfile and daemonising support scripts.

Instead, have a single python file that can be started manually or with systemd, log to stdout or the journal and do not fork into the background. Let systemd handle the restarting and pid status

SNMP driver

Add an SNMP driver generic enough to cover most PDU cases, and as an alternative way to drive newer APC PDU

Job status API

I did not discover that the Tasmota HTTP response handling fixed in #95 did not work with all our devices until I had a look at the log; this is due to the fact that one cannot query if a job was succesfully executed or not.

The asynchronous nature of the API makes this a bit difficult, however, I think the easiest approach would be to respond with a job ID when a command is queued in; this ID can then later be used to query the job status via a separate API endpoint (queued/successful/failure with error message). What do you think about that?

I am mostly interested in using this together with Labgrid in blocking mode (for which #78 is a prerequisite); in that case, the command response can of course directly contain the job status.

esphome: Allow port names to be passed to the driver without having switch_ids in config

In #135 non-int port names were fixed. It was also identified that we can simplify the esphome driver by ignoring the switch_ids configuration mapping and just allowing port names to be passed.

In the configuration there is a a hack for switch_ids which maps passed port number -> port name e.g:

        "pdu-0001": {
            "driver": "esphome-http",
            "username": "admin",
            "password": "supersecretpasswordhere",
            "switch_ids": [
                "pdu-0001_relay_1",
                "pdu-0001_relay_2",
                "pdu-0001_relay_3",
                "pdu-0001_relay_4",
                "pdu-0001_relay_5",
                "pdu-0001_relay_6",
                "pdu-0001_usb"
            ]
        },

I would like to remove switch_ids from the config and just be able to pass the port ID to pdudaemon (and make sure pdudaemon errors out if the ID doesn't exist).

This would be a breaking change, but my local setup is probably the only significant user of this driver...

initial install: missing database

When trying to setup on a fresh machine directly from source, it fails to start because there is no initial database (startup log below.)
Setup should include helper script to setup initial empty database.

2018-02-05 12:37:49,189:INFO:root:Running PDUDaemon Listener /var/log/pdudaemon.log 0.0.0.0 16421. 2018-02-05 12:37:49,191:INFO:pdudaemon.socketserver:listening on 0.0.0.0:16421 Traceback (most recent call last): File "./pdudaemon-start", line 51, in <module> ListenerServer(settings).start() File "/home/khilman/work/platforms/lava/pdudaemon/pdudaemon/socketserver.py", line 48, in __init__ self.server.dbh = DBHandler(settings) File "/home/khilman/work/platforms/lava/pdudaemon/pdudaemon/dbhandler.py", line 34, in __init__ host=config["dbhost"]) File "/usr/local/lib/python2.7/dist-packages/psycopg2/__init__.py", line 130, in connect conn = _connect(dsn, connection_factory=connection_factory, **kwasync) psycopg2.OperationalError: FATAL: database "pdudaemon" does not exist

debian packages

Convince a friendly debian-ist, or become one myself, to package pdudaemon and get it uploaded.

PDU status query API

It is currently not possible to query the status of a PDU, i.e. if it is turned on or off. This would be useful for the Labgrid PDUDaemonDriver, which currently cannot implement its get method.

I have a synchronous API endpoint in mind that queries the status via a driver method (could be optional, maybe not all PDUs support status query), no caching by pdudaemon involved. What are your thoughts on this?

Smart home automation support

I've got a whole pile of PDUs that I'd like to connect to home automation systems, Google Nest, Alexa, etc

Maybe add a plugin to HomeAssistant, or add MQTT support.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.