diff options
Diffstat (limited to '')
-rw-r--r-- | lib/plugins/stonith/wti_nps.c | 813 |
1 files changed, 813 insertions, 0 deletions
diff --git a/lib/plugins/stonith/wti_nps.c b/lib/plugins/stonith/wti_nps.c new file mode 100644 index 0000000..f0b81f7 --- /dev/null +++ b/lib/plugins/stonith/wti_nps.c @@ -0,0 +1,813 @@ +/* + * + * Copyright 2001 Mission Critical Linux, Inc. + * + * All Rights Reserved. + */ +/* + * Stonith module for WTI Network Power Switch Devices (NPS-xxx) + * Also supports the WTI Telnet Power Switch Devices (TPS-xxx) + * + * Copyright 2001 Mission Critical Linux, Inc. + * author: mike ledoux <mwl@mclinux.com> + * author: Todd Wheeling <wheeling@mclinux.com> + * Mangled by Zhaokai <zhaokai@cn.ibm.com>, IBM, 2005 + * Further hurt by Lon <lhh@redhat.com>, Red Hat, 2005 + * + * Supported WTI devices: + * NPS-115 + * NPS-230 + * IPS-15 + * IPS-800 + * IPS-800-CE + * NBB-1600 + * NBB-1600-CE + * TPS-2 + * + * 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 WTI Network Power Switch, unlike the BayTech network power switch, + * accpets only one (telnet) connection/session at a time. When one + * session is active, any subsequent attempt to connect to the NPS will + * result in a connection refused/closed failure. In a cluster environment + * or other environment utilizing polling/monitoring of the NPS + * (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 NPS became unresponsive. In some + * configurations this necessitated placing the power switch onto a + * private subnet. + */ + +#include <lha_internal.h> +#define DEVICE "WTI Network Power Switch" + +#define DOESNT_USE_STONITHKILLCOMM 1 + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN wti_nps +#define PIL_PLUGIN_S "wti_nps" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#define MAX_WTIPLUGINID 256 + +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * wti_nps_new(const char *); +static void wti_nps_destroy(StonithPlugin *); +static const char * const * wti_nps_get_confignames(StonithPlugin *); +static int wti_nps_set_config(StonithPlugin * , StonithNVpair * ); +static const char * wti_nps_get_info(StonithPlugin * s, int InfoType); +static int wti_nps_status(StonithPlugin * ); +static int wti_nps_reset_req(StonithPlugin * s, int request, const char * host); +static char ** wti_nps_hostlist(StonithPlugin *); + +static struct stonith_ops wti_npsOps ={ + wti_nps_new, /* Create new STONITH object */ + wti_nps_destroy, /* Destroy STONITH object */ + wti_nps_get_info, /* Return STONITH info string */ + wti_nps_get_confignames,/* Return configration parameters */ + wti_nps_set_config, /* set configration */ + wti_nps_status, /* Return STONITH device status */ + wti_nps_reset_req, /* Request a reset */ + wti_nps_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 + , &wti_npsOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * I have a NPS-110. This code has been tested with this switch. + * (Tested with NPS-230 and TPS-2 by lmb) + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + pid_t pid; + int rdfd; + int wrfd; + char * device; + char * passwd; +}; + +static const char * pluginid = "WTINPS-Stonith"; +static const char * NOTnpsid = "WTINPS device has been destroyed"; + +#include "stonith_config_xml.h" + +static const char *wti_npsXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_PASSWD_PARM + XML_PARAMETERS_END; + + +/* + * Different expect strings that we get from the WTI + * Network Power Switch + */ + +#define WTINPSSTR " Power Switch" +#define WTINBBSTR "Boot Bar" + +static struct Etoken password[] = { {"Password:", 0, 0}, {NULL,0,0}}; +static struct Etoken Prompt[] = { {"PS>", 0, 0} + , {"IPS>", 0, 0} + , {"BB>", 0, 0} + , {NULL,0,0}}; +static struct Etoken LoginOK[] = { {WTINPSSTR, 0, 0} + , {WTINBBSTR, 0, 0} + , {"Invalid password", 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[] = { {"rocessing - please wait", 0, 0} + , {"(Y/N):", 1, 0} + , {NULL,0,0}}; + +static int NPS_connect_device(struct pluginDevice * nps); +static int NPSLogin(struct pluginDevice * nps); +static int NPSNametoOutlet(struct pluginDevice*, const char * name, char **outlets); +static int NPSReset(struct pluginDevice*, char * outlets, const char * rebootid); +static int NPSLogout(struct pluginDevice * nps); + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int NPS_onoff(struct pluginDevice*, const char * outlets, const char * unitid +, int request); +#endif + +/* Attempt to login up to 20 times... */ +static int +NPSRobustLogin(struct pluginDevice * nps) +{ + int rc = S_OOPS; + int j = 0; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + for ( ; ; ) { + if (NPS_connect_device(nps) == S_OK) { + rc = NPSLogin(nps); + if (rc == S_OK) { + break; + } + } + if ((++j) == 20) { + break; + } + else { + sleep(1); + } + } + + return rc; +} + +/* Login to the WTI Network Power Switch (NPS) */ +static int +NPSLogin(struct pluginDevice * nps) +{ + char IDinfo[128]; + char * idptr = IDinfo; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Look for the unit type info */ + if (EXPECT_TOK(nps->rdfd, password, 2, IDinfo + , sizeof(IDinfo), Debug) < 0) { + LOG(PIL_CRIT, "No initial response from %s.", nps->idinfo); + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + idptr += strspn(idptr, WHITESPACE); + /* + * We should be looking at something like this: + * Enter Password: + */ + + SEND(nps->wrfd, nps->passwd); + SEND(nps->wrfd, "\r"); + /* Expect "Network Power Switch vX.YY" */ + + switch (StonithLookFor(nps->rdfd, LoginOK, 5)) { + + case 0: /* Good! */ + LOG(PIL_INFO, "Successful login to %s.", nps->idinfo); + break; + + case 1: /* Uh-oh - bad password */ + LOG(PIL_CRIT, "Invalid password for %s.", nps->idinfo); + return(S_ACCESS); + + default: + return(errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS); + } + return(S_OK); +} + +/* Log out of the WTI NPS */ + +static int +NPSLogout(struct pluginDevice* nps) +{ + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + /* + SEND(nps->wrfd, "/h\r"); + */ + /* Expect "PS>" */ + rc = StonithLookFor(nps->rdfd, Prompt, 5); + + /* "/x" is Logout, "/x,y" auto-confirms */ + SEND(nps->wrfd, "/x,y\r"); + + close(nps->wrfd); + close(nps->rdfd); + nps->wrfd = nps->rdfd = -1; + + return(rc >= 0 ? S_OK : (errno == ETIMEDOUT ? S_TIMEOUT : S_OOPS)); +} + +/* Reset (power-cycle) the given outlets */ +static int +NPSReset(struct pluginDevice* nps, char * outlets, const char * rebootid) +{ + char unum[32]; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send REBOOT command for given outlets */ + snprintf(unum, sizeof(unum), "/BOOT %s,y\r", outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing "... or "(Y/N)" (if confirmation turned on) */ + + retry: + switch (StonithLookFor(nps->rdfd, Processing, 5)) { + case 0: /* Got "Processing" Do nothing */ + break; + + case 1: /* Got that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + goto retry; + + default: + return(errno == ETIMEDOUT ? S_RESETFAIL : S_OOPS); + } + LOG(PIL_INFO, "Host is being rebooted: %s", rebootid); + + /* Expect "PS>" */ + if (StonithLookFor(nps->rdfd, Prompt, 60) < 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", rebootid); + SEND(nps->wrfd, "/h\r"); + return(S_OK); +} + +#if defined(ST_POWERON) && defined(ST_POWEROFF) +static int +NPS_onoff(struct pluginDevice* nps, const char * outlets, const char * unitid, int req) +{ + char unum[32]; + + const char * onoff = (req == ST_POWERON ? "/On" : "/Off"); + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + /* Send "/h" help command and expect prompt back */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* Send ON/OFF command for given outlet */ + snprintf(unum, sizeof(unum), "%s %s,y\r", onoff, outlets); + SEND(nps->wrfd, unum); + + /* Expect "Processing"... or "(Y/N)" (if confirmation turned on) */ + + if (StonithLookFor(nps->rdfd, Processing, 5) == 1) { + /* They've turned on that annoying command confirmation :-( */ + SEND(nps->wrfd, "Y\r"); + } + EXPECT(nps->rdfd, Prompt, 60); + + /* All Right! Command done. Life is Good! */ + LOG(PIL_INFO, "Power to NPS outlet(s) %s turned %s", outlets, onoff); + + SEND(nps->wrfd, "/h\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 +NPSNametoOutlet(struct pluginDevice* nps, const char * name, char **outlets) +{ + char NameMapping[128]; + int sockno; + char sockname[32]; + char buf[32]; + int left = 17; + int ret = -1; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + if ((*outlets = (char *)MALLOC(left*sizeof(char))) == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(-1); + } + + strncpy(*outlets, "", left); + left = left - 1; /* ensure terminating '\0' */ + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----+" so we can skip over it... */ + EXPECT(nps->rdfd, Separator, 5); + + do { + NameMapping[0] = EOS; + SNARF(nps->rdfd, NameMapping, 5); + + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + *last = EOS; + --last; + /* Strip off trailing blanks */ + for(; last > sockname; --last) { + if (*last == ' ') { + *last = EOS; + }else{ + break; + } + } + if (strncasecmp(name, sockname, 16) == 0) { + ret = sockno; + snprintf(buf, sizeof(buf), "%d ", sockno); + strncat(*outlets, buf, left); + left = left - strlen(buf); + } + } + } while (strlen(NameMapping) > 2 && left > 0); + + return(ret); +} + +static int +wti_nps_status(StonithPlugin *s) +{ + struct pluginDevice* nps; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps) != S_OK)) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(rc); + } + + /* Send "/h" help command and expect back prompt */ + SEND(nps->wrfd, "/h\r"); + /* Expect "PS>" */ + EXPECT(nps->rdfd, Prompt, 5); + + return(NPSLogout(nps)); +} + +/* + * Return the list of hosts (outlet names) for the devices on this NPS unit + */ + +static char ** +wti_nps_hostlist(StonithPlugin *s) +{ + char NameMapping[128]; + char* NameList[64]; + unsigned int numnames = 0; + char ** ret = NULL; + struct pluginDevice* nps; + unsigned int i; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,NULL); + + nps = (struct pluginDevice*) s; + if (NPSRobustLogin(nps) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + return(NULL); + } + + /* Expect "PS>" */ + NULLEXPECT(nps->rdfd, Prompt, 5); + + /* The status command output contains mapping of hosts to outlets */ + SEND(nps->wrfd, "/s\r"); + + /* Expect: "-----" so we can skip over it... */ + NULLEXPECT(nps->rdfd, Separator, 5); + NULLEXPECT(nps->rdfd, CRNL, 5); + + /* Looks Good! Parse the status output */ + + do { + int sockno; + char sockname[64]; + NameMapping[0] = EOS; + NULLSNARF(nps->rdfd, NameMapping, 5); + if (sscanf(NameMapping + , "%d | %16c",&sockno, sockname) == 2) { + + char * last = sockname+16; + 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 (!strcmp(sockname,"(undefined)") || + !strcmp(sockname,"---")) { + /* lhh - skip undefined */ + continue; + } + if ((nm = STRDUP(sockname)) == NULL) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + ++numnames; + NameList[numnames] = NULL; + } + } while (strlen(NameMapping) > 2); + + if (numnames >= 1) { + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + }else{ + memset(ret, 0, (numnames+1)*sizeof(char*)); + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + } + (void)NPSLogout(nps); + + 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 NPS device. We should add serial support here + * eventually... + */ +static int +NPS_connect_device(struct pluginDevice * nps) +{ + int fd = OurImports->OpenStreamSocket(nps->device + , TELNET_PORT, TELNET_SERVICE); + + if (fd < 0) { + return(S_OOPS); + } + nps->rdfd = nps->wrfd = fd; + return(S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +wti_nps_reset_req(StonithPlugin * s, int request, const char * host) +{ + int rc = 0; + int lorc = 0; + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s:called.", __FUNCTION__); + } + + ERRIFNOTCONFIGED(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = NPSRobustLogin(nps)) != S_OK) { + LOG(PIL_CRIT, "Cannot log into %s.", nps->idinfo); + }else{ + char *outlets; + int noutlet; + + outlets = NULL; + noutlet = NPSNametoOutlet(nps, host, &outlets); + + if (noutlet < 1) { + LOG(PIL_WARN, "%s doesn't control host [%s]" + , nps->device, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + return(S_BADHOST); + } + switch(request) { + +#if defined(ST_POWERON) && defined(ST_POWEROFF) + case ST_POWERON: + case ST_POWEROFF: + rc = NPS_onoff(nps, outlets, host, request); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; +#endif + case ST_GENERIC_RESET: + rc = NPSReset(nps, outlets, host); + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + default: + rc = S_INVAL; + if (outlets != NULL) { + FREE(outlets); + outlets = NULL; + } + break; + } + } + + lorc = NPSLogout(nps); + return(rc != S_OK ? rc : lorc); +} + +/* + * Parse the information in the given string, + * and stash it away... + */ +static int +wti_nps_set_config(StonithPlugin * s, StonithNVpair *list) +{ + struct pluginDevice* nps; + StonithNamesToGet namestocopy [] = + { {ST_IPADDR, NULL} + , {ST_PASSWD, NULL} + , {NULL, NULL} + }; + int rc; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.\n", __FUNCTION__); + } + + ERRIFWRONGDEV(s,S_OOPS); + + nps = (struct pluginDevice*) s; + + if ((rc = OurImports->CopyAllValues(namestocopy, list)) != S_OK) { + return rc; + } + nps->device = namestocopy[0].s_value; + nps->passwd = namestocopy[1].s_value; + return S_OK; +} + + +/* + * Return the Stonith plugin configuration parameter + * + */ +static const char * const * +wti_nps_get_confignames(StonithPlugin * p) +{ + static const char * names[] = { ST_IPADDR , ST_PASSWD , NULL}; + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + return names; +} + +/* + * Get info about the stonith device + * + */ +static const char * +wti_nps_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice* nps; + const char * ret; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + ERRIFWRONGDEV(s,NULL); + + /* + * We look in the ST_TEXTDOMAIN catalog for our messages + */ + nps = (struct pluginDevice *)s; + + switch (reqtype) { + + case ST_DEVICEID: + ret = nps->idinfo; + break; + case ST_DEVICENAME: + ret = nps->device; + break; + case ST_DEVICEDESCR: + ret = "Western Telematic (WTI) Network Power Switch Devices (NPS-xxx)\n" + "Also supports the WTI Telnet Power Switch Devices (TPS-xxx)\n" + "NOTE: The WTI Network Power Switch, accepts only " + "one (telnet) connection/session at a time."; + break; + case ST_DEVICEURL: + ret = "http://www.wti.com/"; + break; + case ST_CONF_XML: /* XML metadata */ + ret = wti_npsXML; + break; + default: + ret = NULL; + break; + } + return ret; +} + +/* + * WTI NPS Stonith destructor... + */ +static void +wti_nps_destroy(StonithPlugin *s) +{ + struct pluginDevice* nps; + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + VOIDERRIFWRONGDEV(s); + + nps = (struct pluginDevice *)s; + + nps->pluginid = NOTnpsid; + if (nps->rdfd >= 0) { + close(nps->rdfd); + nps->rdfd = -1; + } + if (nps->wrfd >= 0) { + close(nps->wrfd); + nps->wrfd = -1; + } + if (nps->device != NULL) { + FREE(nps->device); + nps->device = NULL; + } + if (nps->passwd != NULL) { + FREE(nps->passwd); + nps->passwd = NULL; + } + FREE(nps); +} + +/* Create a new BayTech Stonith device. */ + +static StonithPlugin * +wti_nps_new(const char *subplugin) +{ + struct pluginDevice* nps = ST_MALLOCT(struct pluginDevice); + + if (Debug) { + LOG(PIL_DEBUG, "%s: called.", __FUNCTION__); + } + + if (nps == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + memset(nps, 0, sizeof(*nps)); + nps->pluginid = pluginid; + nps->pid = -1; + nps->rdfd = -1; + nps->wrfd = -1; + nps->device = NULL; + nps->passwd = NULL; + nps->idinfo = DEVICE; + nps->sp.s_ops = &wti_npsOps; + + return &(nps->sp); +} + |