Basic Ipmi Tools

2013-07-16

So I'd been working on getting some IPMI tool intergration with a basic web interface and the easiest way to achieve that was to use a shell script that the PHP script would call.

This allowed me to do a lot of the processing through bash and required minimal amounts of PHP.

The script runs using a preset list of server config files in the "servers/" directory (relative to the script) each of which have the following:

~~~~
hostname=servername
ip=1.1.1.1
username=root
password=password
lantype=lan
ssh=no
~~~~

Most of that should be pretty self explanitory, the hostname allows you to specify a "nice name" for the server (used by the web front end), the IP is that the script will use to connect, the username and password allow you to issue commands without having to type out the login every time.

The lantype is either "lan" or "lanplus" depending on what the motherboard supports (read the ipmi documentation for more info).

The final entry is wether to connect via ssh or not (using the hard coded remote ssh host -- this may change to a config file setting at some point, if the need arises).

When you have some server.cfg files in there, you can run "./ipmitools.sh --server SERVER COMMANDS" where SERVER is the file name (minus the .cfg part)  and COMMANDS are the extra switches.

I haven't yet got to the point of adding a help file at the moment, but the current commands (if you can't deduce them from the script below) are:

--info full / power
--power on / off / cycle

Power info reports back a 1 for on, a 0 for off and a 2 for unreachable (the reason for these numbers rather than just outputting the IMPI error is due to simplicity in my Ajax checks).

The full shell script is here -- if you plan on using SSH, you need to edit the ssh line in __config__:

~~~~
#!/bin/bash

## Error reporting ##

function __error__ () {

	case ${1} in
		"config") echo "Malformed config file" ${2} "on line" ${3}; exit 1 ;;
	esac

}

#####################

## Read config file ##

function __config__ () {

	c="servers/${1}.cfg"
	i=1
	while read line; do
		[[ ${line} =~ ^(.*)[=](.*)$ ]] && var=${BASH_REMATCH[1]} && val=${BASH_REMATCH[2]} || __error__ "config" ${c} ${i}
		case ${var} in
			"hostname") hostname=${val} ;;
			"ip") ip=${val} ;;
			"username") username=${val} ;;
			"password") password=${val} ;;
			"lantype") lantype=${val} ;;
			"ssh") [[ ${val} == "yes" || ${val} == "1" ]] && ssh="ssh REMOTE@SSH.SERVER" || ssh="" ;;
		esac
		i=[${i}+1]
	done < ${c}

}

######################

## Get switches ##

function __switches__ () {

	while [[ ${@} ]]; do
		case ${1} in
			"--server") server=${2}; shift 2 ;;
			"--info")
				case ${2} in
					"full") command="full" ;;
					"power") command="power" ;;
					*) echo "Unknown operator ${2}"; exit 1 ;;
				esac
				shift 2
				ipmicommand="info"
			;;
			"--power")
				case ${2} in
					"on") command="power"; extracommand="on" ;;
					"off") command="power"; extracommand="off" ;;
					"cycle") command="power"; extracommand="cycle" ;;
					*) echo "Unknown operator ${2}"; exit 1 ;;
				esac
				shift 2
				ipmicommand="command"
			;;
			*) echo "Unknown switch ${1}"; exit 1 ;;
		esac
	done

}

##################

## Pull information ##
# info type (internal command), lantype, IP, username, password

function __ipmiinfo__ () {

	case ${1} in
		"full")
			command=`${6} ipmitool -I ${2} -H ${3} -U ${4} -P "${5}" chassis status`
			echo -e "${command}"
			exit 0
		;;
		"power")
			command=`${6} ipmitool -I ${2} -H ${3} -U ${4} -P "${5}" chassis power status 2>/dev/null`
			if [[ ${command} == "Chassis Power is on" ]]; then 
				echo "1"
			elif [[ ${command} == "Chassis Power is off" ]]; then
				echo "0"
			else
				echo "2"
			fi
			exit 0
		;;
	esac


}

###########################

## Send Commands ##
# command type, lantype, IP, username, password, extra command

function __ipmicmd__ () {

	case ${1} in
		"power")
			command=`${7} ipmitool -I ${2} -H ${3} -U ${4} -P "${5}" power ${6}`
			exit 0
		;;
	esac

}

#########################

## Startup ##

function __bootstrap__ () {
	__switches__ ${@}
	__config__ ${server}
	case ${ipmicommand} in
		"info") __ipmiinfo__ ${command} ${lantype} ${ip} ${username} ${password} "${ssh}"; exit 0 ;;
		"command") __ipmicmd__ ${command} ${lantype} ${ip} ${username} ${password} ${extracommand} "${ssh}"; exit 0 ;;
	esac 
}

__bootstrap__ ${@}

#############
~~~~

I'm sure you can probably work out the PHP code to set this up, but the main bulk was the Ajax calls to run each command. I am in no way profficent with javascript but I managed to work out the following:

~~~~
					function loadServerPower(server){
					var xmlHttp;
					var prevState = document.getElementById(server).className;
					xmlHttp=new XMLHttpRequest();
					xmlHttp.onreadystatechange=function(){
							if(prevState == ""){
								document.getElementById(server).className = "serverItemX";
								document.getElementById("power-"+server).innerHTML="Connecting";
								document.getElementById("powercycle-"+server).disabled = true;
								document.getElementById("poweroff-"+server).disabled = true;
								document.getElementById("poweron-"+server).disabled = true;
							}
							if(xmlHttp.readyState==4){
								if(xmlHttp.responseText == "1" && prevState != "serverItem1"){
									document.getElementById(server).className = "serverItem1";
									document.getElementById("power-"+server).innerHTML="On";
									document.getElementById("powercycle-"+server).disabled = false;
									document.getElementById("poweroff-"+server).disabled = false;
								}else if(xmlHttp.responseText == "0" && prevState !="serverItem0"){
									document.getElementById(server).className = "serverItem0";
									document.getElementById("power-"+server).innerHTML="Off";
									document.getElementById("powercycle-"+server).disabled = false;
									document.getElementById("poweron-"+server).disabled = false;
								}else if(xmlHttp.responseText == "2" && prevState !="serverItem2"){
									document.getElementById(server).className = "serverItem2";
									document.getElementById("power-"+server).innerHTML="Unreachable";
								}
							}

						}
						url="?power="+server;
						xmlHttp.open("GET",url,true);
						xmlHttp.send(null);
					}
					function loadServerStatus(server){
						var xmlHttp;
						xmlHttp=new XMLHttpRequest();
						xmlHttp.onreadystatechange=function(){
							if(xmlHttp.readyState==4){
									document.getElementById("status-"+server).innerHTML=xmlHttp.responseText;
							}
						}
						url="?status="+server;
						xmlHttp.open("GET",url,true);
						xmlHttp.send(null);
					}
					function sendServerPower(server,power){
						if(power == 'powercycle'){
							var warning='Are you sure you want to power cycle '+server+'?';
						}else if(power == 'poweron'){
							var warning='Are you sure you want to power on '+server+'?';
						}else if(power == 'poweroff'){
							var warning='Are you sure you want to power off '+server+'?';
						}

						var response = confirm(warning);
						if(response){
							var xmlHttp;
							xmlHttp=new XMLHttpRequest();
							xmlHttp.onreadystatechange=function(){
								if(xmlHttp.readyState==4){
									document.getElementById(server).className = "serverItemX";
                        	                                        document.getElementById("power-"+server).innerHTML="Acting";
                	                                                document.getElementById("powercycle-"+server).disabled = true;
        	                                                        document.getElementById("poweroff-"+server).disabled = true;
	                                                                document.getElementById("poweron-"+server).disabled = true;
								}
							}
							url="?command="+power+"&server="+server;
							xmlHttp.open("GET",url,true);
							xmlHttp.send(null);
						}
					}
					function toggleServerStatus(id){
						var e = document.getElementById(id);
						if(e.style.display == 'block') {
							e.style.display = 'none';
						}else{
							e.style.display = 'block';
						}
					}
~~~~

I just ran a simple PHP loop on  the directory contents of "servers/" to work out how many rows there shold be and applied the above javascript to buttons and information panels. And used GET switches to work out wihich command type was being called.

As usual, I'm not responsible for what you do with this information / scripts and will definately not be held accountable for you turning off a live production server (I suggest you have a test server or, at the very least, read the IPMI documentation).