1#!/usr/local/bin/python3.8 2# vim: set filetype=python 3####################################################################### 4# 5# ibmrsa-telnet - External stonith plugin for HAv2 (http://linux-ha.org/wiki) 6# Connects to IBM RSA Board via telnet and switches power 7# of server appropriately. 8# 9# Author: Andreas Mock (andreas.mock@web.de) 10# 11# History: 12# 2007-10-19 Fixed bad commandline handling in case of stonithing 13# 2007-10-11 First release. 14# 15# Comment: Please send bug fixes and enhancements. 16# I hope the functionality of communicating via telnet is encapsulated 17# enough so that someone can use it for similar purposes. 18# 19# Description: IBM offers Remote Supervisor Adapters II for several 20# servers. These RSA boards can be accessed in different ways. 21# One of that is via telnet. Once logged in you can use 'help' to 22# show all available commands. With 'power' you can reset, power on and 23# off the controlled server. This command is used in combination 24# with python's standard library 'telnetlib' to do it automatically. 25# 26# cib-snippet: Please see README.ibmrsa-telnet for examples. 27# 28# Copyright (c) 2007 Andreas Mock (andreas.mock@web.de) 29# All Rights Reserved. 30# 31# This program is free software; you can redistribute it and/or modify 32# it under the terms of version 2 or later of the GNU General Public 33# License as published by the Free Software Foundation. 34# 35# This program is distributed in the hope that it would be useful, but 36# WITHOUT ANY WARRANTY; without even the implied warranty of 37# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 38# 39# Further, this software is distributed without any warranty that it is 40# free of the rightful claim of any third person regarding infringement 41# or the like. Any license provided herein, whether implied or 42# otherwise, applies only to this software file. Patent licenses, if 43# any, provided herein do not apply to combinations of this program with 44# other software, or any other product whatsoever. 45# 46# You should have received a copy of the GNU General Public License 47# along with this program; if not, write the Free Software Foundation, 48# Inc., 59 Temple Place - Suite 330, Boston MA 02111-1307, USA. 49# 50####################################################################### 51import sys 52import os 53import time 54import telnetlib 55import subprocess 56 57class TimeoutException(Exception): 58 def __init__(self, value=None): 59 Exception.__init__(self) 60 self.value = value 61 62 def __str__(self): 63 return repr(self.value) 64 65class RSABoard(telnetlib.Telnet): 66 def __init__(self, *args, **kwargs): 67 telnetlib.Telnet.__init__(self, *args, **kwargs) 68 self._timeout = 10 69 self._loggedin = 0 70 self._history = [] 71 self._appl = os.path.basename(sys.argv[0]) 72 73 def _get_timestamp(self): 74 ct = time.time() 75 msecs = (ct - long(ct)) * 1000 76 return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", 77 time.localtime(ct)), msecs) 78 79 def write(self, buffer, nolog = False): 80 self._history.append(self._get_timestamp() + ': WRITE: ' + 81 (nolog and '******' or repr(buffer))) 82 telnetlib.Telnet.write(self, buffer) 83 84 def expect(self, what, timeout=20): 85 line = telnetlib.Telnet.expect(self, what, timeout) 86 self._history.append(self._get_timestamp() + ': READ : ' + repr(line)) 87 if not line: 88 raise TimeoutException("Timeout while waiting for '%s'." % (what, )) 89 return line 90 91 def login(self, user, passwd): 92 time.sleep(1) 93 line = self.expect(['\nlogin : ', '\nusername: '], self._timeout) 94 self.write(user) 95 self.write('\r') 96 line = self.expect(['\nPassword: ', '\npassword: '], self._timeout) 97 self.write(passwd, nolog = True) 98 self.write('\r') 99 line = self.expect(['\nsystem>', '> '], self._timeout) 100 101 def reset(self): 102 self.write('power cycle\r') 103 line = self.expect(['\nok'], self._timeout) 104 line = self.expect(['\nsystem>', '> '], self._timeout) 105 106 def on(self): 107 self.write('power on\r') 108 line = self.expect(['\nok'], self._timeout) 109 line = self.expect(['\nsystem>', '> '], self._timeout) 110 111 def off(self): 112 self.write('power off\r') 113 line = self.expect(['\nok'], self._timeout) 114 line = self.expect(['\nsystem>', '> '], self._timeout) 115 116 def exit(self): 117 self.write('exit\r') 118 119 def get_history(self): 120 return "\n".join(self._history) 121 122 123class RSAStonithPlugin: 124 def __init__(self): 125 # define the external stonith plugin api 126 self._required_cmds = \ 127 'reset gethosts status getconfignames getinfo-devid ' \ 128 'getinfo-devname getinfo-devdescr getinfo-devurl ' \ 129 'getinfo-xml' 130 self._optional_cmds = 'on off' 131 self._required_cmds_list = self._required_cmds.split() 132 self._optional_cmds_list = self._optional_cmds.split() 133 134 # who am i 135 self._appl = os.path.basename(sys.argv[0]) 136 137 # telnet connection object 138 self._connection = None 139 140 # the list of configuration names 141 self._confignames = ['nodename', 'ip_address', 'username', 'password'] 142 143 # catch the parameters provided by environment 144 self._parameters = {} 145 for name in self._confignames: 146 try: 147 self._parameters[name] = os.environ.get(name, '').split()[0] 148 except IndexError: 149 self._parameters[name] = '' 150 151 def _get_timestamp(self): 152 ct = time.time() 153 msecs = (ct - long(ct)) * 1000 154 return "%s,%03d" % (time.strftime("%Y-%m-%d %H:%M:%S", 155 time.localtime(ct)), msecs) 156 157 def _echo_debug(self, *args): 158 self.echo_log('debug', *args) 159 160 def echo(self, *args): 161 what = ''.join([str(x) for x in args]) 162 sys.stdout.write(what) 163 sys.stdout.write('\n') 164 sys.stdout.flush() 165 self._echo_debug("STDOUT:", what) 166 167 def echo_log(self, level, *args): 168 subprocess.call(('ha_log.sh', level) + args) 169 170 def _get_connection(self): 171 if not self._connection: 172 c = RSABoard() 173 self._echo_debug("Connect to '%s'" % 174 (self._parameters['ip_address'],)) 175 c.open(self._parameters['ip_address']) 176 self._echo_debug("Connection established") 177 c.login(self._parameters['username'], 178 self._parameters['password']) 179 self._connection = c 180 181 def _end_connection(self): 182 if self._connection: 183 self._connection.exit() 184 self._connection.close() 185 186 def reset(self): 187 self._get_connection() 188 self._connection.reset() 189 self._end_connection() 190 self._echo_debug(self._connection.get_history()) 191 self.echo_log("info", "Reset of node '%s' done" % 192 (self._parameters['nodename'],)) 193 return(0) 194 195 def on(self): 196 self._get_connection() 197 self._connection.on() 198 self._end_connection() 199 self._echo_debug(self._connection.get_history()) 200 self.echo_log("info", "Switched node '%s' ON" % 201 (self._parameters['nodename'],)) 202 return(0) 203 204 def off(self): 205 self._get_connection() 206 self._connection.off() 207 self._end_connection() 208 self._echo_debug(self._connection.get_history()) 209 self.echo_log("info", "Switched node '%s' OFF" % 210 (self._parameters['nodename'],)) 211 return(0) 212 213 def gethosts(self): 214 self.echo(self._parameters['nodename']) 215 return(0) 216 217 def status(self): 218 self._get_connection() 219 self._end_connection() 220 self._echo_debug(self._connection.get_history()) 221 return(0) 222 223 def getconfignames(self): 224 for name in ['nodename', 'ip_address', 'username', 'password']: 225 self.echo(name) 226 return(0) 227 228 def getinfo_devid(self): 229 self.echo("External Stonith Plugin for IBM RSA Boards") 230 return(0) 231 232 def getinfo_devname(self): 233 self.echo("External Stonith Plugin for IBM RSA Boards connecting " 234 "via Telnet") 235 return(0) 236 237 def getinfo_devdescr(self): 238 self.echo("External stonith plugin for HAv2 which connects to " 239 "a RSA board on IBM servers via telnet. Commands to " 240 "turn on/off power and to reset server are sent " 241 "appropriately. " 242 "(c) 2007 by Andreas Mock (andreas.mock@web.de)") 243 return(0) 244 245 def getinfo_devurl(self): 246 self.echo("http://www.ibm.com/Search/?q=remote+supervisor+adapter") 247 248 def getinfo_xml(self): 249 info = """<parameters> 250 <parameter name="nodename" unique="1" required="1"> 251 <content type="string" /> 252 <shortdesc lang="en">nodename to shoot</shortdesc> 253 <longdesc lang="en"> 254 Name of the node which has to be stonithed in case. 255 </longdesc> 256 </parameter> 257 <parameter name="ip_address" unique="1" required="1"> 258 <content type="string" /> 259 <shortdesc lang="en">hostname or ip address of RSA</shortdesc> 260 <longdesc lang="en"> 261 Hostname or ip address of RSA board used to reset node. 262 </longdesc> 263 </parameter> 264 <parameter name="username" unique="1" required="1"> 265 <content type="string" /> 266 <shortdesc lang="en">username to login on RSA board</shortdesc> 267 <longdesc lang="en"> 268 Username to login on RSA board. 269 </longdesc> 270 </parameter> 271 <parameter name="password" unique="1" required="1"> 272 <content type="string" /> 273 <shortdesc lang="en">password to login on RSA board</shortdesc> 274 <longdesc lang="en"> 275 Password to login on RSA board. 276 </longdesc> 277 </parameter> 278 </parameters> 279 """ 280 self.echo(info) 281 return(0) 282 283 def not_implemented(self, cmd): 284 self.echo_log("err", "Command '%s' not implemented." % (cmd,)) 285 return(1) 286 287 def usage(self): 288 usage = "Call me with one of the allowed commands: %s, %s" % ( 289 ', '.join(self._required_cmds_list), 290 ', '.join(self._optional_cmds_list)) 291 return usage 292 293 def process(self, argv): 294 self._echo_debug("========== Start =============") 295 if len(argv) < 1: 296 self.echo_log("err", 'At least one commandline argument required.') 297 return(1) 298 cmd = argv[0] 299 self._echo_debug("cmd:", cmd) 300 if cmd not in self._required_cmds_list and \ 301 cmd not in self._optional_cmds_list: 302 self.echo_log("err", "Command '%s' not supported." % (cmd,)) 303 return(1) 304 try: 305 cmd = cmd.lower().replace('-', '_') 306 func = getattr(self, cmd, self.not_implemented) 307 rc = func() 308 return(rc) 309 except Exception, args: 310 self.echo_log("err", 'Exception raised:', str(args)) 311 if self._connection: 312 self.echo_log("err", self._connection.get_history()) 313 self._connection.close() 314 return(1) 315 316 317if __name__ == '__main__': 318 stonith = RSAStonithPlugin() 319 rc = stonith.process(sys.argv[1:]) 320 sys.exit(rc) 321