Last active
April 2, 2023 10:43
-
-
Save stephanGarland/93c02385e344d8b338aab67a113dd1e2 to your computer and use it in GitHub Desktop.
Bespoke WOL as one of my Supermicros has issues reliably coming up, often requiring a few boot attempts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python3 | |
import datetime | |
import os | |
import socket | |
import subprocess | |
import sys | |
import time | |
MAC_ADDR = "0025904F3A00" | |
BCAST_IP_ADDR = "255.255.255.255" | |
BACKUP_IP_ADDR = "192.168.1.208" | |
IPMI_IP_ADDR = "192.168.1.252" | |
IPMI_USERNAME = os.environ.get("IPMI_USERNAME") | |
IPMI_PASSWORD = os.environ.get("IPMI_PASSWORD") | |
SSH_PORT = 22 | |
UDP_PORT = 9 | |
PACKET = "FF" * 6 + MAC_ADDR * 16 | |
sock_udp = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) | |
sock_tcp = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
# Wake up | |
#sock_udp.sendto(bytes(PACKET, "utf-8"), (BCAST_IP_ADDR, UDP_PORT)) | |
def fake_logger(level: str, msg: str) -> str: | |
"""A terrible implementation of logging. | |
I want to write to pyznap's log along with pyznap, | |
and don't want to try to deal with multiple file handles. By redirecting | |
the script's output to the logfile in shell, print() writes to it. | |
Also, this was intended to be a very quick script, but here we are. | |
Args: | |
level: A string, ideally following POSIX standards e.g. INFO, WARN, etc. | |
msg: A string of the message you want to log. | |
Returns: | |
A formatted string to log. | |
""" | |
return f"{datetime.datetime.now().strftime('%b %d %H:%M:%S')} {level} {msg}" | |
def ipmi_cmd(cmd: str): | |
"""Issues an IPMI command. | |
Uses ipmipower and subprocess to issue power commands to the host. | |
Args: | |
cmd: The option(complete with hyphens) you want to issue. See https://linux.die.net/man/8/ipmipower. | |
Returns: | |
Nothing. | |
""" | |
subprocess.run( | |
["ipmipower", "-h", IPMI_IP_ADDR, "-u", IPMI_USERNAME, "-p", IPMI_PASSWORD, cmd], | |
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL | |
) | |
def check_is_booted() -> bool: | |
"""Attempts to boot the hypervisor. | |
First issues a PWRON command, and then checks for the VM to come up. | |
If it fails (in my case, likely because the BIOS doesn't always see the NVMe), | |
it will repeatedly power cycle the hypervisor until success. | |
Args: | |
None. | |
Returns: | |
A boolean indicating whether or not the VM is up. | |
""" | |
print(fake_logger("INFO", "Starting hypervisor...")) | |
ipmi_cmd("--on") | |
# Should be adequate time to cold boot and start the VM | |
print(fake_logger("INFO", "Sleeping 180 seconds...")) | |
time.sleep(180) | |
# Check if it woke up | |
try: | |
print(fake_logger("INFO", "Trying to connect to VM on port 22...")) | |
sock_tcp.connect((BACKUP_IP_ADDR, SSH_PORT)) | |
print(fake_logger("INFO", "Successfully connected - VM is up")) | |
except socket.error as e: | |
print(fake_logger("ERROR", "Unable to connect to port 22, failed to boot")) | |
return False | |
sock_tcp.close() | |
return True | |
def get_secrets() -> bool: | |
"""Ensures we can read secrets. | |
Ensures that SOPS correctly decrypted the secrets, | |
and placed them into the environment. Due to the usage of | |
os.environ.get(), they will be None if not present. | |
Args: | |
None. | |
Returns: | |
A boolean indicating success or failure. | |
""" | |
return all([x for x in [IPMI_USERNAME, IPMI_PASSWORD]]) | |
if __name__ == "__main__": | |
if not get_secrets(): | |
print(fake_logger("CRITICAL", "Couldn't read secrets, exiting")) | |
raise SystemExit | |
if sys.argv[1] == "start": | |
while not check_is_booted(): | |
print(fake_logger("ERROR", "Powercycling hypervisor...")) | |
ipmi_cmd("--reset") | |
elif sys.argv[1] == "stop": | |
print(fake_logger("INFO", "Shutting down hypervisor...")) | |
ipmi_cmd("--soft") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A small logger configuration fyi that streams both to console as well as to file