You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
heartbeat/lib/plugins/stonith/apcmaster.c

827 lines
19 KiB

/*
*
* Copyright 2001 Mission Critical Linux, Inc.
*
* All Rights Reserved.
*/
/*
* Stonith module for APC Master Switch (AP9211)
*
* Copyright (c) 2001 Mission Critical Linux, Inc.
* author: mike ledoux <mwl@mclinux.com>
* author: Todd Wheeling <wheeling@mclinux.com>
* mangled by Sun Jiang Dong, <sunjd@cn.ibm.com>, IBM, 2005
*
* Based strongly on original code from baytech.c by Alan Robertson.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
/* Observations/Notes
*
* 1. The APC MasterSwitch, unlike the BayTech network power switch,
* accepts only one (telnet) connection/session at a time. When one
* session is active, any subsequent attempt to connect to the MasterSwitch
* will result in a connection refused/closed failure. In a cluster
* environment or other environment utilizing polling/monitoring of the
* MasterSwitch (from multiple nodes), this can clearly cause problems.
* Obviously the more nodes and the shorter the polling interval, the more
* frequently such errors/collisions may occur.
*
* 2. We observed that on busy networks where there may be high occurances
* of broadcasts, the MasterSwitch became unresponsive. In some
* configurations this necessitated placing the power switch onto a
* private subnet.
*/
/*
* Version string that is filled in by CVS
*/
static const char *version __attribute__ ((unused)) = "$Revision: 1.27 $";
#include <lha_internal.h>
#define DEVICE "APC MasterSwitch"
#define DOESNT_USE_STONITHKILLCOMM 1
#include "stonith_plugin_common.h"
#define PIL_PLUGIN apcmaster
#define PIL_PLUGIN_S "apcmaster"
#define PIL_PLUGINLICENSE LICENSE_LGPL
#define PIL_PLUGINLICENSEURL URL_LGPL
#include <pils/plugin.h>
#include "stonith_signal.h"
static StonithPlugin * apcmaster_new(const char *);
static void apcmaster_destroy(StonithPlugin *);
static const char ** apcmaster_get_confignames(StonithPlugin *);
static int apcmaster_set_config(StonithPlugin *, StonithNVpair *);
static const char * apcmaster_getinfo(StonithPlugin * s, int InfoType);
static int apcmaster_status(StonithPlugin * );
static int apcmaster_reset_req(StonithPlugin * s, int request, const char * host);
static char ** apcmaster_hostlist(StonithPlugin *);
static struct stonith_ops apcmasterOps ={
apcmaster_new, /* Create new STONITH object */
apcmaster_destroy, /* Destroy STONITH object */
apcmaster_getinfo, /* Return STONITH info string */
apcmaster_get_confignames, /* Get configuration parameters */
apcmaster_set_config, /* Set configuration */
apcmaster_status, /* Return STONITH device status */
apcmaster_reset_req, /* Request a reset */
apcmaster_hostlist, /* Return list of supported hosts */
};
PIL_PLUGIN_BOILERPLATE2("1.0", Debug)
static const PILPluginImports* PluginImports;
static PILPlugin* OurPlugin;
static PILInterface* OurInterface;
static StonithImports* OurImports;
static void* interfprivate;
#include "stonith_expect_helpers.h"
PIL_rc
PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports);
PIL_rc
PIL_PLUGIN_INIT(PILPlugin*us, const PILPluginImports* imports)
{
/* Force the compiler to do a little type checking */
(void)(PILPluginInitFun)PIL_PLUGIN_INIT;
PluginImports = imports;
OurPlugin = us;
/* Register ourself as a plugin */
imports->register_plugin(us, &OurPIExports);
/* Register our interface implementation */
return imports->register_interface(us, PIL_PLUGINTYPE_S
, PIL_PLUGIN_S
, &apcmasterOps
, NULL /*close */
, &OurInterface
, (void*)&OurImports
, &interfprivate);
}
/*
* I have an AP9211. This code has been tested with this switch.
*/
struct pluginDevice {
StonithPlugin sp;
const char * pluginid;
const char * idinfo;
pid_t pid;
int rdfd;
int wrfd;
char * device;
char * user;
char * passwd;
};
static const char * pluginid = "APCMS-Stonith";
static const char * NOTpluginID = "APCMS device has been destroyed";
/*
* Different expect strings that we get from the APC MasterSwitch
*/
#define APCMSSTR "American Power Conversion"
static struct Etoken EscapeChar[] = { {"Escape character is '^]'.", 0, 0}
, {NULL,0,0}};
static struct Etoken login[] = { {"User Name :", 0, 0}, {NULL,0,0}};
static struct Etoken password[] = { {"Password :", 0, 0} ,{NULL,0,0}};
static struct Etoken Prompt[] = { {"> ", 0, 0} ,{NULL,0,0}};
static struct Etoken LoginOK[] = { {APCMSSTR, 0, 0}
, {"User Name :", 1, 0} ,{NULL,0,0}};
static struct Etoken Separator[] = { {"-----", 0, 0} ,{NULL,0,0}};
/* We may get a notice about rebooting, or a request for confirmation */
static struct Etoken Processing[] = { {"Press <ENTER> to continue", 0, 0}
, {"Enter 'YES' to continue", 1, 0}
, {NULL,0,0}};
#include "stonith_config_xml.h"
static const char *apcmasterXML =
XML_PARAMETERS_BEGIN
XML_IPADDR_PARM
XML_LOGIN_PARM
XML_PASSWD_PARM
XML_PARAMETERS_END;
static int MS_connect_device(struct pluginDevice * ms);
static int MSLogin(struct pluginDevice * ms);
static int MSRobustLogin(struct pluginDevice * ms);
static int MSNametoOutlet(struct pluginDevice*, const char * name);
static int MSReset(struct pluginDevice*, int outletNum, const char * host);
static int MSLogout(struct pluginDevice * ms);
#if defined(ST_POWERON) && defined(ST_POWEROFF)
static int apcmaster_onoff(struct pluginDevice*, int outletnum, const char * unitid
, int request);
#endif
/* Login to the APC Master Switch */
static int
MSLogin(struct pluginDevice * ms)
{
EXPECT(ms->rdfd, EscapeChar, 10);
/*
* We should be looking at something like this:
* User Name :
*/
EXPECT(ms->rdfd, login, 10);
SEND(ms->wrfd, ms->user);
SEND(ms->wrfd, "\r");
/* Expect "Password :" */
EXPECT(ms->rdfd, password, 10);
SEND(ms->wrfd, ms->passwd);
SEND(ms->wrfd, "\r");
switch (StonithLookFor(ms->rdfd, LoginOK, 30)) {
case 0: /* Good! */
LOG(PIL_INFO, "Successful login to %s.", ms->idinfo);
break;
case 1: /* Uh-oh - bad password */
LOG(PIL_CRIT, "Invalid password for %s.", ms->idinfo);
return(S_ACCESS);
default:
return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS);
}
return(S_OK);
}
/* Attempt to login up to 20 times... */
static int
MSRobustLogin(struct pluginDevice * ms)
{
int rc = S_OOPS;
int j = 0;
for ( ; ; ) {
if (MS_connect_device(ms) == S_OK) {
rc = MSLogin(ms);
if( rc == S_OK ) {
break;
}
}
if ((++j) == 20) {
break;
} else {
sleep(1);
}
}
return rc;
}
/* Log out of the APC Master Switch */
static
int MSLogout(struct pluginDevice* ms)
{
int rc;
/* Make sure we're in the right menu... */
/*SEND(ms->wrfd, "\033\033\033\033\033\033\033"); */
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
/* Expect "> " */
rc = StonithLookFor(ms->rdfd, Prompt, 5);
/* "4" is logout */
SEND(ms->wrfd, "4\r");
close(ms->wrfd);
close(ms->rdfd);
ms->wrfd = ms->rdfd = -1;
return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS));
}
/* Reset (power-cycle) the given outlets */
static int
MSReset(struct pluginDevice* ms, int outletNum, const char *host)
{
char unum[32];
/* Make sure we're in the top level menu */
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
/* Expect ">" */
EXPECT(ms->rdfd, Prompt, 5);
/* Request menu 1 (Device Control) */
SEND(ms->wrfd, "1\r");
/* Select requested outlet */
EXPECT(ms->rdfd, Prompt, 5);
snprintf(unum, sizeof(unum), "%i\r", outletNum);
SEND(ms->wrfd, unum);
/* Select menu 1 (Control Outlet) */
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "1\r");
/* Select menu 3 (Immediate Reboot) */
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "3\r");
/* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */
retry:
switch (StonithLookFor(ms->rdfd, Processing, 5)) {
case 0: /* Got "Press <ENTER>" Do so */
SEND(ms->wrfd, "\r");
break;
case 1: /* Got that annoying command confirmation :-( */
SEND(ms->wrfd, "YES\r");
goto retry;
default:
return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
}
LOG(PIL_INFO, "Host being rebooted: %s", host);
/* Expect ">" */
if (StonithLookFor(ms->rdfd, Prompt, 10) < 0) {
return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
}
/* All Right! Power is back on. Life is Good! */
LOG(PIL_INFO, "Power restored to host: %s", host);
/* Return to top level menu */
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
return(S_OK);
}
#if defined(ST_POWERON) && defined(ST_POWEROFF)
static int
apcmaster_onoff(struct pluginDevice* ms, int outletNum, const char * unitid, int req)
{
char unum[32];
const char * onoff = (req == ST_POWERON ? "1\r" : "2\r");
int rc;
if ((rc = MSRobustLogin(ms) != S_OK)) {
LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
return(rc);
}
/* Make sure we're in the top level menu */
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
/* Expect ">" */
EXPECT(ms->rdfd, Prompt, 5);
/* Request menu 1 (Device Control) */
SEND(ms->wrfd, "1\r");
/* Select requested outlet */
snprintf(unum, sizeof(unum), "%d\r", outletNum);
SEND(ms->wrfd, unum);
/* Select menu 1 (Control Outlet) */
SEND(ms->wrfd, "1\r");
/* Send ON/OFF command for given outlet */
SEND(ms->wrfd, onoff);
/* Expect "Press <ENTER> " or "Enter 'YES'" (if confirmation turned on) */
retry:
switch (StonithLookFor(ms->rdfd, Processing, 5)) {
case 0: /* Got "Press <ENTER>" Do so */
SEND(ms->wrfd, "\r");
break;
case 1: /* Got that annoying command confirmation :-( */
SEND(ms->wrfd, "YES\r");
goto retry;
default:
return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS);
}
EXPECT(ms->rdfd, Prompt, 10);
/* All Right! Command done. Life is Good! */
LOG(PIL_INFO, "Power to MS outlet(s) %d turned %s", outletNum, onoff);
/* Pop back to main menu */
SEND(ms->wrfd, "\033\033\033\033\033\033\033\r");
return(S_OK);
}
#endif /* defined(ST_POWERON) && defined(ST_POWEROFF) */
/*
* Map the given host name into an (AC) Outlet number on the power strip
*/
static int
MSNametoOutlet(struct pluginDevice* ms, const char * name)
{
char NameMapping[128];
int sockno;
char sockname[32];
int times = 0;
int ret = -1;
/* Verify that we're in the top-level menu */
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
/* Expect ">" */
EXPECT(ms->rdfd, Prompt, 5);
/* Request menu 1 (Device Control) */
SEND(ms->wrfd, "1\r");
/* Expect: "-----" so we can skip over it... */
EXPECT(ms->rdfd, Separator, 5);
EXPECT(ms->rdfd, CRNL, 5);
EXPECT(ms->rdfd, CRNL, 5);
/* Looks Good! Parse the status output */
do {
times++;
NameMapping[0] = EOS;
SNARF(ms->rdfd, NameMapping, 5);
if (sscanf(NameMapping
, "%d- %23c",&sockno, sockname) == 2) {
char * last = sockname+23;
*last = EOS;
--last;
/* Strip off trailing blanks */
for(; last > sockname; --last) {
if (*last == ' ') {
*last = EOS;
}else{
break;
}
}
if (strcasecmp(name, sockname) == 0) {
ret = sockno;
}
}
} while (strlen(NameMapping) > 2 && times < 8);
/* Pop back out to the top level menu */
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
EXPECT(ms->rdfd, Prompt, 5);
SEND(ms->wrfd, "\033");
return(ret);
}
static int
apcmaster_status(StonithPlugin *s)
{
struct pluginDevice* ms;
int rc;
ERRIFNOTCONFIGED(s,S_OOPS);
ms = (struct pluginDevice*) s;
if ((rc = MSRobustLogin(ms) != S_OK)) {
LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
return(rc);
}
/* Expect ">" */
SEND(ms->wrfd, "\033\r");
EXPECT(ms->rdfd, Prompt, 5);
return(MSLogout(ms));
}
/*
* Return the list of hosts (outlet names) for the devices on this MS unit
*/
static char **
apcmaster_hostlist(StonithPlugin *s)
{
char NameMapping[128];
char* NameList[64];
unsigned int numnames = 0;
char ** ret = NULL;
struct pluginDevice* ms;
unsigned int i;
ERRIFNOTCONFIGED(s,NULL);
ms = (struct pluginDevice*) s;
if (MSRobustLogin(ms) != S_OK) {
LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
return(NULL);
}
/* Expect ">" */
NULLEXPECT(ms->rdfd, Prompt, 10);
/* Request menu 1 (Device Control) */
SEND(ms->wrfd, "1\r");
/* Expect: "-----" so we can skip over it... */
NULLEXPECT(ms->rdfd, Separator, 5);
NULLEXPECT(ms->rdfd, CRNL, 5);
NULLEXPECT(ms->rdfd, CRNL, 5);
/* Looks Good! Parse the status output */
do {
int sockno;
char sockname[64];
NameMapping[0] = EOS;
NULLSNARF(ms->rdfd, NameMapping, 5);
if (sscanf(NameMapping
, "%d- %23c",&sockno, sockname) == 2) {
char * last = sockname+23;
char * nm;
*last = EOS;
--last;
/* Strip off trailing blanks */
for(; last > sockname; --last) {
if (*last == ' ') {
*last = EOS;
}else{
break;
}
}
if (numnames >= DIMOF(NameList)-1) {
break;
}
if ((nm = (char*)STRDUP(sockname)) == NULL) {
goto out_of_memory;
}
g_strdown(nm);
NameList[numnames] = nm;
++numnames;
NameList[numnames] = NULL;
}
} while (strlen(NameMapping) > 2);
/* Pop back out to the top level menu */
SEND(ms->wrfd, "\033");
NULLEXPECT(ms->rdfd, Prompt, 10);
SEND(ms->wrfd, "\033");
NULLEXPECT(ms->rdfd, Prompt, 10);
SEND(ms->wrfd, "\033");
NULLEXPECT(ms->rdfd, Prompt, 10);
SEND(ms->wrfd, "\033");
NULLEXPECT(ms->rdfd, Prompt, 10);
if (numnames >= 1) {
ret = (char **)MALLOC((numnames+1)*sizeof(char*));
if (ret == NULL) {
goto out_of_memory;
}else{
memcpy(ret, NameList, (numnames+1)*sizeof(char*));
}
}
(void)MSLogout(ms);
return(ret);
out_of_memory:
LOG(PIL_CRIT, "out of memory");
for (i=0; i<numnames; i++) {
FREE(NameList[i]);
}
return(NULL);
}
/*
* Connect to the given MS device. We should add serial support here
* eventually...
*/
static int
MS_connect_device(struct pluginDevice * ms)
{
int fd = OurImports->OpenStreamSocket(ms->device
, TELNET_PORT, TELNET_SERVICE);
if (fd < 0) {
return(S_OOPS);
}
ms->rdfd = ms->wrfd = fd;
return(S_OK);
}
/*
* Reset the given host on this StonithPlugin device.
*/
static int
apcmaster_reset_req(StonithPlugin * s, int request, const char * host)
{
int rc = 0;
int lorc = 0;
struct pluginDevice* ms;
ERRIFNOTCONFIGED(s,S_OOPS);
ms = (struct pluginDevice*) s;
if ((rc = MSRobustLogin(ms)) != S_OK) {
LOG(PIL_CRIT, "Cannot log into %s.", ms->idinfo);
return(rc);
}else{
int noutlet;
noutlet = MSNametoOutlet(ms, host);
if (noutlet < 1) {
LOG(PIL_WARN, "%s doesn't control host [%s]"
, ms->device, host);
return(S_BADHOST);
}
switch(request) {
#if defined(ST_POWERON) && defined(ST_POWEROFF)
case ST_POWERON:
rc = apcmaster_onoff(ms, noutlet, host, request);
break;
case ST_POWEROFF:
rc = apcmaster_onoff(ms, noutlet, host, request);
break;
#endif
case ST_GENERIC_RESET:
rc = MSReset(ms, noutlet, host);
break;
default:
rc = S_INVAL;
break;
}
}
lorc = MSLogout(ms);
return(rc != S_OK ? rc : lorc);
}
/*
* Get the configuration parameters names
*/
static const char **
apcmaster_get_confignames(StonithPlugin * s)
{
static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_PASSWD, NULL};
return ret;
}
/*
* Set the configuration parameters
*/
static int
apcmaster_set_config(StonithPlugin * s, StonithNVpair * list)
{
struct pluginDevice* sd = (struct pluginDevice *)s;
int rc;
StonithNamesToGet namestocopy [] =
{ {ST_IPADDR, NULL}
, {ST_LOGIN, NULL}
, {ST_PASSWD, NULL}
, {NULL, NULL}
};
ERRIFWRONGDEV(s, S_OOPS);
if (sd->sp.isconfigured) {
return S_OOPS;
}
if ((rc=OurImports->CopyAllValues(namestocopy, list)) != S_OK) {
return rc;
}
sd->device = namestocopy[0].s_value;
sd->user = namestocopy[1].s_value;
sd->passwd = namestocopy[2].s_value;
return(S_OK);
}
static const char *
apcmaster_getinfo(StonithPlugin * s, int reqtype)
{
struct pluginDevice* ms;
const char * ret;
ERRIFWRONGDEV(s,NULL);
/*
* We look in the ST_TEXTDOMAIN catalog for our messages
*/
ms = (struct pluginDevice *)s;
switch (reqtype) {
case ST_DEVICEID:
ret = ms->idinfo;
break;
case ST_DEVICENAME: /* Which particular device? */
ret = ms->device;
break;
case ST_DEVICEDESCR:
ret = "APC MasterSwitch (via telnet)\n"
"NOTE: The APC MasterSwitch accepts only one (telnet)\n"
"connection/session a time. When one session is active,\n"
"subsequent attempts to connect to the MasterSwitch"
" will fail.";
break;
case ST_DEVICEURL:
ret = "http://www.apc.com/";
break;
case ST_CONF_XML: /* XML metadata */
ret = apcmasterXML;
break;
default:
ret = NULL;
break;
}
return ret;
}
/*
* APC MasterSwitch StonithPlugin destructor...
*/
static void
apcmaster_destroy(StonithPlugin *s)
{
struct pluginDevice* ms;
VOIDERRIFWRONGDEV(s);
ms = (struct pluginDevice *)s;
ms->pluginid = NOTpluginID;
if (ms->rdfd >= 0) {
close(ms->rdfd);
ms->rdfd = -1;
}
if (ms->wrfd >= 0) {
close(ms->wrfd);
ms->wrfd = -1;
}
if (ms->device != NULL) {
FREE(ms->device);
ms->device = NULL;
}
if (ms->user != NULL) {
FREE(ms->user);
ms->user = NULL;
}
if (ms->passwd != NULL) {
FREE(ms->passwd);
ms->passwd = NULL;
}
FREE(ms);
}
/* Create a new APC Master Switch StonithPlugin device. */
static StonithPlugin *
apcmaster_new(const char *subplugin)
{
struct pluginDevice* ms = ST_MALLOCT(struct pluginDevice);
if (ms == NULL) {
LOG(PIL_CRIT, "out of memory");
return(NULL);
}
memset(ms, 0, sizeof(*ms));
ms->pluginid = pluginid;
ms->pid = -1;
ms->rdfd = -1;
ms->wrfd = -1;
ms->user = NULL;
ms->device = NULL;
ms->passwd = NULL;
ms->idinfo = DEVICE;
ms->sp.s_ops = &apcmasterOps;
return(&(ms->sp));
}