diff options
Diffstat (limited to 'lib/plugins/stonith/cyclades.c')
-rw-r--r-- | lib/plugins/stonith/cyclades.c | 650 |
1 files changed, 650 insertions, 0 deletions
diff --git a/lib/plugins/stonith/cyclades.c b/lib/plugins/stonith/cyclades.c new file mode 100644 index 0000000..6744cd4 --- /dev/null +++ b/lib/plugins/stonith/cyclades.c @@ -0,0 +1,650 @@ +/* + * Stonith module for Cyclades AlterPath PM + * Bases off the SSH plugin + * + * Copyright (c) 2004 Cyclades corp. + * + * Author: Jon Taylor <jon.taylor@cyclades.com> + * + * Rewritten from scratch using baytech.c structure and code + * and currently maintained by + * Marcelo Tosatti <marcelo.tosatti@cyclades.com> + * + * 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 + * + */ + +#include <lha_internal.h> + +#define DEVICE "Cyclades AlterPath PM" + +#define DOESNT_USE_STONITHSCANLINE + +#include "stonith_plugin_common.h" + +#define PIL_PLUGIN cyclades +#define PIL_PLUGIN_S "cyclades" +#define PIL_PLUGINLICENSE LICENSE_LGPL +#define PIL_PLUGINLICENSEURL URL_LGPL +#include <pils/plugin.h> + +#include "stonith_signal.h" + +static StonithPlugin * cyclades_new(const char *); +static void cyclades_destroy(StonithPlugin *); +static int cyclades_set_config(StonithPlugin *, StonithNVpair *); +static const char * const * cyclades_get_confignames(StonithPlugin * s); +static const char * cyclades_get_info(StonithPlugin * s, int InfoType); +static int cyclades_status(StonithPlugin *); +static int cyclades_reset_req(StonithPlugin * s, int request, const char * host); +static char ** cyclades_hostlist(StonithPlugin *); + + + +static struct stonith_ops cycladesOps ={ + cyclades_new, /* Create new STONITH object */ + cyclades_destroy, /* Destroy STONITH object */ + cyclades_get_info, /* Return STONITH info string */ + cyclades_get_confignames, /* Return STONITH config vars */ + cyclades_set_config, /* set configuration from vars */ + cyclades_status, /* Return STONITH device status */ + cyclades_reset_req, /* Request a reset */ + cyclades_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 + , &cycladesOps + , NULL /*close */ + , &OurInterface + , (void*)&OurImports + , &interfprivate); +} + +/* + * Cyclades STONITH device + * + */ + +struct pluginDevice { + StonithPlugin sp; + const char * pluginid; + const char * idinfo; + char * device; + char * user; + + int serial_port; + + /* pid of ssh client process and its in/out file descriptors */ + pid_t pid; + int rdfd, wrfd; +}; + +static struct Etoken StatusOutput[] = { + { "Outlet\t\tName\t\tStatus\t\tUsers\t\tInterval (s)", 1, 0}, + { "Outlet\tName\t\t\tStatus\t\tInterval (s)\tUsers", 2, 0}, + { "Outlet Name Status Post-on Delay(s)", 3, 0}, + { NULL, 0, 0} +}; + +static struct Etoken CRNL[] = { {"\n\r",0,0},{"\r\n",0,0},{NULL,0,0}}; + + +/* Commands of PM devices */ +static char status_all[] = "status all"; +static char cycle[] = "cycle"; + +static int CYC_robust_cmd(struct pluginDevice *, char *); + +static const char * pluginid = "CycladesDevice-Stonith"; +static const char * NOTpluginID = "Cyclades device has been destroyed"; + +#define MAX_OUTLETS 128 + +#define ST_SERIALPORT "serialport" + +#define ZEROEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) \ + return(0); \ + } + +#define RESETEXPECT(fd,p,t) { \ + if (StonithLookFor(fd, p, t) < 0) { \ + FREE(outletstr); \ + return(errno == ETIMEDOUT \ + ? S_RESETFAIL : S_OOPS); \ + } \ + } + +#include "stonith_config_xml.h" + +#define XML_SERIALPORT_SHORTDESC \ + XML_PARM_SHORTDESC_BEGIN("en") \ + ST_SERIALPORT \ + XML_PARM_SHORTDESC_END + +#define XML_SERIALPORT_LONGDESC \ + XML_PARM_LONGDESC_BEGIN("en") \ + "The serial port of the IPDU which can powercycle the node" \ + XML_PARM_LONGDESC_END + +#define XML_SERIALPORT_PARM \ + XML_PARAMETER_BEGIN(ST_SERIALPORT, "string", "1", "0") \ + XML_SERIALPORT_SHORTDESC \ + XML_SERIALPORT_LONGDESC \ + XML_PARAMETER_END + +static const char *cycladesXML = + XML_PARAMETERS_BEGIN + XML_IPADDR_PARM + XML_LOGIN_PARM + XML_SERIALPORT_PARM + XML_PARAMETERS_END; + +static int +CYCScanLine(struct pluginDevice *sd, int timeout, char * buf, int max) +{ + if (EXPECT_TOK(sd->rdfd, CRNL, timeout, buf, max, Debug) < 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + return(S_OOPS); + } + return(S_OK); +} + +static int +cyclades_status(StonithPlugin *s) +{ + struct pluginDevice *sd; + char *cmd = status_all; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return(S_OOPS); + } + + EXPECT(sd->rdfd, StatusOutput, 50); + + return(S_OK); +} + +static int CYC_run_command(struct pluginDevice *sd, char *cmd) +{ + char SshCommand[MAX_OUTLETS*4]; + + snprintf(SshCommand, sizeof(SshCommand), + "exec ssh -q %s@%s /bin/pmCommand %d %s 2>/dev/null", + sd->user, sd->device, sd->serial_port, cmd); + + sd->pid = STARTPROC(SshCommand, &sd->rdfd, &sd->wrfd); + + if (sd->pid <= 0) { + return(S_OOPS); + } + + return(S_OK); +} + +static int +CYC_robust_cmd(struct pluginDevice *sd, char *cmd) +{ + int rc = S_OOPS; + int i; + + for (i=0; i < 20 && rc != S_OK; i++) { + + if (sd->pid > 0) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + } + + if (CYC_run_command(sd, cmd) != S_OK) { + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + continue; + } + rc = S_OK; + } + + return rc; +} + +#define MAXSAVE 512 +static int CYCNametoOutlet(struct pluginDevice *sd, const char *host, int *outlets, int maxoutlet) +{ + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err; + int outlet, numoutlet = 0; + char name[17], locked[11], on[4]; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return 0; + } + + ZEROEXPECT(sd->rdfd, StatusOutput, 50); + + ZEROEXPECT(sd->rdfd, CRNL, 50); + + do { + + memset(savebuf, 0, sizeof(savebuf)); + memset(name, 0, sizeof(name)); + memset(locked, 0, sizeof(locked)); + memset(on, 0, sizeof(on)); + + err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf)); + + if ((err == S_OK) && + (sscanf(savebuf,"%3d %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + if (!strncasecmp(name, host, strlen(host))) { + if (numoutlet >= maxoutlet) { + LOG(PIL_CRIT, "too many outlets"); + return 0; + } + outlets[numoutlet++] = outlet; + } + } + + } while (err == S_OK); + + return (numoutlet); +} + + +/* + * Return the list of hosts configured for this Cyclades device + */ + +static char ** +cyclades_hostlist(StonithPlugin *s) +{ + struct pluginDevice* sd; + char *cmd = status_all; + char savebuf[MAXSAVE]; + int err, i; + int outlet; + int numnames = 0; + char name[17], locked[11], on[4]; + char *NameList[MAX_OUTLETS]; + char **ret = NULL; + + ERRIFNOTCONFIGED(s,NULL); + + sd = (struct pluginDevice*) s; + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run status all command"); + return (NULL); + } + + memset(savebuf, 0, sizeof(savebuf)); + + NULLEXPECT(sd->rdfd, StatusOutput, 50); + + NULLEXPECT(sd->rdfd, CRNL, 50); + + do { + char *nm; + + memset(savebuf, 0, sizeof(savebuf)); + memset(name, 0, sizeof(name)); + memset(locked, 0, sizeof(locked)); + memset(on, 0, sizeof(on)); + + err = CYCScanLine(sd, 2, savebuf, sizeof(savebuf)); + + if ((err == S_OK) && + (sscanf(savebuf,"%3d %16s %10s %3s", &outlet, + name, locked, on) > 0)) { + nm = (char *) STRDUP (name); + if (!nm) { + goto out_of_memory; + } + strdown(nm); + NameList[numnames] = nm; + numnames++; + NameList[numnames] = NULL; + } + + } while (err == S_OK); + + if (numnames) { + + ret = (char **)MALLOC((numnames+1)*sizeof(char*)); + if (ret == NULL) { + goto out_of_memory; + } else { + memcpy(ret, NameList, (numnames+1)*sizeof(char*)); + } + return (ret); + } + return(ret); + +out_of_memory: + LOG(PIL_CRIT, "out of memory"); + for (i=0; i<numnames; i++) { + FREE(NameList[i]); + } + + return (NULL); +} + + +static char *cyclades_outletstr(int *outlet, int numoutlet) +{ + int i, len; + char *ret; + + /* maximum length per outlet is currently four (outlet is one to + * three digits, followed by either a comma or null), so add one + * for good measure */ + len = numoutlet * 5 * sizeof(char); + if ((ret = MALLOC(len)) != NULL) { + snprintf(ret, len, "%d", outlet[0]); + for (i = 1; i < numoutlet; i++) { + char buf[5]; + snprintf(buf, sizeof(buf), ",%d", outlet[i]); + strcat(ret, buf); + } + } + return(ret); +} + + +static int cyclades_onoff(struct pluginDevice *sd, int *outlet, int numoutlet, + const char *unitid, int req) +{ + const char * onoff; + char cmd[MAX_OUTLETS*4], expstring[64]; + struct Etoken exp[] = {{NULL, 0, 0}, {NULL, 0, 0}}; + char *outletstr; + int i; + + onoff = (req == ST_POWERON ? "on" : "off"); + + memset(cmd, 0, sizeof(cmd)); + + outletstr = cyclades_outletstr(outlet, numoutlet); + if (outletstr == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (S_OOPS); + } + snprintf(cmd, sizeof(cmd), "%s %s", onoff, outletstr); + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run %s command", onoff); + FREE(outletstr); + return(S_OOPS); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring), "%d: Outlet turned %s." + , outlet[i], onoff); + + exp[0].string = expstring; + + /* FIXME: should handle "already powered on/off" case and inform + to log */ + + EXPECT(sd->rdfd, exp, 50); + } + + LOG(PIL_DEBUG, "Power to host %s turned %s", unitid, onoff); + + FREE(outletstr); + return (S_OK); +} + +static int cyclades_reset(struct pluginDevice *sd, int *outlet, int numoutlet, + const char *unitid) +{ + char cmd[MAX_OUTLETS*4], expstring[64]; + struct Etoken exp[] = {{NULL, 0, 0}, {NULL, 0, 0}}; + char *outletstr; + int i; + + memset(cmd, 0, sizeof(cmd)); + + outletstr = cyclades_outletstr(outlet, numoutlet); + if (outletstr == NULL) { + LOG(PIL_CRIT, "out of memory"); + return (S_OOPS); + } + snprintf(cmd, sizeof(cmd), "%s %s", cycle, outletstr); + + LOG(PIL_INFO, "Host %s being rebooted.", unitid); + + if (CYC_robust_cmd(sd, cmd) != S_OK) { + LOG(PIL_CRIT, "can't run cycle command"); + FREE(outletstr); + return(S_OOPS); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring) + , "%d: Outlet turned off.", outlet[i]); + + exp[0].string = expstring; + RESETEXPECT(sd->rdfd, exp, 50); + } + + for (i = 0; i < numoutlet; i++) { + memset(expstring, 0, sizeof(expstring)); + snprintf(expstring, sizeof(expstring) + , "%d: Outlet turned on.", outlet[i]); + + exp[0].string = expstring; + RESETEXPECT(sd->rdfd, exp, 50); + } + + FREE(outletstr); + return (S_OK); +} + +/* + * Reset the given host on this Stonith device. + */ +static int +cyclades_reset_req(StonithPlugin * s, int request, const char * host) +{ + struct pluginDevice *sd; + int rc = 0; + int numoutlet, outlets[MAX_OUTLETS]; + + ERRIFNOTCONFIGED(s,S_OOPS); + + sd = (struct pluginDevice*) s; + + numoutlet = CYCNametoOutlet(sd, host, outlets, MAX_OUTLETS); + + if (!numoutlet) { + LOG(PIL_CRIT, "Unknown host %s to Cyclades PM", host); + return (S_OOPS); + } + + + switch (request) { + case ST_POWERON: + case ST_POWEROFF: + rc = cyclades_onoff(sd, outlets, numoutlet, host, request); + break; + + case ST_GENERIC_RESET: + rc = cyclades_reset(sd, outlets, numoutlet, host); + break; + default: + rc = S_INVAL; + break; + } + + return rc; +} + +static const char * const * +cyclades_get_confignames(StonithPlugin * s) +{ + static const char * ret[] = {ST_IPADDR, ST_LOGIN, ST_SERIALPORT, NULL}; + return ret; +} + +/* + * Parse the config information in the given string, and stash it away... + */ +static int +cyclades_set_config(StonithPlugin* s, StonithNVpair* list) +{ + struct pluginDevice* sd = (struct pluginDevice *)s; + int rc; + StonithNamesToGet namestocopy[] = + { {ST_IPADDR, NULL} + , {ST_LOGIN, NULL} + , {ST_SERIALPORT, 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->serial_port = atoi(namestocopy[2].s_value); + FREE(namestocopy[2].s_value); + + return(S_OK); +} + +static const char * +cyclades_get_info(StonithPlugin * s, int reqtype) +{ + struct pluginDevice * sd; + const char * ret; + + ERRIFWRONGDEV(s, NULL); + + sd = (struct pluginDevice*) s; + + switch (reqtype) { + case ST_DEVICEID: /* What type of device? */ + /* FIXME: could inform the exact PM model */ + ret = sd->idinfo; + break; + + case ST_DEVICENAME: /* What particular device? */ + ret = sd->device; + break; + + case ST_DEVICEDESCR: /* Description of dev type */ + ret = "Cyclades AlterPath PM " + "series power switches (via TS/ACS/KVM)."; + break; + + case ST_DEVICEURL: /* Manufacturer's web site */ + ret = "http://www.cyclades.com/"; + break; + + case ST_CONF_XML: /* XML metadata */ + ret = cycladesXML; + break; + + default: + ret = NULL; + break; + } + return ret; +} + +/* + * Cyclades Stonith destructor... + */ +static void +cyclades_destroy(StonithPlugin *s) +{ + struct pluginDevice* sd; + + VOIDERRIFWRONGDEV(s); + + sd = (struct pluginDevice*) s; + + sd->pluginid = NOTpluginID; + Stonithkillcomm(&sd->rdfd, &sd->wrfd, &sd->pid); + if (sd->device != NULL) { + FREE(sd->device); + sd->device = NULL; + } + if (sd->user != NULL) { + FREE(sd->user); + sd->user = NULL; + } + + FREE(sd); +} + +/* Create a new cyclades Stonith device */ +static StonithPlugin * +cyclades_new(const char *plugin) +{ + struct pluginDevice* sd = ST_MALLOCT(struct pluginDevice); + + if (sd == NULL) { + LOG(PIL_CRIT, "out of memory"); + return(NULL); + } + + memset(sd, 0, sizeof(*sd)); + sd->pluginid = pluginid; + sd->pid = -1; + sd->rdfd = -1; + sd->wrfd = -1; + sd->idinfo = DEVICE; + sd->sp.s_ops = &cycladesOps; + + return &(sd->sp); /* same as sd */ +} |