Skip to content

Instantly share code, notes, and snippets.

@alfonsrv
Last active April 2, 2023 10:47
Show Gist options
  • Save alfonsrv/063447338a51579ab8df46980bebbab6 to your computer and use it in GitHub Desktop.
Save alfonsrv/063447338a51579ab8df46980bebbab6 to your computer and use it in GitHub Desktop.
Start and stop Supermicro servers for replica job via IPMI – since wake on lan does not work reliably for one of my servers. Uses the web interface directly so no ipmi binary is required – since supplying the username for ipmiutil didn't want to work reliably for me either
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# Rau Systemberatung GmbH (www.rausys.de)
# Licensed under the GNU General Public License, Version 3
import argparse
import re
from time import sleep
from base64 import b64encode
from urllib3.exceptions import InsecureRequestWarning
from typing import Literal
import requests
from requests import Session
# Suppress only the single warning from urllib3 needed.
requests.packages.urllib3.disable_warnings(category=InsecureRequestWarning)
IPMI_HOST = '10.100.10.12'
IPMI_USER = 'admin'
IPMI_PASSWORD = 'hunter42'
IPMI_POST_HEADERS = {
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8',
'Accept-Encoding': 'gzip, deflate, br',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
}
def login(*, user: str, password: str):
payload = {
'name': b64encode(user.encode()),
'pwd': b64encode(password.encode()),
'check': '00'
}
s.post(
f'https://{IPMI_HOST}/cgi/login.cgi',
data=payload,
headers=IPMI_POST_HEADERS
)
def csrf_token() -> str:
reply = s.get(
f'https://{IPMI_HOST}/cgi/url_redirect.cgi',
params={'url_name': 'topmenu'},
)
token = re.search(r'\((?:\"CSRF_TOKEN\")\, \"(?P<token>.*)\"\);', reply.text)
return token.groupdict()['token']
def is_online() -> bool:
payload = {
'op': 'GET_POWER_INFO.XML',
'r': '(0,0)'
}
reply = s.post(
f'https://{IPMI_HOST}/cgi/ipmi.cgi',
data=payload,
headers=IPMI_POST_HEADERS
)
status = re.search(r'POWER STATUS=\"(?P<status>.*)\"', reply.text)
return status.groupdict()['status'] == 'ON'
def set_power(*, status: Literal['(1,1)', '(1,3)', '(1,5)']) -> None:
""" (1,1) power up; (1,5) power down; (1,3) reset """
payload = {
'op': 'SET_POWER_INFO.XML',
'r': status
}
reply = s.post(
f'https://{IPMI_HOST}/cgi/ipmi.cgi',
data=payload,
headers=IPMI_POST_HEADERS
)
return reply
if __name__ == '__main__':
parser = argparse.ArgumentParser()
opts = parser.add_mutually_exclusive_group(required=True)
opts.add_argument('--start', help='Turn on Supermicro server via IPMI', action='store_true')
opts.add_argument('--stop', help='Turn off Supermicro server via IPMI', action='store_true')
opts.add_argument('--status', help='Get current power status of Supermicro server', action='store_true')
parser.add_argument('--no-wait', help='Do not wait for server to start before script exists', action='store_true')
# TODO: Maybe add specifying username, password + host via argparse too
args = parser.parse_args()
if not (args.start or args.stop or args.status):
print('No action specified; exiting...')
exit(1);
s = Session()
s.verify = False
s.get(f'https://{IPMI_HOST}/')
login(user=IPMI_USER, password=IPMI_PASSWORD)
csrf_token = csrf_token()
IPMI_POST_HEADERS.setdefault('CSRF_TOKEN', csrf_token)
if args.start:
if is_online():
print(f'Server already online! Cannot start server ({IPMI_HOST})')
exit()
print(f'Starting Supermicro server via IPMI... ({IPMI_HOST})')
set_power(status='(1,1)')
if not args.no_wait: sleep(180)
elif args.stop:
if not is_online():
print(f'Server already offline! Cannot shutdown server ({IPMI_HOST})')
exit()
print(f'Shutting down Supermicro server via IPMI... ({IPMI_HOST})')
set_power(status='(1,5)')
elif args.status:
status = 'Online' if is_online() else 'Offline'
print(f'Current status: {status} ({IPMI_HOST})')
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment